File: calendar.tcl

package info (click to toggle)
classified-ads 0.13-1
  • links: PTS, VCS
  • area: main
  • in suites: bullseye, buster
  • size: 6,772 kB
  • sloc: cpp: 34,291; tcl: 1,175; xml: 64; makefile: 40
file content (1622 lines) | stat: -rw-r--r-- 71,748 bytes parent folder | download | duplicates (2)
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
#     -*-TCL-*- -*-coding: utf-8-unix;-*-
#  Classified Ads is Copyright (c) Antti Järvinen 2013-2017.
#
#  This file is part of Classified Ads.
#
#  Classified Ads is free software; you can redistribute it and/or
#  modify it under the terms of the GNU Lesser General Public
#  License as published by the Free Software Foundation; either
#  version 2.1 of the License, or (at your option) any later version.
#
#  Classified Ads is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#  Lesser General Public License for more details.
#
#  You should have received a copy of the GNU Lesser General Public
#  License along with Classified Ads; if not, write to the Free Software
#  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
#  This file implements gregorian calendar with shared calendar events. 
#

#
#  Workflows in operations: 
#  - Operation when starting up:
#  -- Init UI
#  -- Load local settings
#  -- Set day to display (current time, in practice)
#  -- Fetch events of selected day. As Db records in db contain events so
#     that from each operator there is one months records in single record
#     the search is done by month and all events found for month are
#     appended into global array "::allEvents"
#  -- Events from "::allEvents" that occur on selected day, are drawn into
#     canvas for user to ogle
#  -- Fetch event comments from storage like what is done to events.
#     Comments are stored in a dictionary containing list of dictionaries, 
#     key to first dictionary is the event that is commented, inside
#     is a list of comments, each being a dictionary. In storage 
#     (local and published records) comments are saved in similar
#     structure. 
#
#  - Operations when user selects another day to be shown or changes
#    event collection to display
#  -- Fetch events of selected day. The database implementation of 
#     classified ads will not re-send db record queries to remote 
#     nodes if query criteria remains the same, so re-doing same
#     query multiple times is only small waste of resources. 
#     Anyway, by checking whether month changed and querying the actual
#     database only when month changes will speed up operations
#  -- Events from "::allEvents" that occur on selected day, are drawn into
#     canvas for user to ogle
#
#  - Operations when user posts a new event
#  -- Event is appended into "::allEvents". 
#  -- Local database (the application specific non-shared db) is
#     updated to contain the event (and retain events of other operator
#     profiles that may be stored in same database record)
#  -- Depending on publicity setting of the event (public, by profile setting)
#     a shared database record is created/updated to contain the new event
#     and that record is published to network
# 
#  - Operations when user deletes an event
#  -- Same as "post" put event is removed from storages and records. Publish
#     needs to occur if deleted event as public or by profile settings publicity.
#
#  - Operations when new database record is received into system
#  -- This record may or may not contain records from collection in display,
#     it may or may not contain events that belong to our selected month/day.
#     If there is events for selected collection it is easiest to prune all
#     events from "::allEvents" that originate from newly-received record,
#     then do a re-display of selected day. 
#
# procedure for initializing global variables
proc initGlobals {} {
    # Month on display, global variable
    set ::monthOnDisplay [ clock seconds ]
    # Selected day on display, global variable:
    set ::dayOnDisplay $::monthOnDisplay
    set ::leftMarginInPixels 100
    # This is used in canvas resize event to not try updating until
    # initialization is wholly done
    set ::initReady false
    # Timer for delaying re-draw events when user keeps on scaling the window
    set ::resizeEventTimer -1
    # Global list for events in calendar
    set ::allEvents [ list ]
    # Global list of events to store locally
    set ::allLocalEvents [ list ]
    # Global dictionary for db records whose events are in ::allEvents.
    # Key in dict is the record id, value is publish time. Idea is that
    # if we fetch same record again and the publish time is same 
    # as previously then content has not changed and there is no need
    # to process it at all.
    set ::allDbRecords [ dict create ]
    # Program settings is a dictionary
    set ::settings [ dict create ]
    # What is the intended audience to new events:
    set ::postPublicity public
    # What is the default collection for events
    set ::collectionInUse "GlobalEventCollection"
    # Dictionary for comments, key is the commented event, 
    # inside is a list of comments
    set ::allComments [ dict create ]
    # in similar manner as there is ::allEvents and ::allLocalEvents,
    # lets have also
    set ::allLocalComments [ dict create ]
}

# 
# Init procedure for user interface widgets. Contains several sub-procedures
# that are called by this proc.
#
proc initUI {} {
    # figure out initial size for widgets, something smaller than screen size
    set ::initialWidth [ expr int ( [ winfo screenwidth . ] * 0.90 ) ]
    set ::initialHeight [ expr int ( [ winfo screenheight . ] * 0.85 ) ]
    # frame for display day-selection calendar    
    frame .s -borderwidth 2 -relief ridge -width $::initialWidth -height $::initialHeight
    grid .s -row 0 -column 0 -sticky news
    # frame for buttons on top
    frame .rightframe
    grid .rightframe -row 0 -column 1 -sticky news
    # configure grid for expanding:
    grid rowconfigure . 0 -weight 1
    grid columnconfigure . .rightframe -weight 1
    frame .rightframe.buttons -borderwidth 2 -relief ridge
    button .rightframe.buttons.dayview -text {Calendar view} -command showDayView
    button .rightframe.buttons.settings -text {Settings} -command showSettings
    button .rightframe.buttons.newEvent -text {Create event} -command showCreateEvent
    pack .rightframe.buttons -side top
    pack .rightframe.buttons.settings -padx 5 -side right
    pack .rightframe.buttons.dayview -padx 5 -side right
    pack .rightframe.buttons.newEvent -padx 5 -side right
    frame .rightframe.dayframe -borderwidth 2 -relief ridge
    set dayFrameWidth [ expr int($::initialWidth  * 0.75) ]
    set dayFrameHeight [ expr int($::initialHeight  * 0.90) ]
    canvas .rightframe.dayframe.daycanvas -relief ridge -borderwidth 2 -height $dayFrameHeight -width $dayFrameWidth -scrollregion " 0 0 $dayFrameWidth 1000 "
    # ask for resize events:
    bind .rightframe.dayframe.daycanvas <Configure> {
        daycanvasResizeEvent %w %h
    }
    scrollbar .rightframe.dayframe.hscroll -command scrollDayview
    .rightframe.dayframe.hscroll set 0 0.3333

    # set up things visible in settings frame
    frame .rightframe.settingsframe 
    label .rightframe.settingsframe.collectionText -text {Name of event collection in use: }
    entry .rightframe.settingsframe.collectionEntry
    button .rightframe.settingsframe.changeCollectionBtn -text Change -command changeCollectionCallback
    pack .rightframe.settingsframe.changeCollectionBtn -side right -anchor sw
    pack .rightframe.settingsframe.collectionEntry -side right -anchor sw
    pack .rightframe.settingsframe.collectionText -side right -expand y -fill x  -anchor sw
    .rightframe.settingsframe.collectionEntry insert 0 $::collectionInUse
    # UI frame for writing and posting a new event
    frame .rightframe.newEventFrame
    constructNewEventView .rightframe.newEventFrame
}

#
# Procedure called when "new event" is selected ; initializes start+end time 
# labels.  See also displayNewEventDates. 
#
proc initializeNewEventDates {} {
    set currentLabelText [ lindex [ .rightframe.newEventFrame.timeFrame.startDayLabel configure -text ] 4 ]
    if { $currentLabelText == {} } {

        set month [ clock format $::dayOnDisplay -format {%N} ]
        set year [ clock format $::dayOnDisplay -format {%Y} ]
        set day [ clock format $::dayOnDisplay -format {%d} ]
        set hour [ clock format [ clock seconds ] -format {%H} ]
        set min 00

	# remove possible leading zeroes:
	regsub {^[0]} $month {\1} month
	regsub {^[0]} $day {\1} day
	regsub {^[0]} $hour {\1} hour
	regsub {^[0]} $min {\1} min
	
        set ::newEventStart [ clock scan [ format "%04d %02d %02d %02d %02d 00" $year $month $day $hour $min] -format {%Y %m %d %H %M %M} -gmt false ]
        # push start time to next hour
        set ::newEventStart [ clock add $::newEventStart 1 hour ]
        # and end time one hour after that:
        set ::newEventEnd [ clock add $::newEventStart 1 hour ]
        displayNewEventDates
    }
}
#
# displays $::newEventStart on labels
#
proc displayNewEventDates {} {
    .rightframe.newEventFrame.timeFrame.startDayLabel configure -text [ clock format $::newEventStart -format {%d} ]
    .rightframe.newEventFrame.timeFrame.startMonthLabel configure -text [ clock format $::newEventStart -format {%B} -locale system ]
    .rightframe.newEventFrame.timeFrame.startYearLabel configure -text [ clock format $::newEventStart -format {%Y} ]
    .rightframe.newEventFrame.timeFrame.startHourLabel configure -text [ clock format $::newEventStart -format {%H} -locale system ]
    .rightframe.newEventFrame.timeFrame.startMinuteLabel configure -text [ clock format $::newEventStart -format {%M} ]

    .rightframe.newEventFrame.timeFrame.endDayLabel configure -text [ clock format $::newEventEnd -format {%d} ]
    .rightframe.newEventFrame.timeFrame.endMonthLabel configure -text [ clock format $::newEventEnd -format {%B} -locale system ]
    .rightframe.newEventFrame.timeFrame.endYearLabel configure -text [ clock format $::newEventEnd -format {%Y} ]
    .rightframe.newEventFrame.timeFrame.endHourLabel configure -text [ clock format $::newEventEnd -format {%H} -locale system ]
    .rightframe.newEventFrame.timeFrame.endMinuteLabel configure -text [ clock format $::newEventEnd -format {%M} ]
}

#
# button callback called from plus/minus buttons at event date selection
#
proc modifyEventTime { startOrEnd modification } {
    if { $startOrEnd == "start" } {
        set dateToModify $::newEventStart
    } else {
        set dateToModify $::newEventEnd
    }
    switch $modification {
        +d {
            set dateToModify [ clock add $dateToModify 1 day ]
        }
        +mo {
            set dateToModify [ clock add $dateToModify 1 month ]
        }
        +y {
            set dateToModify [ clock add $dateToModify 1 year ]
        }
        +h {
            set dateToModify [ clock add $dateToModify 1 hour ]
        }
        +mi {
            set dateToModify [ clock add $dateToModify 1 minute ]
        }
        -d {
            set dateToModify [ clock add $dateToModify -1 day ]
        }
        -mo {
            set dateToModify [ clock add $dateToModify -1 month ]
        }
        -y {
            set dateToModify [ clock add $dateToModify -1 year ]
        }
        -h {
            set dateToModify [ clock add $dateToModify -1 hour ]
        }
        -mi {
            set dateToModify [ clock add $dateToModify -1 minute ]
        }
    }
    if { $startOrEnd == "start" } {
        set ::newEventStart $dateToModify
    } else {
        set ::newEventEnd $dateToModify
    }
    displayNewEventDates
}

proc showDayView {} {
    pack forget .rightframe.settingsframe
    pack forget .rightframe.newEventFrame
    pack .rightframe.dayframe.hscroll -side right -fill y 
    pack .rightframe.dayframe.daycanvas -fill both -expand yes
    pack .rightframe.dayframe -side bottom -expand yes -fill both
}

proc showSettings {} {
    pack forget .rightframe.dayframe
    pack forget .rightframe.newEventFrame
    pack .rightframe.settingsframe -side top
}

proc showCreateEvent {} {
    pack forget .rightframe.dayframe
    pack forget .rightframe.settingsframe
    pack .rightframe.newEventFrame -side bottom -fill both -expand yes
    initializeNewEventDates
}


#
# basic structure: fields on top of each others
# first is start time, then end time, then title + desc
# followed by possible attendees and publish options
# times are constructed as grids with fields for date-values
# and +- -buttons
#
proc constructNewEventView { baseWidget } {
    # buttons for start time
    frame $baseWidget.timeFrame
    pack $baseWidget.timeFrame -fill both -expand y
    button $baseWidget.timeFrame.startDayPlusBtn -text "+" -command {modifyEventTime start +d} -repeatdelay 500 -repeatinterval 150
    button $baseWidget.timeFrame.startMonthPlusBtn -text "+" -command {modifyEventTime start +mo}  -repeatdelay 500 -repeatinterval 150
    button $baseWidget.timeFrame.startYearPlusBtn -text "+" -command {modifyEventTime start +y}  -repeatdelay 500 -repeatinterval 150
    button $baseWidget.timeFrame.startHourPlusBtn -text "+" -command {modifyEventTime start +h}  -repeatdelay 500 -repeatinterval 150
    button $baseWidget.timeFrame.startMinutePlusBtn -text "+" -command {modifyEventTime start +mi}  -repeatdelay 500 -repeatinterval 150
    
    grid $baseWidget.timeFrame.startDayPlusBtn -row 0 -column 1
    grid $baseWidget.timeFrame.startMonthPlusBtn -row 0 -column 2
    grid $baseWidget.timeFrame.startYearPlusBtn -row 0 -column 3
    grid $baseWidget.timeFrame.startHourPlusBtn -row 0 -column 4
    grid $baseWidget.timeFrame.startMinutePlusBtn -row 0 -column 6

    label $baseWidget.timeFrame.startTimeLabel -text "Event start: "
    label $baseWidget.timeFrame.startDayLabel 
    label $baseWidget.timeFrame.startMonthLabel 
    label $baseWidget.timeFrame.startYearLabel 
    label $baseWidget.timeFrame.startHourLabel 
    label $baseWidget.timeFrame.startTimeColonLabel -text ":"
    label $baseWidget.timeFrame.startMinuteLabel 

    grid $baseWidget.timeFrame.startTimeLabel -row 1 -column 0 -sticky e
    grid $baseWidget.timeFrame.startDayLabel -row 1 -column 1
    grid $baseWidget.timeFrame.startMonthLabel -row 1 -column 2
    grid $baseWidget.timeFrame.startYearLabel -row 1 -column 3
    grid $baseWidget.timeFrame.startHourLabel -row 1 -column 4
    grid $baseWidget.timeFrame.startTimeColonLabel -row 1 -column 5
    grid $baseWidget.timeFrame.startMinuteLabel -row 1 -column 6

    button $baseWidget.timeFrame.startDayMinusBtn -text "-" -command {modifyEventTime start -d}  -repeatdelay 500 -repeatinterval 150
    button $baseWidget.timeFrame.startMonthMinusBtn -text "-" -command {modifyEventTime start -mo}  -repeatdelay 500 -repeatinterval 150
    button $baseWidget.timeFrame.startYearMinusBtn -text "-" -command {modifyEventTime start -y}  -repeatdelay 500 -repeatinterval 150
    button $baseWidget.timeFrame.startHourMinusBtn -text "-" -command {modifyEventTime start -h}  -repeatdelay 500 -repeatinterval 150
    button $baseWidget.timeFrame.startMinuteMinusBtn -text "-" -command {modifyEventTime start -mi}  -repeatdelay 500 -repeatinterval 150

    grid $baseWidget.timeFrame.startDayMinusBtn -row 2 -column 1
    grid $baseWidget.timeFrame.startMonthMinusBtn -row 2 -column 2
    grid $baseWidget.timeFrame.startYearMinusBtn -row 2 -column 3
    grid $baseWidget.timeFrame.startHourMinusBtn -row 2 -column 4
    grid $baseWidget.timeFrame.startMinuteMinusBtn -row 2 -column 6

    #end time selector:
    button $baseWidget.timeFrame.endDayPlusBtn -text "+" -command {modifyEventTime end +d}  -repeatdelay 500 -repeatinterval 150
    button $baseWidget.timeFrame.endMonthPlusBtn -text "+" -command {modifyEventTime end +mo}  -repeatdelay 500 -repeatinterval 150
    button $baseWidget.timeFrame.endYearPlusBtn -text "+" -command {modifyEventTime end +y}  -repeatdelay 500 -repeatinterval 150
    button $baseWidget.timeFrame.endHourPlusBtn -text "+" -command {modifyEventTime end +h} -repeatdelay 500 -repeatinterval 150
    button $baseWidget.timeFrame.endMinutePlusBtn -text "+" -command {modifyEventTime end +mi} -repeatdelay 500 -repeatinterval 150
    
    grid $baseWidget.timeFrame.endDayPlusBtn -row 3 -column 1
    grid $baseWidget.timeFrame.endMonthPlusBtn -row 3 -column 2
    grid $baseWidget.timeFrame.endYearPlusBtn -row 3 -column 3
    grid $baseWidget.timeFrame.endHourPlusBtn -row 3 -column 4
    grid $baseWidget.timeFrame.endMinutePlusBtn -row 3 -column 6

    label $baseWidget.timeFrame.endTimeLabel -text "Event end: "
    label $baseWidget.timeFrame.endDayLabel 
    label $baseWidget.timeFrame.endMonthLabel 
    label $baseWidget.timeFrame.endYearLabel 
    label $baseWidget.timeFrame.endHourLabel 
    label $baseWidget.timeFrame.endTimeColonLabel -text ":"
    label $baseWidget.timeFrame.endMinuteLabel 

    grid $baseWidget.timeFrame.endTimeLabel -row 4 -column 0 -sticky e
    grid $baseWidget.timeFrame.endDayLabel -row 4 -column 1
    grid $baseWidget.timeFrame.endMonthLabel -row 4 -column 2
    grid $baseWidget.timeFrame.endYearLabel -row 4 -column 3
    grid $baseWidget.timeFrame.endHourLabel -row 4 -column 4
    grid $baseWidget.timeFrame.endTimeColonLabel -row 4 -column 5
    grid $baseWidget.timeFrame.endMinuteLabel -row 4 -column 6

    button $baseWidget.timeFrame.endDayMinusBtn -text "-" -command {modifyEventTime end -d} -repeatdelay 500 -repeatinterval 150
    button $baseWidget.timeFrame.endMonthMinusBtn -text "-" -command {modifyEventTime end -mo} -repeatdelay 500 -repeatinterval 150
    button $baseWidget.timeFrame.endYearMinusBtn -text "-" -command {modifyEventTime end -y} -repeatdelay 500 -repeatinterval 150
    button $baseWidget.timeFrame.endHourMinusBtn -text "-" -command {modifyEventTime end -h} -repeatdelay 500 -repeatinterval 150
    button $baseWidget.timeFrame.endMinuteMinusBtn -text "-" -command {modifyEventTime end -mi} -repeatdelay 500 -repeatinterval 150

    grid $baseWidget.timeFrame.endDayMinusBtn -row 5 -column 1
    grid $baseWidget.timeFrame.endMonthMinusBtn -row 5 -column 2
    grid $baseWidget.timeFrame.endYearMinusBtn -row 5 -column 3
    grid $baseWidget.timeFrame.endHourMinusBtn -row 5 -column 4
    grid $baseWidget.timeFrame.endMinuteMinusBtn -row 5 -column 6

    frame $baseWidget.titleFrame
    pack $baseWidget.titleFrame -fill x -expand yes

    label $baseWidget.titleFrame.titleLabel -text {Event title: }
    entry $baseWidget.titleFrame.titleEntry
    pack $baseWidget.titleFrame.titleEntry -side right -expand y -fill x
    pack $baseWidget.titleFrame.titleLabel -side right

    frame $baseWidget.descFrame
    pack $baseWidget.descFrame -fill both -expand yes
    label $baseWidget.descFrame.descLabel -text {Event description: }
    text $baseWidget.descFrame.descText
    pack $baseWidget.descFrame.descText -side right -fill both -expand yes
    pack $baseWidget.descFrame.descLabel -side right -anchor n 

    frame $baseWidget.postOptionsFrame
    pack $baseWidget.postOptionsFrame -fill x -expand yes
    label $baseWidget.postOptionsFrame.publicityLabel -text "Intended audience of the event: "
    radiobutton $baseWidget.postOptionsFrame.publicPost -value public -text "Public to all users having same event collection" -variable ::postPublicity 
    radiobutton $baseWidget.postOptionsFrame.accordingToProfile -value byprofile -text "Follows your profile privacy setting" -variable ::postPublicity 
    radiobutton $baseWidget.postOptionsFrame.privatePost -value private -text "Private to local calendar only" -variable ::postPublicity 
    pack $baseWidget.postOptionsFrame.publicityLabel -anchor w
    pack $baseWidget.postOptionsFrame.publicPost -anchor w
    pack $baseWidget.postOptionsFrame.accordingToProfile -anchor w
    pack $baseWidget.postOptionsFrame.privatePost -anchor w

    frame $baseWidget.buttonFrame
    pack $baseWidget.buttonFrame -fill x -expand yes
    button $baseWidget.buttonFrame.okButton -text {Post event} -command postNewEventCmd
    button $baseWidget.buttonFrame.cancelButton -text {Cancel} -command cancelNewEventCmd
    pack $baseWidget.buttonFrame.cancelButton  -side right 
    pack $baseWidget.buttonFrame.okButton  -side right 
    label $baseWidget.buttonFrame.errorLabel
    pack $baseWidget.buttonFrame.errorLabel -side right 
}



#
# called when user changes size of tk window ; we must scale some of the
# widgets too and most notably re-draw the actual events-view
#
proc daycanvasResizeEvent { newWidth newHeight } {
    puts [ format "resize canvas %d %d" $newWidth $newHeight ]
    # update scrollbar:
    set nowat [.rightframe.dayframe.daycanvas yview]
    eval ".rightframe.dayframe.hscroll set $nowat"
    .rightframe.dayframe.daycanvas configure -scrollregion " 0 0 $newWidth 1000 "
    # if init is already done:
    if { $::initReady != false } {
	# dont't draw straight away, because usually we get multiple
	# events in succession and drawing is heavy operation: instead
	# set up 200ms timer and if it expires (meaning no resize event
	# seen in 200ms) then do the redraw in there, in procedure
	# redrawAfterResizeEvent
	if { $::resizeEventTimer != -1 } {
	    after cancel $::resizeEventTimer
	}
	set ::resizeEventTimer [ after 200 redrawAfterResizeEvent ]
    }
}
#
# procedure called in async via $::resizeEventTimer
#
proc redrawAfterResizeEvent { } {
    displayDay [ clock format $::dayOnDisplay -format "%d" ]
    set ::resizeEventTimer -1 
}


proc scrollDayview {args} {
    eval ".rightframe.dayframe.daycanvas yview $args"
    set nowat [.rightframe.dayframe.daycanvas yview]
    eval ".rightframe.dayframe.hscroll set $nowat"
}

#
# procedure that updates buttons in selection calendar.
# in practice it shows selected button as "lowered" and
# the rest as "raised"
#
# selectedDay is date whose button is pressed
#
proc updateSelectionButtonsState { selectedDay } {
    set currentDayNum [ clock format $selectedDay -format "%d" ]
    foreach btn [ winfo children .s.selectionFrame ] {
        if { [ string first .s.selectionFrame.button $btn ] != -1 } {
            # yes is button a button, check if it is button for to-day:
            if { [ lindex [ $btn configure -text ] 4 ] == $currentDayNum } {
                # yes, is for to-day
                $btn configure -relief sunken -bg green
            } else {
                $btn configure -relief raised -bg lightgray
            }
        }
    }
}


proc displaySelectionCalendar {starttime} {
    frame .s.selectionFrame
    set month [ clock format $starttime -format {%N} ]
    set day [ string trim [ clock format $starttime -format {%e} ] ]
    set monthFirstDay [ expr $starttime - [ expr ( $day -1 ) * 24 * 60 * 60 ] ]
    set row 0
    set weekCounter 0 
    button .s.selectionFrame.minusYear -text {<<} -command {updateSelection [ clock add $::monthOnDisplay -1 year ]}
    button .s.selectionFrame.plusYear -text {>>} -command {updateSelection [ clock add $::monthOnDisplay 1 year ]}
    button .s.selectionFrame.minusMonth -text {<} -command {updateSelection [ clock add $monthOnDisplay -1 month ]}
    button .s.selectionFrame.plusMonth -text {>} -command {updateSelection [ clock add $monthOnDisplay 1 month ]}
    grid .s.selectionFrame.minusMonth -row 0 -column 1 
    grid .s.selectionFrame.minusYear -row 0 -column 0 
    grid .s.selectionFrame.plusYear -row 0 -column 5 
    grid .s.selectionFrame.plusMonth -row 0 -column 4
    label .s.selectionFrame.yearNumber -text [ clock format $starttime -format {%Y} ]
    grid .s.selectionFrame.yearNumber -row 0 -column 3
    label .s.selectionFrame.monthName -text  [ clock format $starttime -format {%b} -locale system]
    grid .s.selectionFrame.monthName -row 0 -column 2
    incr row
    for {set currentDay $monthFirstDay} {$weekCounter < 7} {set currentDay [ expr $currentDay + ( 24 * 60 * 60 ) ]} {
        incr weekCounter
        set labelDayOfWeek [ expr [ clock format $currentDay -format {%u} ] - 1 ]
        label .s.selectionFrame.l$labelDayOfWeek -text [ clock format $currentDay -format {%a} -locale system]
        grid .s.selectionFrame.l$labelDayOfWeek -row $row -column $labelDayOfWeek
    }
    incr row
    for {set currentDay $monthFirstDay} {[clock format $currentDay -format {%N} ] == $month } {set currentDay [ expr $currentDay + ( 24 * 60 * 60 ) ]} {
        set buttonDayNumber [ string trim [ clock format $currentDay -format {%e} ] ]
        button .s.selectionFrame.button$buttonDayNumber -text [ format {%02d} $buttonDayNumber ] -command "displayDay $buttonDayNumber"
        set buttonDayOfWeek [ expr [ clock format $currentDay -format {%u} ] - 1 ]
        grid .s.selectionFrame.button$buttonDayNumber -row $row -column $buttonDayOfWeek
        if { $buttonDayOfWeek == 6 } {
            incr row
        }
    }
    pack .s.selectionFrame
}

#
# This procedure is called when user selects another month/year from
# selection calendar. This causes re-draw of the calendar under
# month/year buttons
#
proc updateSelection { newtime } {
    destroy .s.selectionFrame
    displaySelectionCalendar $newtime
    set ::monthOnDisplay $newtime
    # fetch events of newly-selected month
    eventsByTimeAndCollection $::monthOnDisplay
    # and update calendar view:
    displayDay [ clock format $newtime -format {%d} ]
}
#
# procedure that is called when user selects a day from selection
# calendar ; will force selected day on display and update its 
# events
#
proc displayDay { dayNumber } {
    puts [ format "display day %s" $dayNumber ]
    # figure out start and end time of the day
    set month [ clock format $::monthOnDisplay -format {%N} ]
    set year [ clock format $::monthOnDisplay -format {%Y} ]
    # remove possible leading zeroes:
    regsub {^[0]} $month {\1} month
    regsub {^[0]} $dayNumber {\1} dayNumber
    set start [ clock scan [ format "%04d %02d %02d 00 00 00" $year $month $dayNumber ] -format {%Y %m %d %H %M %S} -gmt false ]
    set end [clock scan [ format "%04d %02d %02d 23 59 59" $year $month $dayNumber ] -format {%Y %m %d %H %M %S} -gmt false ]
    puts [ clock format $start ]
    puts [ clock format $end ]
    updateSingleDayCanvas .rightframe.dayframe.daycanvas $start $end
    updateSelectionButtonsState $start
    set ::dayOnDisplay $start
}

#
# procedure for drawing single event into canvas, this is called from 
# proc packevents below when it is known which event collide with
# each others
#
# c is canvas
# ev is the event
# startOfDay is [ clock seconds ] from midnight when day started
#
proc drawSingleEvent { c ev startOfDay } {
    set secondsFromStartToPrevMidnight [ expr [ dict get $ev startTime ]-$startOfDay ]
    set startPos [ expr 25.0 + ( ( $secondsFromStartToPrevMidnight / ( 60.0 * 60.0 ) ) * $::heightForSingleHour ) ]
    set secondsFromEndToPrevMidnight [ expr [ dict get $ev endTime ]-$startOfDay ]
    set endPos [ expr 25.0 + ( ( $secondsFromEndToPrevMidnight / ( 60.0 * 60.0 ) ) * $::heightForSingleHour ) ]
    set scSizeOption [ $c configure -scrollregion ]
    set regSize [ lindex $scSizeOption 4 ]
    set width [ expr [ lindex $regSize 2 ] - $::leftMarginInPixels ]
    if { $width > 6 } {
        set left [ expr {$::leftMarginInPixels + int ( $width * [ dict get $ev left ] )} ]
        set right [ expr {$::leftMarginInPixels + int ( $width * [ dict get $ev right ] )} ]
        set eventRectangle [ $c create rectangle $left $startPos $right $endPos -fill green ]
        
        set eventTitleText [ $c create text [ expr $left + 5 ] [ expr $startPos + 5 ] -width [ expr ( $right - $left ) -5 ] -fill white -text [ dict get $ev title ] -anchor nw ]

        $c bind $eventRectangle <ButtonRelease-1> "displaySingleEvent [ dict get $ev identifier ]"
        $c bind $eventTitleText <ButtonRelease-1> "displaySingleEvent [ dict get $ev identifier ]"
    }
}
# procedure for drawing times+events into single canvas that
# re-presents single day
#
proc updateSingleDayCanvas {c start end} {
    #figure out canvas scroll-region size
    set scSizeOption [ $c configure -scrollregion ]
    set regSize [ lindex $scSizeOption 4 ]
    set width [ lindex $regSize 2 ] 
    set height [ lindex $regSize 3 ]
    puts [ format "scroll area w %d h %d" $width $height ]
    # this same variable is used also event drawing procedure
    # assumption is that all day-specific canvases have same
    # $height
    set ::heightForSingleHour [ expr ( $height - 30.0 ) / 24.0 ]
    # clear canvas
    $c delete "all"
    # print day number on top
    $c create text [ expr $width / 2 ] 15 -text [ clock format $start -format "%A %d" -locale system]
    # figure out dimensions
    set hourCounter 0
    while { $hourCounter < 24 } {
        set month [ clock format $start -format {%N} ]
        set year [ clock format $start -format {%Y} ]
        set dayNumber [ clock format $start -format {%d} ]
        set currentHour [clock scan [ format "%s %s %s %s 00 00" $year $month $dayNumber $hourCounter ] -format {%Y %m %d %H %M %S} -gmt false ]
        set textPos [ expr 25 + ( $hourCounter * $::heightForSingleHour ) ]
        $c create text 35 $textPos -text [ clock format $currentHour -format "%X" -locale system]
        $c create line $::leftMarginInPixels $textPos $width $textPos
        incr hourCounter
    }
    # http://stackoverflow.com/questions/11311410/visualization-of-calendar-events-algorithm-to-layout-events-with-maximum-width
    set eventsOfDay [ lsort -increasing -command startComparator [ eventsBetween $start $end ] ]
    set columns [ list ]
    set lastEndTime -1
    foreach ev $eventsOfDay {
        if { [ dict get $ev startTime ] >= $lastEndTime &&
             $lastEndTime != -1 } {
            packevents $c $columns $start
            set columns [ list ]
            set lastEndTime -1
        }
        set placed false
        set colCounter 0 
        for { set colCount [ llength $columns ] } { $colCounter < $colCount } { incr colCounter } {
            set col [ lindex $columns $colCounter ]
            set lastItem [ lindex $col [ expr [ llength $col ] - 1 ] ]
            if { [ overlaps $lastItem $ev ] == false } {
                lappend col $ev
                set placed true
                # lets put col back to columns
                set columns [ lreplace $columns $colCounter $colCounter $col ]
                break
            }
        }
        if { $placed == false } {
            set col [ list ]
            lappend col $ev
            lappend columns $col
        }
        if { $lastEndTime == -1 || [ dict get $ev endTime ] > $lastEndTime } {
            set lastEndTime [ dict get $ev endTime ]
        }
    }
    if { [ llength $columns ] > 0 } {
        packevents $c $columns $start
    }
}
proc packevents { c columns startOfDay } {
    set numcolumns [ llength $columns ]
    set numcolumns [ format "%0.2f" $numcolumns ]
    set iCol 0
    foreach col $columns {
        foreach ev $col {
            set colSpan [ expandEvent $ev $iCol $columns ]
            dict set ev left [ expr  $iCol  / $numcolumns ]
            dict set ev right [ expr (  $iCol + $colSpan ) / $numcolumns ]
            # position of event is now known: draw
            drawSingleEvent $c $ev $startOfDay
        }
        incr iCol
    }
}
#
# procedure that checks if it is ok to extend event over multiple columns
#
proc expandEvent { ev iCol columns } {
    set colSpan 1
    for { set i [ expr $iCol + 1 ] } { $i < [ llength $columns ] } { incr i } {
        set column [ lindex $columns $i ]
        foreach listEvent $column {
            if { [ overlaps $listEvent $ev ] == true } {
                return $colSpan
            } 
        }
        incr colSpan
    }
    return $colSpan
}

#
# procedure returns true if event a overlaps with b
#
proc overlaps { a b } {
    set aStart [ dict get $a startTime ]
    set aEnd [ dict get $a endTime ]
    set bStart [ dict get $b startTime ]
    set bEnd [ dict get $b endTime ]
    if { ($aStart == $bStart ) ||
         ( $aEnd > $bStart && $aEnd <= $bEnd ) ||
         ( $aStart > $bStart && $aStart < $bEnd ) ||
         ( $bStart > $aStart && $bStart < $aEnd ) ||
         ( $aStart > $bStart && $aStart < $bEnd ) } {
        return true
    } else {
        return false
    }
}

#
# procedure called when user presses button saving a new event
#
proc postNewEventCmd {} {
    set duration [ expr {$::newEventEnd-$::newEventStart} ]
    if { $duration < 1 } {
        .rightframe.newEventFrame.buttonFrame.errorLabel configure -text {Event end date too early}
    } elseif { $duration > [ expr 60 * 60 * 24 * 14 ] } {
        .rightframe.newEventFrame.buttonFrame.errorLabel configure -text {Event max duration is 14 days}
    } elseif { [ string length [ string trim [ .rightframe.newEventFrame.titleFrame.titleEntry get ] ] ] < 1 } {
        .rightframe.newEventFrame.buttonFrame.errorLabel configure -text {Title is mandatory}
    } else {
        .rightframe.newEventFrame.buttonFrame.errorLabel configure -text {}
        # so, construct the new event
        dict set ev startTime $::newEventStart
        dict set ev endTime $::newEventEnd
        dict set ev title [ .rightframe.newEventFrame.titleFrame.titleEntry get ]
	dict set ev senderId $::profileInUse
        if { [ string length [ string trim [ .rightframe.newEventFrame.descFrame.descText get 1.0 ] ] ]  > 0 } {
            dict set ev description [ string trim [ .rightframe.newEventFrame.descFrame.descText get 1.0 end ] ]
        }
        # invent identifier for the event
        dict set ev identifier [ calculateSHA1 [ format {%s-%s-%s-%s} $::newEventStart $::newEventEnd $::profileInUse [ clock seconds ] ] ]
        # include variable telling if event is supposed to be local, private or public
        if { $::postPublicity == {local} } {
            dict set ev publicity $::postPublicity
        } else {
            if { [ isProfilePrivate ] } {
                dict set ev publicity {public}
            } else {
                dict set ev publicity $::postPublicity
            }
        }
        # timestamp of creation for the event
        dict set ev createdOn [ clock seconds ]
        # set collection name into event. in our local storage we may
        # have events belonging to multiple collections but at
        # publish we may publish only events that belong to selected
        # collection
        dict set ev collection $::collectionInUse
        # and store event into datamodel
        storeNewEvent $ev
        # and clear fields using "cancel" methods
        cancelNewEventCmd 
        # and update the calendar view:
        displayDay [ clock format $::dayOnDisplay -format "%d" ]
    }
}
#
# cancel creation of new event by clearing input fields and navigating
# to calendar view
#
proc cancelNewEventCmd {} {
    .rightframe.newEventFrame.timeFrame.startDayLabel configure -text {}
    .rightframe.newEventFrame.titleFrame.titleEntry delete 0 end
    .rightframe.newEventFrame.descFrame.descText delete 1.0 end
    showDayView
}
#
# Procedure called from button, changes event database (collection) name. 
# All events in memory need to be discarded, and re-fetched from the
# newly-selected collection.
#
proc changeCollectionCallback {} {
    # set collection into settings:
    set collection [ .rightframe.settingsframe.collectionEntry get ]
    if { [ string length $collection ] > 0 } {
        set ::collectionInUse $collection
        dict set ::settings collectionInUse $collection
        # save settings into permanent storage:
        persistLocalData
        # remove all events
        set ::allEvents [ list ]
        # set all records as un-seen
        set ::allDbRecords [ dict create ]
        # re-load local events
        loadLocalDataFromStorage
        # load events of other operators from shared database:
        eventsByTimeAndCollection $::monthOnDisplay
        # and display currently selected day
        displayDay [ clock format $::monthOnDisplay -format "%d" ]
    }
}
#
# Callback procedure for adding a comment to an event
#
proc addCommentCallback { eventIdentifier } {
    puts [ format "addCommentCallback %s" $eventIdentifier ]
    # first see if we have any text..
    set commentText [ string trim [ .rightframe.dayframe.daycanvas.singleEventFrame.commentsFrame.newCommentEntry get ] ]
    if { [ string length $commentText ] < 1 } {
        return
    }
    # we had some text, so lets construct the comment.
    dict set newComment commentText $commentText
    dict set newComment commentTime [ clock seconds ]
    dict set newComment commentedEvent $eventIdentifier
    dict set newComment identifier [ calculateSHA1 [ format {%s-%s-%s} $eventIdentifier $::profileInUse [ clock seconds ] ] ]
    # fetch the event
    set ev [ eventById $eventIdentifier ]
    # comment publicity follows publicity of the event
    dict set newComment publicity [ dict get $ev publicity ]
    dict set newComment collection [ dict get $ev collection ]
    # in profile comment also set start time of the event commented,
    # it will help in deciding which is the right db record 
    dict set newComment eventStartTime [ dict get $ev startTime ]
    dict set newComment senderId $::profileInUse
    # getProfile will fail if operators profile is not published:
    if { [ catch {
	set p [ getProfile $::profileInUse ]
	dict set newComment senderName [ dict get $p displayName ]
    } fid ] } {
	dict set newComment senderName $::profileInUse
    }
    # see if this event already had local comments:
    if { [ dict exists $::allLocalComments $eventIdentifier ] } {
        # yes, this event has already been commented
        set eventCommentList [ dict get $::allLocalComments $eventIdentifier ]
        lappend eventCommentList $newComment
        # and put the list back into dictionary
        dict set ::allLocalComments $eventIdentifier $eventCommentList
        puts [ format "comment appended to existing event comment list of %s" $eventIdentifier ]
    } else {
        # No, event did not have any local comments. Create the first one
        set eventCommentList [ list ]
        lappend eventCommentList $newComment
        dict set ::allLocalComments $eventIdentifier $eventCommentList
        puts [ format "comment appended to new comment list of %s" $eventIdentifier ]
    }
    # see if this event already had any comments from any operator
    if { [ dict exists $::allComments $eventIdentifier ] } {
        # yes, this event has already been commented
        set eventCommentList [ dict get $::allComments $eventIdentifier ]
        lappend eventCommentList $newComment
        dict set ::allComments $eventIdentifier $eventCommentList
    } else {
        # this is first comment for given event
        set eventCommentList [ list ]
        lappend eventCommentList $newComment
        dict set ::allComments $eventIdentifier $eventCommentList
    }
    # then persist locally
    persistLocalData
    # and conditionally publish
    if { [ dict get $newComment publicity ] != {private} } {
        publishEvents $ev
    }
    updateEventCommentsDisplay $eventIdentifier 
}
#
# Procedure for updating comments of event on display. This updates
# the comments-section of event display.
#
proc updateEventCommentsDisplay { eventIdentifier } {
    if { [ dict exists $::allComments $eventIdentifier ] } {
        # yes, this event has already been commented
        .rightframe.dayframe.daycanvas.singleEventFrame.commentsDisplayFrame.oldCommentText configure -state normal
        # wipe out old content:
        .rightframe.dayframe.daycanvas.singleEventFrame.commentsDisplayFrame.oldCommentText delete 1.0 end
        set eventCommentList [ dict get $::allComments $eventIdentifier ]
        puts [ format "updateEventCommentsDisplay: %d comments for %s" [ llength $eventCommentList ] $eventIdentifier ]
        foreach c $eventCommentList {
            .rightframe.dayframe.daycanvas.singleEventFrame.commentsDisplayFrame.oldCommentText insert 1.0 [ format "%s (%s) %s:\n%s\n" [ dict get $c senderName ] [ dict get $c senderId ] [ clock format [ dict get $c commentTime ] -locale system ] [ dict get $c commentText ] ]
        }
        .rightframe.dayframe.daycanvas.singleEventFrame.commentsDisplayFrame.oldCommentText configure -state disabled
    } else {
        puts [ format "updateEventCommentsDisplay: no comments for %s" $eventIdentifier ]
    }
}

#
# data-model part.
# 
# data-model here is more or less the calendar-events. events may be
# created and deleted. there might be a "comment" on event, maybe something
# like "Yes, I will attend" 
#
# Event is a dictionary with values
# startTime : number, something that [ clock seconds ] returns. mandatory
# endTime : number, mandatory
# title : title for the event, mandatory. 
# description : longer description for the event, optional
# invitees : list of classified-ads operator profile addresses, SHA1-format, 
#            optional
#

#
# Procedure that returns events in currently open collection between start- 
# and end-date. Events are returned as a list of dictionaries. Resultset
# includes events in selected collection and events posted by operator. 
# From usage point of view that makes sense ; user always sees her own events
# and then additionally events of other operators that are in the 
# selected collection
#
proc eventsBetween { start end } {

    set retval [ list ]

    dict set dummyComparatorEvent startTime $start
    dict set dummyComparatorEvent endTime $end
    
    set eventsInStorage [ llength $::allEvents ]
    set i 0 
    while { $i < $eventsInStorage } {
        set evInStorage [ lindex $::allEvents $i ]
        if { [ overlaps $evInStorage $dummyComparatorEvent ] == true } { 
            # also, for display purposes select events that either belong
            # to selected collection or are posted by current operator
            if { ( [ dict get $evInStorage collection ] == $::collectionInUse ) ||
                 ( [ dict exists $evInStorage senderId ] &&
                   [ dict get $evInStorage senderId ] == $::profileInUse ) } {
                lappend retval $evInStorage
            }
        }
        incr i
    }
    return $retval
}
#
# Procedure that is called when user clicks on event on display.
# This displays details of the event for user to see.
#
proc displaySingleEvent { eventIdentifier } {
    puts [ format "displaySingleEvent %s" $eventIdentifier ]
    # get rid of possible previous display:
    catch closeSingleEventDisplay
    # which canvas to use
    set c .rightframe.dayframe.daycanvas
    # calculate size of canvas to create:

    # figure out canvas scroll-region size, first ask size of the
    # canvas, including possible unvisible parts
    set scSizeOption [ $c configure -scrollregion ]
    set regSize [ lindex $scSizeOption 4 ]
    set width [ lindex $regSize 2 ] 
    set height [ lindex $regSize 3 ]
    puts [ format "scroll area w %d h %d" $width $height ]
    # then figure out the visible rectangle
    set visibleYStart [ expr int ( $height * [ lindex [ $c yview ] 0 ] ) ]
    set visibleYEnd [ expr int ( $height * [ lindex [ $c yview ] 1 ] ) ]
    set visibleY [ expr ( $visibleYEnd - $visibleYStart ) - 50 ]
    set width [ expr $width - 50 ]
    puts [ format "frame size %d %d" $width $visibleY ]
    # first create frame to host contents of event display
    frame $c.singleEventFrame -borderwidth 2 -relief raised -width $width -height $visibleY

    # fetch the event
    set ev [ eventById $eventIdentifier ]
    if { $ev != {null} } {
        # then populate things into frame:
        label $c.singleEventFrame.titleLabel -text [ dict get $ev title ]
        # see if we know the event sender profile:
        set p [ getProfileAndEmptyIfNotFound [ dict get $ev senderId ] ]
        if { [ dict size $p ] == 0 } {
            # profile not found:
            label $c.singleEventFrame.senderLabel -text [ format "Posted by %s" [ dict get $ev senderId ] ]
        } else {
            label $c.singleEventFrame.senderLabel -text [ format "Posted by %s (%s)" [ dict get $p displayName ] [ dict get $ev senderId ] ]
        }
        label $c.singleEventFrame.startTime -text [ format "Start time is %s" [ clock format [ dict get $ev startTime ] -locale system ] ]
        label $c.singleEventFrame.endTime -text [ format "End time is %s" [ clock format [ dict get $ev endTime ] -locale system ] ]
        if { [ dict exists $ev description ] == false } {
            label $c.singleEventFrame.description -text { }
        } else {
            label $c.singleEventFrame.description -text [dict get $ev description]
        }
        frame $c.singleEventFrame.buttonFrame
        button $c.singleEventFrame.buttonFrame.closeButton -text {Close} -command closeSingleEventDisplay
        if { [ dict get $ev senderId ] == $::profileInUse } {
            button $c.singleEventFrame.buttonFrame.deleteButton -text {Delete event} -command "deleteEvent $eventIdentifier"
        }
        pack $c.singleEventFrame.titleLabel -anchor nw
        pack $c.singleEventFrame.senderLabel -anchor nw
        pack $c.singleEventFrame.startTime -anchor nw
        pack $c.singleEventFrame.endTime -anchor nw
        pack $c.singleEventFrame.description -anchor nw -ipadx 15 -ipady 15
        pack $c.singleEventFrame.buttonFrame.closeButton -side left
        if { [ dict get $ev senderId ] == $::profileInUse } {
            pack $c.singleEventFrame.buttonFrame.deleteButton -side left
        }
        pack $c.singleEventFrame.buttonFrame

        # comments-section of the event:
        frame $c.singleEventFrame.commentsFrame
        label $c.singleEventFrame.commentsFrame.newCommentLabel -text {New comment: }
        entry $c.singleEventFrame.commentsFrame.newCommentEntry
        button $c.singleEventFrame.commentsFrame.newCommentBtn -text Add -command "addCommentCallback $eventIdentifier"
        pack $c.singleEventFrame.commentsFrame.newCommentBtn -side right -anchor sw
        pack $c.singleEventFrame.commentsFrame.newCommentEntry -side right -anchor sw -fill x -expand yes
        pack $c.singleEventFrame.commentsFrame.newCommentLabel -side right -anchor sw
        pack $c.singleEventFrame.commentsFrame -fill x -expand yes
        # next is one single entry for all the comments posted by all
        # users:
        frame $c.singleEventFrame.commentsDisplayFrame
        label $c.singleEventFrame.commentsDisplayFrame.oldCommentLabel -text {Comments: }
        text $c.singleEventFrame.commentsDisplayFrame.oldCommentText -state disabled
        pack $c.singleEventFrame.commentsDisplayFrame.oldCommentText -side right -anchor sw -fill both -expand yes
        pack $c.singleEventFrame.commentsDisplayFrame.oldCommentLabel -side top -anchor sw
        pack $c.singleEventFrame.commentsDisplayFrame -fill both -expand yes

        # populate possible comments regarding the event:
        updateEventCommentsDisplay $eventIdentifier 

        # put the frame into window that is in the canvas:
        $c create window 25 [ expr $visibleYStart + 25 ] -window $c.singleEventFrame -width $width -height $visibleY -anchor nw -tags singleEventWindow
    } else {
        catch closeSingleEventDisplay
    }
}
#
# Procedure for closing single event display. UI-button callback. 
#
proc closeSingleEventDisplay { } {
    set c .rightframe.dayframe.daycanvas
    destroy $c.singleEventFrame
    $c delete singleEventWindow
}
#
# Procedure for deleting a previously posted event. This is UI-button
# callback. 
#
proc deleteEvent { eventIdentifier } {
    puts [ format "deleteEvent %s" $eventIdentifier ]
    # first delete from storage, will cause publish also if public event
    deleteEventFromStorage [ eventById $eventIdentifier ]
    # close the dialog showing the event about to be deleted
    closeSingleEventDisplay
    # and update the event display, now with one event missing
    displayDay [ clock format $::dayOnDisplay -format "%d" ]
}
#
# Procedure for comparing durations of 2 events
#
# a and b are events from procedure eventsBetween and
# this returns 1 if a has longer duration than b, 0
# if durations are equal.
#
proc durationComparator { a b } {
    set aStart [ dict get $a startTime ]
    set aEnd [ dict get $a endTime ]
    set bStart [ dict get $b startTime ]
    set bEnd [ dict get $b endTime ]
    set aDuration [ expr { $aEnd - $aStart } ]
    set bDuration [ expr { $bEnd - $bStart } ]
    if { $aDuration == $bDuration } {
        return 0 
    } 
    if { $aDuration > $bDuration } {
        return 1
    }
    return -1
}
#
# Procedure for comparing start-times of 2 events
#
# Used for sorting by start-time order. If 2 events
# have same start time, then by end-time
#
proc startComparator { a b } {
    set aStart [ dict get $a startTime ]
    set aEnd [ dict get $a endTime ]
    set bStart [ dict get $b startTime ]
    set bEnd [ dict get $b endTime ]
    if { $aStart == $bStart && $bEnd == $aEnd } {
        return 0 
    } 
    if { $aStart > $bStart } {
        return 1
    }
    if { $aStart < $bStart } {
        return -1
    }
    # start-times did not differ, next is to compare end times
    if { $aEnd > $bEnd } {
        return 1
    }
    if { $aEnd < $bEnd } {
        return -1
    }
    # following line should not be reached
    return 0
}
#
# Procedure for storing a single event
# 
proc storeNewEvent { ev } {
    # check if that particular event was already in storage
    set found false
    set eventsInStorage [ llength $::allEvents ]
    set i 0 
    while { $i < $eventsInStorage } {
        set evInStorage [ lindex $::allEvents $i ]
        if { [ dict get $ev identifier ] == [ dict get $evInStorage identifier ] } {
            set found true
            set ::allEvents [ lreplace $::allEvents $i $i $ev ]
            break
        }
        incr i
    }
    if { $found == false } {
        lappend ::allEvents $ev
    }
    puts [ format "after storeNewEvent num of events = %d" [ llength $::allEvents ] ]
    storeEventsLocally
    if { $::postPublicity == "byprofile" || $::postPublicity == "public" } {
        publishEvents $ev
    }
}
#
# Procedure for deleteting a single event
# 
proc deleteEventFromStorage { ev } {
    # check if that particular event was already in storage
    set found false
    set eventsInStorage [ llength $::allEvents ]
    set i 0 
    while { $i < $eventsInStorage } {
        set evInStorage [ lindex $::allEvents $i ]
        if { [ dict get $ev identifier ] == [ dict get $evInStorage identifier ] } {
            set found true
            # replace with empty -> is removal
            set ::allEvents [ lreplace $::allEvents $i $i ]
            break
        }
        incr i
    }
    puts [ format "after deleteEvent num of events = %d" [ llength $::allEvents ] ]
    if { $found == true } {
        storeEventsLocally
        if { $::postPublicity == "byprofile" || $::postPublicity == "public" } {
            publishEvents $ev
        }
    }
}
#
# Method for locally storing own events in program-specific database.
# Now, a caveat. TCL programs are stored only once per classified-ads
# installation. Locally stored data is a single file per program.
# If user has multiple profiles in use and uses calendar app on many
# of those, we must be careful to not accidentally delete events
# posted by other profiles the operator may be operating. 
# 
proc storeEventsLocally {} {
    # set list empty:
    set ::allLocalEvents [ list ]
    # pick own events 
    foreach event $::allEvents {
	if { [ dict get $event senderId ] == $::profileInUse } {
	    lappend ::allLocalEvents $event
	}
    }
    persistLocalData
}
#
# Method for persisting locally stored data (events and settings, in practice)
#
# Structure of the data storage is this:
# at top level there is dictionary. Keys into dictionary are of form
#  <profileId>-settings
#  <profileId>-events
# because there may be several operator profiles and idea is to keep
# settings and events of each operator intact
#
proc persistLocalData {} {
    # store local data in dictionary. one item is settings. another is event data.
    # yet one more are event comments
    set blob [ retrieveLocalData ]
    if { [ string length $blob ] == 0 } {
        set blob [ dict create ]
    } 
    dict set blob [ format "%s-settings" $::profileInUse ] $::settings
    dict set blob [ format "%s-events" $::profileInUse ] $::allLocalEvents
    dict set blob [ format "%s-comments" $::profileInUse ] $::allLocalComments
    # this extension method writes data to local database, specific to this particular program:
    storeLocalData $blob
}
#
# Method for loading locally stored data upon startup. See format description
# at procedure "persistLocalData" that is counterpart to this.
#
proc loadLocalDataFromStorage {} {
    set blob [ retrieveLocalData ]
    if { [ string length $blob ] > 0 } {
        if { [ dict exists $blob [ format "%s-settings" $::profileInUse ] ] } {
            set ::settings [ dict get $blob [ format "%s-settings" $::profileInUse ] ]
        }
        if { [ dict exists $blob [ format "%s-events" $::profileInUse ] ] } {
            set ::allLocalEvents [ dict get $blob [ format "%s-events" $::profileInUse ] ]
        }
        if { [ dict exists $blob [ format "%s-comments" $::profileInUse ] ] } {
            set ::allLocalComments [ dict get $blob [ format "%s-comments" $::profileInUse ] ]
        }
	# because this procedure is called only at start-up there is no need
	# merge events. the locally stored events are at this stage
	# all that we've got
	set ::allEvents $::allLocalEvents
        set ::allComments $::allLocalComments
    }
}
#
# Procedure that applies settings that must have been previously
# loaded using procedure loadLocalDataFromStorage - settings are
# stored in global dictionary ::settings
proc loadSettings {} {
    if { [ dict exists $::settings collectionInUse ] } {
        set ::collectionInUse [ dict get $::settings collectionInUse ]
        .rightframe.settingsframe.collectionEntry delete 0 end
        .rightframe.settingsframe.collectionEntry insert 0 $::collectionInUse
    }
}
#
# method that returns true if selected profile is public profile
#
proc isProfilePrivate {} {
    # getProfile will fail if operators profile is not published:
    if { [ catch {    
	set p [ getProfile $::profileInUse ]
	# profile is a dictionary
	if { [ dict get $p isPrivate ] > 0 } {
	    return true
	} else {
	    return false
	}
    } fid ] } {
	# if failed during query, assume it is unpublished and
	# assume that user wants to keep the profile secret. 
	return true
    }
}
#
# Publish procedures - publishes all public events in same month
# with given event. It could be possible to post every single
# event as db-record of its own but that would lead to huge
# number of records - lets instead list all events belonging to 
# same month into single database record and publish that. That
# will lead each user having about 12 db records per year. This
# method also makes it easier to detect situation where event
# is deleted by user.
#
# If user has public profile, all published events will be
# published with "public" setting because that is users
# selected setting. Otherwise, if user has "private" profile
# setting, and event is set to be sent with "byprofile" 
# setting, then event will be encrypted only to readers of 
# the current profile. 
#
# Additionally this procedure takes care of including 
# event comments into published record. 
#
proc publishEvents { newOrDeletedEvent } {
    # check out publicity setting of the new event, it determines
    # the database record where it will be published. 
    set publicity [ dict get $newOrDeletedEvent publicity ]

    # for db record pick up every public event by me
    # whose start time is in the same month. 
    set startTime [ dict get $newOrDeletedEvent startTime ]
    set monthStart [ startOfMonth $startTime ]
    set monthEnd [ endOfMonth $startTime ]
    # pad start+end with one day to get around events not
    # beloning to same month in all timezones:
    set monthStart [ clock add $monthStart -1 day ]
    set monthEnd [ clock add $monthEnd 1 day ]
    # fetch all events of month, will include events from any operator:
    set allEventsOfMonth [ eventsBetween $monthStart $monthEnd ]
    
    set myEventsToBePublished [ list ]
    set myCommentsToBePublished [ list ]
    # pick own events that go selected collection
    foreach event $allEventsOfMonth {
        if { [ dict get $event publicity ] != $publicity } {
            # not our kind of event, don't publish
            continue
        }
        if { [ dict get $event collection ] != $::collectionInUse } {
            # event belongs to other collection than the one we're
            # publishing now
            continue
        }
        # at this stage we've noticed that we're dealing with
        # event that has suitable publicity setting and belongs
        # to right collection. lets see if there is comments
        # regarding that event that should go to same db record:
        if { [ dict exists $::allLocalComments [ dict get $event identifier ] ] } {
            # yes, this event has already been commented
            set eventCommentList [ dict get $::allLocalComments [ dict get $event identifier ] ]
            puts [ format "For publish event %s has %d comments" [ dict get $event identifier ] [ llength $eventCommentList ] ]
            foreach c $eventCommentList {
                lappend myCommentsToBePublished $c
            }
        } else {
            puts [ format "For publish event %s had no comments" [ dict get $event identifier ] ]
        }
        # then check by sender id if this was our event, and decide
        # if that should be published
        if { [ dict get $event senderId ] != $::profileInUse } {
            # not my event, don't publish
            continue
        }
        lappend myEventsToBePublished $event
    }
    # fetch possible previous version of the months record
    dict set searchDict searchPhrase [ searchString [ dict get $newOrDeletedEvent startTime ] ]
    dict set searchDict senderId $::profileInUse 
    dict set searchDict collectionId [ calculateSHA1 $::collectionInUse ]
    # getDbRecord returns a list if dictionaries. each dictionary has
    # key "data" that is our actual payload
    set previousDbRecords [ getDbRecord $searchDict ]
    set previousRecordFound false
    foreach r $previousDbRecords {
        if { [ dict exists $r data ] } {
            set recordData [ dict get $r data ]
            if { [ dict exists $recordData publicity ] } {
                set publicityOfRecord [ dict get $recordData publicity ]
                if { $publicityOfRecord == $publicity } {
                    set existingRecord $r
                    set previousRecordFound true
                    break
                }
            }
        }
    }
    if { $previousRecordFound == true } {
        puts "In publish found previous record whose data is updated"
        set recordToSave $existingRecord
        set recordData [ dict get $existingRecord data ]
        # replace the events in data:
        dict set recordData events $myEventsToBePublished
        dict set recordData comments $myCommentsToBePublished
        dict set recordToSave data $recordData
    } else {
        # construct a brand new record, record is a dictionary
        puts "In publish no previous record found"
        dict set recordToSave collectionId [ calculateSHA1 $::collectionInUse ]
        dict set recordToSave searchPhrase [ searchString [ dict get $event startTime ] ]
        if { $publicity == {public} || ( [ isProfilePrivate ] != true && $publicity == {byprofile} ) } {
            dict set recordToSave encrypted false
        } else {
            dict set recordToSave encrypted true
        }
        # construct another dictionary for the record data
        dict set recordData publicity $publicity
        dict set recordData startTime $monthStart
        dict set recordData endTime $monthEnd
        dict set recordData events $myEventsToBePublished
        dict set recordData comments $myCommentsToBePublished
        # and finally the events+comments:
        dict set recordToSave data $recordData
    }
    # record is ready, publish:
    publishDbRecord $recordToSave
}

# 
# method that returns beginning of month where given time belongs
# to
#
proc startOfMonth { timeStamp } {
    set yearNumber [ clock format $timeStamp -format {%Y} ]
    set monthNumber [ clock format $timeStamp -format {%N} ]
    set timeOfstartOfMonth [clock scan [ format "%s %s 01 00 00 00" $yearNumber $monthNumber ] -format {%Y %m %d %H %M %S} -gmt false ]
    return $timeOfstartOfMonth
}
# 
# method that returns end of month where given time belongs to
#
proc endOfMonth { timeStamp } {
    set timeOfStartOfMonth [startOfMonth $timeStamp ]
    set timeOfEndOfMonth [ clock add $timeOfStartOfMonth 1 month ]
    return $timeOfEndOfMonth
}
#
# method that produces db-record search string based on event start time.
# search string has structure "monXX-YYYY" where XX is month number
# and YYYY is year and it is calculated according to event start 
# time, using UTC timezone. Because events starting first hours of 
# month may appear in previous month in some timezones, it is 
# convenient to pad each month to contain also events from start+end
# of next month+prev month. 
# 
proc searchString { startTime } {
    return [ clock format  $startTime -format {mon%m-%Y} -locale UTC ]
}
#
# Procedure for fetching events from database by collection
# and time. Updates in-memory catalog of already seen db records
# to make it easier to prune deleted events when same db-record
# is published again by another operator. Does not return anything
# but updates the global ::allEvents list.
#
# parameter startTime determines the time, global variable ::collectionInUse
# the collection
#
proc eventsByTimeAndCollection {startTime} {
    # set search phrase to search dict
    dict set searchDict searchPhrase [ searchString $startTime ]
    # set collection id to search dict
    dict set searchDict collectionId [ calculateSHA1 $::collectionInUse ]
    # do actual search
    set dbRecords [ getDbRecord $searchDict ]
    # then loop through results. note bad algorithm .. this goes through
    # $::allEvents as many times as there are records in db so this is
    # O(n*m) while we could extract all changes from records in first pass
    # and then loop the $::allEvents with all changes to reduce the load
    # slightly
    foreach r $dbRecords {
        # if throws an error, $fid will contain error message
        if { [ catch {
            updateOneDbRecordIntoEventCollection $r
        } fid ] } {
            puts [ format {updateOneDbRecordIntoEventCollection: %s} $fid ]
        }
    }
}

#
# Proc that updates content of one db record into in-memory event
# collection. Should be called via "catch" method because input comes
# from other nodes and can be potentially *anything* 
#
proc updateOneDbRecordIntoEventCollection { r } {
    # see if this record has been seen already:
    set recordIsSeenAndNew false
    if { [ dict exists $::allDbRecords [ dict get $r recordId ] ] } {
        # yes, lets see if timestamp is the same
        set timeOfPreviouslyPublished [ dict get $::allDbRecords [ dict get $r recordId ] ]
        set timeOfNewRecord [ dict get $r timeOfPublish ]
        if { $timeOfNewRecord < $timeOfPreviouslyPublished } {
            # record has been seen and is same, or older:
            return
        } else {
            set recordIsSeenAndNew true
        }
    }
    # if my own record, we can skip because all my events should
    # be in local storage also so lets not bring them in twice:
    if { [ dict get $r senderId ] == $::profileInUse } {
        return
    }

    # stupid validity check:
    if { [ dict exists $r data ] == false } {
        # ugh, malformed record it must be, yoda ask for details
        return
    }

    set recordData [ dict get $r data ]

    # first remove all events by operator of record and matching publicity

    # each operator for given month (== searchPhrase) has either public or
    # private record, or both but no more than that, lets remove events
    # from in-memory list based on operator and publicity
    if { $recordIsSeenAndNew == true } {
        removeEventsByOperatorAndPublicity [ dict get $r senderId ] [ dict get $recordData publicity ]
        removeEventsByRecordId [ dict get $r recordId ]
    }
    # then, after events from that data record are not in 
    # our in-memory collection any more, lets add them
    # back
    foreach e [ dict get $recordData events ] {
        # before appending event to in-memory collection
        # set event owner to event, key in dictionary is senderId:
        dict set e senderId [ dict get $r senderId ]
        # before appending event to in-memory collection
        # set id of the originating db record
        dict set e recordId [ dict get $r recordId ]
        puts [ format "Adding from record %s event %s" [ dict get $r recordId ] [ dict get $e identifier ] ]
        lappend ::allEvents $e
    }
    # After events from record have been processed, lets continue 
    # with comments. Note how ::allComments is a dict where 
    # key is the commented event id ; published comments in turn
    # are just a list of dictionaries and the event id is
    # inside each comments dictionary. 
    if { [ dict exists $recordData comments ] } {
        foreach c [ dict get $recordData comments ] {
            # before appending comment to in-memory collection
            # set event owner to comment, key in dictionary is senderId:
            dict set c senderId [ dict get $r senderId ]
            # before appending comment to in-memory collection
            # set id of the originating db record
            dict set c recordId [ dict get $r recordId ]
            puts [ format "Adding from record %s comment %s" [ dict get $r recordId ] [ dict get $c identifier ] ]
            updateOneCommentIntoDictionary $c
        }
    }
    # update dictionary that keeps cache about which dbrecord has been seen
    # and which not:
    dict set ::allDbRecords [ dict get $r recordId ] [ dict get $r timeOfPublish ]
}
#
# procedure that removes from event collection "all events" by operator+publicity
#
proc removeEventsByOperatorAndPublicity { operator publicity } {
    # loop through events from end to start, removing
    # every event matching search criteria
    set listLen [ expr [ llength $::allEvents ] - 1 ]
    for { set i $listLen} { $i >= 0 } { set i [ expr $i - 1 ] } {
        set eventInList [ lindex $::allEvents $i ]
	if { [ dict exists $eventInList senderId ] && [ dict exists $eventInList publicity ] } { 
	    set eventOperator [ dict get $eventInList senderId ]
	    set eventPublicity [ dict get $eventInList publicity ]
	    if { $eventOperator == $operator && $eventPublicity == $publicity } {
		# remove
                puts [ format "Removing by operator event %s" [ dict get $eventInList identifier ] ]
		set ::allEvents [ lreplace $::allEvents $i $i ]
	    }
	}
    } 
}

#
# procedure that removes from event collection "all events" by record identifier
#
proc removeEventsByRecordId { recordIdToRemove } {
    # loop through events from end to start, removing
    # every event matching given record id
    set listLen [ expr [ llength $::allEvents ] - 1 ]
    for { set i $listLen} { $i >= 0 } { set i [ expr $i - 1 ] } {
        set eventInList [ lindex $::allEvents $i ]
	if { [ dict exists $eventInList recordId ] } { 
	    set eventRecordId [ dict get $eventInList recordId ]
	    if { $eventRecordId == $recordIdToRemove } {
		# remove
		set ::allEvents [ lreplace $::allEvents $i $i ]
	    }
	}
    } 
}
#
# Procedure for digging out one event by its id
#
proc eventById { identifier } {
    foreach event $::allEvents {
	if { [ dict get $event identifier ] == $identifier } {
	    return $event
	}
    }
    return null
}
#
# procedure that returns operator profile or if no profile is
# found then empty dictionary
#
proc getProfileAndEmptyIfNotFound { identifier } {
    if { [ catch { 
	set p [ getProfile $identifier ]
    } fid ] } {
        # error
        return [ dict create ]
    } else {
        return $p 
    }
}
#
# procedure that updates one event comment into in-memory dictionary
#
proc updateOneCommentIntoDictionary { comment } {
    if { [ dict exists $comment identifier ] && [ dict exists $comment commentedEvent ] } {
        set eventIdentifier [ dict get $comment commentedEvent ]
        set commentIdentifier [ dict get $comment identifier ]
        if { [ dict exists $::allComments $eventIdentifier ] } {
            # yes, this event has already been commented
            set eventCommentList [ dict get $::allComments $eventIdentifier ]
            # see if this particular comment already appears in the list:
            set commentAlreadySeen false
            foreach c $eventCommentList {
                if { [ dict get $c identifier ] == $commentIdentifier } {
                    # yes, is seen
                    return
                }
            }
            # if we got here, this particular comment is not yet on list:
            lappend eventCommentList $comment
            dict set ::allComments $eventIdentifier $eventCommentList
            puts [ format "Appending comment %s from published record" $commentIdentifier ]
        } else {
            # this is first comment for given event
            set eventCommentList [ list ]
            lappend eventCommentList $comment
            dict set ::allComments $eventIdentifier $eventCommentList
            puts [ format "Creating comment list for comment %s from published record" $commentIdentifier ]
        }
    }
}
# 
# Notification procedure that classified-ads host program calls whenever
# there is new content added to database. 
#
proc dataItemChanged { itemHash itemType } {
    if { $itemType != {dbrecord} } {
        return ; 
    }
    # it was db record, see if we're supposed to be interested:
    set currentSearchPhrase [ searchString $::monthOnDisplay ]
    set collectionHash [ calculateSHA1 $::collectionInUse ]
    dict set searchDict collectionId $collectionHash
    dict set searchDict recordId $itemHash
    set dbRecords [ getDbRecord $searchDict ]
    foreach r $dbRecords {
        set recordCollectionHash [ dict get $r collectionId ]
        if { $recordCollectionHash == $collectionHash } {
            if { [ dict exists $r data ] && [ dict exists $r searchPhrase ] } {
                set recordSearchPhrase [ dict get $r searchPhrase ]
                if { [ string last $currentSearchPhrase $recordSearchPhrase ] != -1 } {
                    # if throws an error, $fid will contain error message
                    if { [ catch {
                        updateOneDbRecordIntoEventCollection $r
                    } fid ] } {
                        puts [ format {updateOneDbRecordIntoEventCollection: %s} $fid ]
                    } else {
                        # update display
                        displayDay [ clock format $::dayOnDisplay -format "%d" ]
                    }
                } 
            } 
        } 
    }    
}
#
# execution starts here
# 
initGlobals
initUI
# load local events, not posted in public:
loadLocalDataFromStorage
# process settings
loadSettings
# load events of other operators from shared database:
eventsByTimeAndCollection $::monthOnDisplay
showDayView
displaySelectionCalendar $::monthOnDisplay
set ::initReady true
displayDay [ clock format $::monthOnDisplay -format "%d" ]
updateSelectionButtonsState $::monthOnDisplay