diff --git a/resources/Scribe.lr8 b/resources/Scribe.lr8
--- a/resources/Scribe.lr8
+++ b/resources/Scribe.lr8
@@ -1,5231 +1,5231 @@
-
+
-
+
-
+
-
+
-
-
+
+
-
+
-
+
-
+
-
+
-
+
+
+
-
-
+
diff --git a/resources/resdefs.h b/resources/resdefs.h
--- a/resources/resdefs.h
+++ b/resources/resdefs.h
@@ -1,1285 +1,1284 @@
// Generated by LgiRes
// This file generated by LgiRes
#define L_FUI_NEW -912
#define L_FUI_LEGEND -911
#define L_FUI_NOT -910
#define L_FUI_OPTIONS -909
#define L_FUI_CONFIGURE -908
#define L_FUI_MOVE_DOWN -907
#define L_FUI_MOVE_UP -906
#define L_FUI_DELETE -905
#define L_FUI_OR -904
#define L_FUI_AND -903
#define L_FUI_NEW_OR -902
#define L_FUI_NEW_AND -901
#define L_FUI_NEW_CONDITION -900
#define L_STORE_RESTART -803
#define L_STORE_MISMATCH -802
#define L_STORE_OS_ERR -801
#define L_STORE_WRITE_ERR -800
#define L_TOOLBAR_SHOW_TEXT -700
#define L_FR_SELECTION_ONLY -608
#define L_FR_REPLACE_WITH -607
#define L_FR_REPLACE_ALL -606
#define L_FR_REPLACE -605
#define L_FR_MATCH_CASE -604
#define L_FR_MATCH_WORD -603
#define L_FR_FIND_NEXT -602
#define L_FR_FIND_WHAT -601
#define L_FR_FIND -600
#define L_COLOUR_NONE -550
#define L_CHANGE_CHARSET -505
#define L_VIEW_IMAGES -504
#define L_VIEW_IN_DEFAULT_BROWSER -503
#define L_COPY_SOURCE -502
#define L_VIEW_SOURCE -501
#define L_COPY_LINK_LOCATION -500
#define L_FONTUI_UNDERLINE -407
#define L_FONTUI_TITLE -406
#define L_FONTUI_STYLE -405
#define L_FONTUI_PTSIZE -404
#define L_FONTUI_PREVIEW -403
#define L_FONTUI_ITALIC -402
#define L_FONTUI_FACE -401
#define L_FONTUI_BOLD -400
#define L_TEXTCTRL_TAB_SIZE -214
#define L_TEXTCTRL_INDENT_SIZE -213
#define L_TEXTCTRL_HARD_TABS -212
#define L_TEXTCTRL_SHOW_WHITESPACE -211
#define L_TEXTCTRL_UNDO -210
#define L_TEXTCTRL_REDO -209
#define L_TEXTCTRL_PASTE -208
#define L_TEXTCTRL_OPENURL -207
#define L_TEXTCTRL_GOTO_LINE -206
#define L_TEXTCTRL_FIXED -205
#define L_TEXTCTRL_EMAIL_TO -204
#define L_TEXTCTRL_CUT -203
#define L_TEXTCTRL_COPYLINK -202
#define L_TEXTCTRL_COPY -201
#define L_TEXTCTRL_AUTO_INDENT -200
#define IDS_ERROR_ESMTP_UNSUPPORTED_AUTHS -101
#define IDS_ERROR_ESMTP_NO_AUTHS -100
#define L_BTN_CANCEL -51
#define L_BTN_OK -50
#define FIELD_FLAGS 1
#define FIELD_TO 2
#define FIELD_CC 3
#define FIELD_FROM 4
#define FIELD_REPLY 5
#define FIELD_SUBJECT 6
#define FIELD_TEXT 7
#define FIELD_MESSAGE_ID 8
#define FIELD_DATE_RECEIVED 9
#define FIELD_INTERNET_HEADER 10
#define FIELD_FIRST_NAME 11
#define FIELD_LAST_NAME 12
#define FIELD_EMAIL 13
#define FIELD_HOME_STREET 14
#define FIELD_HOME_SUBURB 15
#define FIELD_HOME_POSTCODE 16
#define FIELD_HOME_STATE 17
#define FIELD_HOME_COUNTRY 18
#define FIELD_WORK_PHONE 19
#define FIELD_HOME_PHONE 20
#define FIELD_HOME_MOBILE 21
#define FIELD_HOME_IM 22
#define FIELD_HOME_FAX 23
#define FIELD_HOME_WEBPAGE 24
#define FIELD_NICK 25
#define FIELD_SPOUSE 26
#define FIELD_NOTE 27
#define FIELD_PLUGIN_ASSOC 28
#define FIELD_SIZE 29
#define FIELD_DATE_SENT 30
#define FIELD_COLUMN 31
#define FIELD_BCC 32
#define FIELD_MIME_TYPE 33
#define FIELD_PRIORITY 34
#define FIELD_FOLDER_OPEN 35
#define FIELD_CODE_PAGE 36
#define FIELD_MARK_COLOUR 37
#define FIELD_ALTERNATE_HTML 38
#define FIELD_CONTENT_ID 39
#define FIELD_FILTER_NAME 40
#define FIELD_CONDITION 41
#define FIELD_ACTION 42
#define FIELD_COND_FIELD 43
#define FIELD_COND_OPERATOR 44
#define FIELD_COND_VALUE 45
#define FIELD_ACT_TYPE 46
#define FIELD_ACT_ARG 47
#define FIELD_DIGEST_INDEX 48
#define FIELD_COMBINE_OP 49
#define FIELD_FILTER_INDEX 50
#define FIELD_FILTER_INCOMING 55
#define FIELD_FILTER_OUTGOING 56
#define FIELD_FILTER_INTERNAL 59
#define FIELD_CAL_SUBJECT 62
#define FIELD_CAL_LOCATION 63
#define FIELD_CAL_REMINDER_TIME 64
#define FIELD_CAL_REMINDER_ACTION_dep 65
#define FIELD_CAL_REMINDER_ARG_dep 66
#define FIELD_CAL_SHOW_TIME_AS 67
#define FIELD_CAL_RECUR_FREQ 68
#define FIELD_CAL_RECUR_INTERVAL 69
#define FIELD_CAL_RECUR_FILTER_DAYS 70
#define FIELD_CAL_RECUR_FILTER_MONTHS 71
#define FIELD_CAL_RECUR_FILTER_YEARS 72
#define FIELD_CAL_NOTES 73
#define FIELD_CAL_START_UTC 76
#define FIELD_CAL_END_UTC 77
#define FIELD_CAL_RECUR_FILTER_POS 78
#define FIELD_CAL_RECUR_END_DATE 79
#define FIELD_CAL_RECUR_END_COUNT 80
#define FIELD_CAL_RECUR_END_TYPE 81
#define FIELD_CAL_RECUR 82
#define IDC_COLOUR 83
#define FIELD_ATTENDEE_NAME 85
#define FIELD_ATTENDEE_EMAIL 86
#define FIELD_ATTENDEE_ATTENDENCE 87
#define FIELD_ATTENDEE_NOTE 88
#define FIELD_ATTENDEE_RESPONSE 89
#define FIELD_WORK_STREET 90
#define FIELD_WORK_SUBURB 91
#define FIELD_WORK_POSTCODE 92
#define FIELD_WORK_STATE 93
#define FIELD_WORK_COUNTRY 94
#define FIELD_WORK_MOBILE 95
#define FIELD_WORK_IM 96
#define FIELD_WORK_FAX 97
#define FIELD_WORK_WEBPAGE 98
#define FIELD_COMPANY 99
#define FIELD_LABEL 110
#define IDM_SAVE 113
#define FIELD_TITLE 114
#define FIELD_SERVER_UID 117
#define FIELD_GROUP_NAME 120
#define FIELD_GROUP_LIST 123
#define FIELD_FROM_CONTACT_NAME 126
#define IDS_ENCRYPTION_SUPPORT 150
#define FIELD_DATE_MODIFIED 162
#define FIELD_IMAP_SEQ 167
#define FIELD_RECEIVED_DOMAIN 170
#define IDD_FILTER_ITEMS 175
#define IDC_SIGNATURE_TAB 184
#define IDC_RESET_REPLY 186
#define IDC_SIG 187
#define IDC_RESET_FORWARD 189
#define IDS_205 205
#define IDS_SAVE_ITEM 208
#define IDS_DESCRIPTIVE_NAME 209
#define IDS_ERROR_SOFTWARE_UPDATE 210
#define IDS_211 211
#define IDS_235 235
#define IDC_SUB_FOLDERS 275
#define IDM_FEEDBACK 287
#define IDM_SCRIBE_LINK 291
#define IDM_INSCRIBE_LINK 292
#define IDM_FAQ 293
#define IDC_READ 316
#define IDD_SUB_FOLDERS 317
#define IDC_HAS_TEMPLATES 322
#define IDC_INBOX 324
#define IDC_OUTBOX 325
#define IDM_PAGE_SETUP 326
#define IDC_TRASH 327
#define IDC_CONTACT_FLD 328
#define IDS_DEBUG 329
#define IDC_SET_INBOX 330
#define IDC_SET_OUTBOX 331
#define IDC_SET_SENT 332
#define IDC_SET_TRASH 333
#define IDC_SET_CONTACTS 334
#define IDC_SET_FILTERS 335
#define IDC_MAX_SIZE 342
#define IDC_CONFIRM_DEL 343
#define IDS_CONTENT_ID 344
#define IDC_BOLD_UNREAD 346
#define IDC_GRID_LINES 347
#define IDC_TITLE 348
#define IDC_PREVIEW_LINES 349
#define IDC_SET_TEMPLATES 350
#define IDC_351 351
#define IDC_SMTP_AUTH 353
#define IDC_FILTERS_FLD 354
#define IDS_RETRY 355
#define IDS_FOLDER_PROPERTIES_COMPACT 356
#define IDC_HAS_SPAM 357
#define IDS_CALENDAR 359
#define IDS_NO_LOG 360
#define IDC_REC_TYPE 362
#define IDS_FIND 363
#define IDS_NO_ITEMS 364
#define IDS_RECEIVE_ALL_ACCOUNTS 365
#define IDS_NO_ITEMS_IN_FOLDER 366
#define IDS_PREVIEW_ON_SERVER 367
#define IDS_RECEIVE_MAIL 368
#define IDS_NO_TEMPLATES 369
#define IDC_STOP_FILTERING 370
#define IDS_NEW_EMAIL 371
#define IDS_EXIT 372
#define IDS_MARK_ALL_SEND 373
#define IDS_MARK 374
#define IDS_SELECT_MARKED 375
#define IDS_PRIORITY 376
#define IDS_HIGH 377
#define IDS_NORMAL 378
#define IDS_LOW 379
#define IDS_NEW_CONTACT 380
#define IDS_RECEIVE 381
#define IDC_LIMIT_TO 382
#define IDS_PRINT 383
#define IDS_CAL_SDAY_SUN 384
#define IDS_SAVE_CLOSE 385
#define IDS_FILTER 386
#define IDS_PREV_MSG 387
#define IDS_NEXT_MSG 388
#define IDS_CONDITIONS 390
#define IDS_ACTIONS 391
#define IDS_DETAIL 392
#define IDS_FIELDS 393
#define IDS_FILE_NAME 394
#define IDS_MIME_TYPE 395
#define IDS_PREVIEW 396
#define IDS_ERROR_FOLDERS_VERSION 397
#define IDS_ERROR_FOLDERS_STATUS 398
#define IDS_EXPORT 399
#define IDS_STATUS 400
#define IDD_STATUS 401
#define IDS_402 402
#define IDC_ACC_LIST 403
#define IDC_404 404
#define IDC_IN_FOLDER 406
#define IDS_407 407
#define IDS_408 408
#define IDC_STATUS_TXT 409
#define IDC_OP_TXT 410
#define IDS_411 411
#define IDC_EMAIL_TXT 412
#define IDC_EMAIL_PROG 413
#define IDD_MAIL_PROPERTIES 414
#define IDC_STOP 415
#define IDC_SEND_LOG 416
#define IDC_OP_PROG 417
#define IDC_SET_IN 418
#define IDS_HIDE_GNUPG 419
#define IDS_RESIZE 420
#define IDS_COPY_FOLDER 421
#define IDS_GNUPG_ATTACH_PUB_KEY 422
#define IDS_GNUPG_SIGN 423
#define IDS_GNUPG_ENCRYPT 424
#define IDS_GNUPG_DECRYPT 425
#define IDS_GNUPG_ERR_NOMSG 426
#define IDC_CLEAR_KEYWORDS 436
#define IDS_MONTH 437
#define IDC_BAYES_TABLE 439
#define IDS_HIGH_PRIORITY 440
#define IDD_ACCOUNT 441
#define IDS_443 443
#define IDS_444 444
#define IDS_445 445
#define IDC_MAIL_DESCRIPTION 448
#define IDC_SENT 449
#define IDC_RECEIVED 450
#define IDC_FORWARDED 451
#define IDC_REPLIED 452
#define IDC_HAS_ATTACH 453
#define IDC_MARKED 455
#define IDC_READY_SEND 456
#define IDC_OPEN_INSPECTOR 457
#define IDC_CREATED 459
#define IDD_NO_FOLDER 460
#define IDC_MESSAGE 462
#define IDS_470 470
#define IDC_CREATE_FOLDER 471
#define IDS_472 472
#define IDS_475 475
#define IDC_ACCOUNT 476
#define IDC_ACCOUNT_NAME 477
#define IDC_SMTP_PASSWORD 478
#define IDC_EXISTING 486
#define IDC_FOLDER_HISTORY 487
#define IDS_488 488
#define IDS_ASK_TNEF_DECODE 495
#define IDC_RECEIVE_TABLE 500
#define IDC_SEND_SSL 501
#define IDS_OK 502
#define IDC_NEW_FOLDER 503
#define IDC_EXISTING_FOLDER 504
#define IDC_BROWSE_NEW 505
#define IDC_BROWSE_EXISTING 506
#define IDS_YEAR 507
#define IDC_TEXT 508
#define IDC_SEARCH 509
#define IDS_510 510
#define IDC_BAYES_MODE 511
#define IDC_REPEATS 512
#define IDS_YEARS 513
#define IDS_CANCEL 514
#define IDS_SAVE 515
#define IDS_EMAIL 516
#define IDS_REPLY 517
#define IDS_REPLYALL 518
#define IDS_FORWARD 519
#define IDC_SIG_HTML 520
#define IDS_TO 521
#define IDC_DEFAULT_ALT 522
#define IDS_COMPACT_TESTING_DLG 523
#define IDS_FOLDER_COMPACT_ERR_DLG 524
#define IDS_COULDNT_OPEN 525
#define IDC_SEARCH_SUB 526
#define IDC_FIND_CASE 527
#define IDC_FIND_WORD 528
#define IDS_ATTACH_FILE 529
#define IDC_CTRLS_TABLE 530
#define IDC_MAIL_FIELD 531
#define IDC_CONTACT 532
#define IDM_BAYES_STATS 533
#define IDS_EDIT 534
#define IDC_ID_TABLE 535
#define IDC_CONTACT_FIELD 536
#define IDC_ACTION 537
#define IDS_REMOVE 538
#define IDM_UNIT_TESTS 539
#define IDC_LAYOUT 540
#define IDC_TABS 541
#define IDC_275 542
#define IDC_FIRST 543
#define IDS_544 544
#define IDC_OUT_FOLDER 545
#define IDC_SET_OUT 546
#define IDD_PAGE_SETUP 547
#define IDC_PREVIEW 548
#define IDS_ATTACH_WARNING_DLG 549
#define IDC_BROWSE_FONT 550
#define IDS_MAIL_PROPS_DLG 551
#define IDS_552 552
#define IDS_553 553
#define IDS_554 554
#define IDS_555 555
#define IDC_LEFT 556
#define IDC_TOP 557
#define IDC_RIGHT 558
#define IDC_BOTTOM 559
#define IDS_MAIL_PROPS 560
#define IDC_CUSTOM_FIELDS 561
#define IDS_ADD_CONTACTS 562
#define IDS_SUBFLD_NAME_CLASH 563
#define IDC_TREE 564
#define IDS_ERROR_NO_RECIPIENTS 565
#define IDC_USER_PASS_CONFIRM 566
#define IDS_SAVE_ATTACHMENT_AS 567
#define IDS_NO_CONNECTION_TO_SERVER 568
#define IDS_SERVER_ERROR_MSG 569
#define IDS_NO_SMTP_SERVER_SETTINGS 570
#define IDS_ASK_ABOUT_OPTIONS 571
#define IDS_THIS_WEEK 572
#define IDS_UPDATE_PERIOD 573
#define IDS_HEX_LOG 574
#define IDS_OPEN 575
#define IDS_BYTE_LOG 576
#define IDC_CALENDER 577
#define IDS_WINDOWS_SSL_INSTALL 578
#define IDC_KEYWORDS 579
#define IDC_PATH 580
#define IDC_UNREAD 581
#define IDC_LOCATION 582
#define IDS_ADDRESS 583
#define IDS_POPUP 584
#define IDC_PARAM_LABEL 585
#define IDC_DIR 586
#define IDS_LOW_PRIORITY 587
#define IDD_CAL 588
#define IDD_CAL_REMOTE 589
#define IDC_590 590
#define IDS_ADD_CAL 591
#define IDS_ADD_CAL_POPUP 592
#define IDS_SET_READ 593
#define IDS_SET_UNREAD 594
#define IDS_PROPERTIES 595
#define IDS_SUBJECT 596
#define IDS_OPTIONS 597
#define IDS_SMTP 598
#define IDS_SIZE 599
#define IDS_DATE 600
#define IDS_SAVEAS 601
#define IDS_EMPTY 602
#define IDS_ERROR_FOLDERS_DONT_EXIST 603
#define IDS_ERROR_CANT_OPEN_FOLDERS 604
#define IDS_ENTER_FILE_NAME_FOR_FOLDERS 605
#define IDS_SEND_MAIL 606
#define IDS_NEW 607
#define IDS_APPOINTMENT 608
#define IDS_CAL_EVENT 609
#define IDS_CONTACT 610
#define IDS_CREATESUBFOLDER 611
#define IDS_612 612
#define IDS_CAL_VIEW 613
#define IDS_MINUTES 614
#define IDS_HOUR 615
#define IDS_HOURS 616
#define IDS_DAY 617
#define IDS_DAYS 618
#define IDS_FREE 619
#define IDS_TENTATIVE 620
#define IDS_BUSY 621
#define IDS_OUT_OF_OFFICE 622
#define IDS_DOWNLOAD 623
#define IDS_READ_RECEIPT 624
#define IDS_CAL_SDAY_MON 625
#define IDS_RENAMEFOLDER 626
#define IDC_FOLDER_MSG 627
#define IDC_PASS 628
#define IDS_629 629
#define IDS_630 630
#define IDS_631 631
#define IDD_FOLDER_PROPS 632
#define IDS_FIRST 633
#define IDS_LOCAL_CONTACTS 634
#define IDC_ACCOUNT_TBL 635
#define IDS_MOVE_ERROR 636
#define IDS_MESSAGE 637
#define IDS_ACTION 638
#define IDS_MSGS_ERROR 639
#define IDS_CAL_LMONTH_OCT 640
#define IDC_ACCOUNTS_READ 641
#define IDS_CONFIGURE 642
#define IDS_ERROR_FILE_OVERWRITE 643
#define IDC_INTERNAL_FILTERING 644
#define IDC_STORE2_KEEP 645
#define IDS_ERROR_CANT_READ 646
#define IDC_FOLDER_WRITE 647
#define IDS_ERROR_FILE_EXISTS 648
#define IDD_BAYES_SETTINGS 649
#define IDC_USER_LEVEL_PSW 650
#define IDC_USER_PASS 651
#define IDS_ERROR_FOLDERS_ALREADY_EXIST 652
#define IDC_APW_USER 653
#define IDC_COMPACT_MS 654
#define IDD_FIND 655
#define IDC_DEF_SEND 656
#define IDC_TOOLBAR_TEXT 657
#define IDS_ADD 658
#define IDM_SAVEAS 659
#define IDC_URL 660
#define IDC_SUBJECT 661
#define IDD_MAIL_FIELDS 662
#define IDD_KEY 663
#define IDC_LABEL 664
#define IDS_147 665
#define IDC_SENT_DATE 666
#define IDS_148 667
#define IDC_RECEIVED_DATE 668
#define IDD_FILTER 669
#define IDC_PRIVACY_TYPE 670
#define IDS_671 671
#define IDM_NO_IDENTITIES 672
#define IDD_FILTER_ACTION 673
#define IDD_NEWFOLDER_F 674
#define IDD_FILES_IMPORT 675
#define IDS_ADD_LOCAL_CAL_FOLDER 676
#define IDC_UI_FNT_SIZE 677
#define IDC_UI_LANG 678
#define IDS_ERROR_LR8_FAILURE 679
#define IDC_REC_8BIT_CS 680
#define IDC_CLIENT 681
#define IDC_BLINK 682
#define IDC_FOLDER_READ 683
#define IDC_ACCOUNTS_WRITE 684
#define IDC_CUSTOM_TABLE 685
#define IDC_APR_USER 686
#define IDC_NAME_TABLE 687
#define IDC_USER 688
#define IDC_APR_ADMIN 689
#define IDC_APW_NONE 690
#define IDS_ADD_CAL_URL 691
#define IDC_REMINDER_VALUE 692
#define IDS_LIKE 693
#define IDC_PREVIEW_READ 694
#define IDS_ASK_USER_PASS 695
#define IDD_FILTER_SAVE_ATTACH 696
#define IDM_HELP 697
#define IDC_TYPES 698
#define IDC_OTHER_TABLE 699
#define IDC_BROWSE_DIR 700
#define IDC_ONLY_SEND_THIS 701
#define IDC_GROUPS_FLD 702
#define IDC_SET_GROUPS 703
#define IDS_CC 704
#define IDS_RECIPIENTS 705
#define IDS_TEXT 706
#define IDS_ATTACHMENTS 707
#define IDS_INTERNETHEADER 708
#define IDS_FOLDER_PROPERTIES_DLG 709
#define IDS_TOMORROW 710
#define IDS_NEXT_WEEK 711
-#define IDC_FILES 712
-#define IDC_PICK_FILES 713
+#define IDC_SRC 712
#define IDC_USAGE 714
#define IDC_START_TIME 715
#define IDS_ABOUT 716
#define IDC_COLLECT_SUB_MAIL 717
#define IDS_NO_MAIL_TO_FILTER 718
#define IDS_FORWARD_PREFIX 719
#define IDS_REPLY_PREFIX 720
#define IDS_SPAM 721
#define IDS_SEARCH 722
#define IDS_ITEM_FILTER 723
#define IDD_WEBDAV_PROPS 724
#define IDC_725 725
#define IDM_IMPORT_EML 726
#define IDC_CONTACTS_URL 727
#define IDS_DELETE_FOLDER_DLG 728
#define IDS_1035 729
#define IDS_SET_DEFAULT 730
#define IDC_HTML_FONT 731
#define IDC_SET_HTML_FONT 732
#define IDS_APPEARANCE 733
#define IDS_UPDATE 734
#define IDC_EDIT_REPLY 735
#define IDC_EDIT_FORWARD 736
#define IDC_CALENDAR_URL 737
#define IDC_APW_ADMIN 738
#define IDS_ERROR_EXE_FILE 739
#define IDC_USERNAME 740
#define IDS_FROM 741
+#define IDC_ADD 742
#define IDC_DATE_FORMAT 743
#define IDS_1034 744
#define IDS_SORT_SUBFOLDERS 745
#define IDC_DESC 746
#define IDC_948 747
#define IDS_LOADING 748
#define IDS_MOVE_FOLDER 749
#define IDC_FILTER_INDEX 750
#define IDC_FILTER_UP 751
#define IDC_FILTER_DOWN 752
#define IDM_SCRIPT_BREAK_ON_WARN 753
#define IDS_MOVE_ATTACH 754
#define IDS_ERROR_WONT_OVERWRITE_FOLDERS 755
#define IDS_NEXT_FOLDER 756
#define IDC_BAYES_READ 757
#define IDM_DELETE 758
#define IDS_SUB_FOLDER 759
#define IDS_WEEK 760
#define IDS_ERROR_READONLY_FOLDERS 761
#define IDC_TIMEZONE 762
#define IDC_LOCALTIME 763
#define IDC_HIDE_ID 764
#define IDS_SOFTWARE_UPDATE_DOWNLOAD 765
#define IDD_FILTER_SCRIPT 766
#define IDC_SCRIPT 767
#define IDS_ASK_DUPE_CONTACT 768
#define IDS_PERM_DEL_ATTACHMENTS 769
#define IDC_HTTP_PROXY 770
#define IDC_PICK_TZ 771
#define IDS_THREAD 772
#define IDS_SOFTWARE_CURRENT 773
#define IDS_TRANSLATION_AUTHORS 774
#define IDD_CSV_IMPORT 775
#define IDC_THEME 776
#define IDS_BCC 777
#define IDS_CLICK_TO_ADD 778
#define IDS_779 779
#define IDC_BROWSE_THEMES 780
#define IDD_OUTLOOK_EXPORT 781
#define IDS_SUMMARY 782
#define IDC_BROWSE_FOLDER 783
#define IDS_MERGE_TEMPLATE 784
#define IDS_MERGE_FILE 785
#define IDS_COPY 786
#define IDD_SETTINGS 787
#define IDS_DELETE_SYS_FOLDER 788
#define IDS_789 789
#define IDS_CAL_SDAY_TUE 790
#define IDS_791 791
#define IDS_792 792
#define IDS_793 793
#define IDS_794 794
#define IDS_MBOX_IMPORT 795
#define IDC_SET_READ 796
#define IDS_CAL_SDAY_WED 797
#define IDS_CAL_SDAY_THU 798
#define IDS_CAL_SDAY_FRI 799
#define IDS_CAL_SDAY_SAT 800
#define IDS_CAL_LDAY_SUN 801
#define IDS_CAL_LDAY_MON 802
#define IDS_CAL_LDAY_TUE 803
#define IDS_CAL_LDAY_WED 804
#define IDS_CAL_LDAY_THU 805
#define IDS_CAL_LDAY_FRI 806
#define IDS_CAL_LDAY_SAT 807
#define IDS_CAL_SMONTH_JAN 808
#define IDS_CAL_SMONTH_FEB 809
#define IDS_CAL_SMONTH_MAR 810
#define IDS_CAL_SMONTH_APR 811
#define IDS_CHANGED 812
#define IDS_CAL_SMONTH_MAY 813
#define IDS_CAL_SMONTH_JUN 814
#define IDS_CAL_SMONTH_JUL 815
#define IDS_CAL_SMONTH_AUG 816
#define IDS_CAL_SMONTH_SEP 817
#define IDS_CAL_SMONTH_OCT 818
#define IDS_CAL_SMONTH_NOV 819
#define IDS_CAL_SMONTH_DEC 820
#define IDS_CAL_LMONTH_JAN 821
#define IDS_EVENT_DUE 822
#define IDC_BAYES_THRESHOLD 823
#define IDS_CAL_LMONTH_FEB 824
#define IDS_CAL_LMONTH_MAR 825
#define IDS_CAL_LMONTH_APR 826
#define IDS_CAL_LMONTH_MAY 827
#define IDS_CAL_LMONTH_JUN 828
#define IDS_CAL_LMONTH_JUL 829
#define IDS_CAL_LMONTH_AUG 830
#define IDC_PREVIEW_SECONDS 831
#define IDS_CAL_LMONTH_SEP 832
#define IDC_SOCKS5_PASSWORD 833
#define IDS_CAL_LMONTH_NOV 834
#define IDS_CAL_LMONTH_DEC 835
#define IDC_RECEIVE_SSL 836
#define IDC_SEND_CHARSET1 837
#define IDC_SEND_CHARSET2 838
#define IDS_DELETE_ASK 839
#define IDS_DONT_ADJUST_TZ 840
#define IDC_DELETE_AFTER 841
#define IDC_HTML_IMAGES 842
#define IDC_AUTO_DELETE_EXE 843
#define IDD_GROUP 844
#define IDC_SEND_AUTH_TYPE 845
#define IDM_HOMEPAGE 846
#define IDC_GROUP_NAME 847
#define IDC_GROUP_LIST 848
#define IDC_START_DATE 849
#define IDC_850 850
#define IDS_TODAY 851
#define IDC_REC_ASCII_CP 852
#define IDS_FOLDER_OUTBOX 853
#define IDS_FOLDER_SENT 854
#define IDS_780 855
#define IDS_FOLDER_FILTERS 856
#define IDS_FOLDER_CONTACTS 857
#define IDS_ATTACHMENTS_NAME 858
#define IDS_ATTACHMENTS_DATA 859
#define IDS_ANYWHERE 860
#define IDS_TODO 861
#define IDS_FOLDER_TEMPLATES 862
#define IDS_FOLDER_GROUPS 863
#define IDS_FOLDER_CALENDAR 864
#define IDS_DEFAULT_CHARSET_RECEIVE 865
#define IDS_DUE 866
#define IDS_CLICK_TO_CREATE 867
#define IDC_REPEAT 868
#define IDS_1101 869
#define IDS_ERROR_ALREADY_ONLINE 870
#define IDS_NOT_LOADED 871
#define IDC_SHOW_LOCATION 872
#define IDS_ERROR_MAPI_NOT_INSTALLED 873
#define IDC_CALENDAR 874
#define IDS_PLUGINS_NO_CONF 875
#define IDC_TAB1 876
#define IDC_TAB2 877
#define IDC_CHECK_EVERY 878
#define IDS_1116 879
#define IDC_FILTER_ACTIONS 880
#define IDC_LOGIN 881
#define IDC_MSG_STORE 882
#define IDC_SRC_FOLDERS 883
#define IDC_FOLDER 884
#define IDS_ADD_ALL_CONTACTS 885
#define IDS_MAPI_LOGIN_FAILED 886
#define IDS_ACCSTAT_IDLE 887
#define IDS_BROWSE_FOLDER 888
#define IDS_3 889
#define IDS_ASK_ADMIN_PASS 890
#define IDS_LINKS 891
#define IDC_EXTRA_HEADERS 892
#define IDC_FORMAT 893
#define IDM_TABLE2 894
#define IDC_RECEIVE_AUTH_TYPE 895
#define IDS_896 896
#define IDC_SUSPECT_FOLDER 897
#define IDM_SET_UNREAD 898
#define IDS_HTML_TAGS 899
#define IDC_PROGRESS_TBL 900
#define IDS_DELETE 901
#define IDC_SET_SUSPECT_FOLDER 902
#define IDC_SEND_TABLE 903
#define IDS_838 904
#define IDC_TEMPLATE 905
#define IDS_1082 906
#define IDC_TXT_DAYS 907
#define IDC_FOLDERS 908
#define IDD_SECURITY 909
#define IDS_9 910
#define IDS_REMOVE_DUPES 911
#define IDC_SHOW_TOTALS 912
#define IDC_913 913
#define IDC_REMINDER_TYPE 914
#define IDS_ALWAYS_SHOW_REMOTE_CONTENT 915
#define IDC_TXT_SIZE 916
#define IDS_ERROR_FILE_EMPTY 917
#define IDC_REMINDER_PARAM 918
#define IDC_REMINDER_UNIT 919
#define IDD_EML_IMPORT 920
#define IDC_HOME_TABLE 921
#define IDC_APR_NONE 922
#define IDS_OPEN_CONTACT 923
#define IDS_THREAD_SELECT 924
#define IDS_THREAD_DELETE 925
#define IDS_THREAD_IGNORE 926
#define IDS_NONE 927
#define IDS_ALL 928
#define IDS_CONVERT_SELECTION 929
#define IDS_MULTIPLE_ITEMS 930
#define IDC_GROUP 931
#define IDC_MAPPING 932
#define IDS_933 933
#define IDS_934 934
#define IDC_TAB 935
#define IDC_HAS_FILTERS 936
#define IDC_MODE 937
#define IDC_MERGE 938
#define IDC_APPEND 939
#define IDC_CHECK_DEFAULT 940
#define IDC_EVERY 941
#define IDC_LOAD 942
#define IDS_ERROR_CANT_WRITE 943
#define IDS_944 944
#define IDS_SAVE_TO_OUTBOX 945
#define IDS_INC_BETA 946
#define IDS_947 947
#define IDC_TIME_TYPE 948
#define IDS_FOLDER_TRASH 949
#define IDD_FILES_EXPORT 950
#define IDS_1022 951
#define IDC_END_DATE 952
#define IDC_HAM 953
#define IDC_SPAM 954
#define IDC_FALSE_NEG 955
#define IDC_FALSE_POS 956
#define IDC_EFFICIENCY 957
#define IDS_RECEIPT_ASK 958
#define IDS_ERROR_NO_HELP 959
#define IDC_END_TIME 960
#define IDC_869 961
#define IDS_ERROR_CONNECTION 962
#define IDS_CREATE_FOLDER 963
#define IDC_ALL_DAY 964
#define IDC_DEF_REPLYALL_SETTING 965
#define IDS_DEFAULT_CHARSET_SEND 966
#define IDC_WEDNESDAY 967
#define IDS_WARN_NO_SECURITY 968
#define IDC_969 969
#define IDC_LAUNCH_HELP 970
#define IDC_THURSDAY 971
#define IDD_LANG 972
#define IDC_LANG 973
#define IDC_FRIDAY 974
#define IDS_975 975
#define IDS_976 976
#define IDD_FILTER_REPLY 977
#define IDD_FILTER_FORWARD 978
#define IDC_SATURDAY 979
#define IDC_SUNDAY 980
#define IDC_REC_PORT 981
#define IDS_INSPECT 982
#define IDS_FOLDER_INBOX 983
#define IDC_OCCURRENCES 984
#define IDC_CONFIGURE_REMOTE_CONTENT 985
#define IDC_TABLE3 986
#define IDC_BLACKLIST 987
#define IDC_ATTACHMENTS 988
#define IDS_GNUPG_ERR_NO_BOUNDARY 989
#define IDS_GNUPG_ERR_NO_MIME 990
#define IDS_ASK_FORWARD_ATTACHMENTS 991
#define IDC_ALL 992
#define IDC_MARK_REPLIED 993
#define IDC_MARK_FORWARDED 994
#define IDS_BOUNCE 995
#define IDS_REMOVE_WHITELIST 996
#define IDC_UNDELETE 997
#define IDS_ERROR_NO_CONFIG_SEND 998
#define IDS_ERROR_NO_CONFIG_RECEIVE 999
#define IDC_MAIL 1000
#define IDC_CONTACTS 1001
#define IDC_NAME 1002
#define IDC_TYPE 1003
#define IDC_EMAIL 1004
#define IDS_SEND 1005
#define IDC_REPLY_TO 1006
#define IDC_NICK 1007
#define IDM_SCRIBE_FAQ 1008
#define IDC_SPOUSE 1009
#define IDC_HAS_CAL_EVENTS 1010
#define IDC_QUOTE_STR 1011
#define IDC_WRAP_COLS 1012
#define IDS_ASK_DELETE_DUPES 1013
#define IDS_GNUPG_ERR_NO_RECIP 1014
#define IDC_START_IN 1015
#define IDC_SET_START_IN 1016
#define IDM_LAYOUT4 1017
#define IDC_DOWN 1018
#define IDM_REFRESH 1019
#define IDS_ERROR_REG_WRITE 1020
#define IDS_ERROR_NEED_INSTALL 1021
#define IDC_SMTP_SERVER 1022
#define IDC_SMTP_NAME 1023
#define IDC_REC_SERVER 1024
#define IDC_REC_NAME 1025
#define IDC_REC_PASSWORD 1026
#define IDC_REC_CHECK 1027
#define IDC_POP3_LEAVE 1028
-#define IDS_ERROR_SERVER_CONNECT 1029
+#define IDC_DST 1029
#define IDC_LAST 1030
#define IDC_QUOTE 1031
#define IDC_HOME_FAX 1032
#define IDC_HOME_STREET 1033
#define IDC_ACCOUNTS 1034
#define IDC_HOME_SUBURB 1035
#define IDC_FONT 1036
#define IDC_DESCRIPTION 1037
#define IDC_NEW_MAIL_SOUND 1038
#define IDS_MAIL_MESSAGE 1039
#define IDC_SMTP_DOMAIN 1040
#define IDC_HOME_STATE 1041
#define IDC_REPLYWITHSIG 1042
#define IDC_HOME_COUNTRY 1043
#define IDS_POP3 1044
#define IDC_HOME_PHONE 1045
#define IDC_HOME_MOBILE 1046
#define IDC_HOME_IM 1047
#define IDC_HOME_WEBPAGE 1048
#define IDC_NOTE 1049
#define IDC_DEFAULT 1050
#define IDC_TRAY 1051
#define IDS_DELETEFOLDER 1052
#define IDC_CHECKDIALUP 1053
#define IDC_SET_FONT 1054
#define IDC_POP3_ON_START 1055
#define IDC_LIST 1056
#define IDC_START_WEEK 1057
#define IDC_REFRESH 1058
#define IDS_COPY_PATH 1059
#define IDC_SET_SOUND 1060
#define IDS_OE_IMPORT 1061
#define IDS_1062 1062
#define IDM_MENU_1063 1063
#define IDC_DEL_SRC_FOLDER 1064
#define IDC_PICK_FOLDER 1065
#define IDC_WRAP 1066
#define IDC_NO_SPAM_TRASH 1067
-#define IDC_ADD 1068
+#define IDC_PICK_FILES 1068
#define IDC_MSG 1069
#define IDC_KEY 1070
#define IDC_DONT_WARN 1071
#define ID_YES 1072
#define ID_NO 1073
#define IDC_REGISTER_CLIENT 1074
#define IDC_SOCKS5_SERVER 1075
#define IDC_SOCKS5_USER 1076
#define IDC_REMINDER_ADD 1077
#define IDC_USE_TEMPLATE 1078
#define IDC_SET_FOLDER 1079
#define IDC_REMINDERS 1080
#define IDC_AVAILABLE_TYPE 1081
#define IDC_AVAILABLE 1082
#define IDS_ERROR_NO_SPACE 1083
#define IDC_TEMPLATES 1084
#define IDS_RECEIPT_BODY 1085
#define IDS_SPAM_PROB 1086
#define IDS_IS_WHITELIST 1087
#define IDC_BUSY 1088
#define IDS_ERROR_MAPI_DEFAULT 1089
#define IDS_ERROR_SOFTWARE_KEY 1090
#define IDM_MANAGE_MAIL_STORES 1091
#define IDC_PUBLIC 1092
#define IDC_PRIVATE 1093
#define IDC_NEW_FILTER_ACTION 1094
#define IDC_DELETE_FILTER_ACTION 1095
#define IDS_MAIL 1096
#define IDC_RADIO1 1097
#define IDC_RADIO2 1098
#define IDC_DEF_ACTION 1099
#define IDC_WORK_COUNTRY 1100
#define IDC_WORK_PHONE 1101
#define IDC_WORK_MOBILE 1102
#define IDC_WORK_IM 1103
#define IDC_WORK_STATE 1104
#define IDC_WORK_POSTCODE 1105
#define IDC_WORK_STREET 1106
#define IDC_WORK_FAX 1107
#define IDC_WORK_SUBURB 1108
#define IDC_HOME_POSTCODE 1109
#define IDC_COMPANY 1110
#define IDC_WORK_WEBPAGE 1111
#define IDC_SOCKS5 1112
#define IDC_DELETE 1113
#define IDS_MAIL_MERGE_Q 1114
#define IDC_ADD_SRC_FOLDER 1115
#define IDC_PROPERTIES 1116
#define IDD_OUTLOOK_ADD_FLD 1117
#define IDS_ASK_FOLDER_PASS 1118
#define IDC_DEF_SEND_TXT 1119
#define IDS_ERROR_SCRIPT_COMPILE 1120
#define IDS_ANY 1121
#define IDS_ERROR_NOTHING_TO_UPGRADE 1122
#define IDC_PAGE_ALL 1123
#define IDC_SAVE 1124
#define IDC_PAGE_RANGES 1125
#define IDS_PASSWORD_SAVED 1126
#define IDS_RESIZE_JPEG_QUAL 1127
#define IDC_TXT_MIN 1128
#define IDC_TXT_DOWNLOAD 1129
#define IDC_TXT_KBS 1130
#define IDM_SCRIPTING_CONSOLE 1131
#define IDD_PRINT_PREVIEW 1132
#define IDC_SAVE_IMG 1133
#define IDS_ACCSTAT_SETUP 1134
#define IDS_ACCSTAT_CONNECT 1135
#define IDS_ACCSTAT_TRANS 1136
#define IDS_ACCSTAT_WAIT 1137
#define IDS_ACCSTAT_DELETING 1138
#define IDS_ACCSTAT_FINISHED 1139
#define IDS_ACCSTAT_CANCELLED 1140
#define IDS_ERROR 1141
#define IDS_ENTER_KEY 1142
#define IDM_SCRIPT_DEBUG 1143
#define IDC_UPDATE 1144
#define IDC_1145 1145
#define IDS_CONTAINS 1146
#define IDS_STARTS_WITH 1147
#define IDS_ENDS_WITH 1148
#define IDS_SERVER 1149
#define IDS_1150 1150
#define IDM_MENU_1151 1151
#define IDS_TIME 1152
#define IDM_HTML_EDITOR 1153
#define IDS_ERROR_MAPI_INIT_FAILED 1154
#define IDD_CAL_RECUR 1155
#define IDS_WEEKS 1156
-#define IDC_REMOVE_FILES 1157
+#define IDC_DEL 1157
#define IDC_SHOW_EXTRA 1158
#define IDC_IMAGE 1159
#define IDC_GUEST_ENTRY 1160
#define IDC_ADD_GUEST 1161
#define IDS_MISSING_PARENT 1162
#define IDS_ORIGINAL_MESSAGE 1163
#define IDS_PORTABLE_Q 1164
#define IDC_TABLE 1165
#define IDS_NO_EVENTS 1166
#define IDS_UNREAD_MAIL 1167
#define IDS_1168 1168
#define IDC_SHOW_ADDR 1169
#define IDS_AND_OPERATOR 1170
#define IDS_OR_OPERATOR 1171
#define IDS_MEMBER_OF_GROUP 1172
#define IDS_ACTION_MOVE_FOLDER 1173
#define IDS_ACTION_SOUND 1174
#define IDS_ACTION_OPEN 1175
#define IDS_ACTION_EXECUTE 1176
#define IDS_ACTION_SET_COLOUR 1177
#define IDS_ACTION_SET_LABEL 1178
#define IDS_ACTION_EMPTY_FOLDER 1179
#define IDS_ACTION_MARK_SPAM 1180
#define IDS_ACTION_SAVE_ATTACHMENTS 1181
#define IDS_ACTION_DELETE_ATTACHMENTS 1182
#define IDS_ACTION_CREATE_FOLDER 1183
#define IDS_SELECT_FOLDER 1184
#define IDC_BAYES_INCREMENTAL 1185
#define IDC_BAYES_DEBUG 1186
#define IDC_IMG_NOTES_TBL 1187
#define IDC_WORK_TABLE 1188
#define IDC_OUTGOING 1189
#define IDS_ERROR_FILE_DOESNT_EXIST 1190
#define IDS_ERROR_FOLDER_DOESNT_EXIST 1191
#define IDS_IMPORT 1192
#define IDD_SCRIBE_EXPORT 1193
#define IDC_INCOMING 1194
#define IDM_MENU_1195 1195
#define IDC_DEST 1196
#define IDC_SET_DEST 1197
#define IDC_HAS_GROUPS 1198
#define IDC_CAL_URL 1199
#define IDC_SPAM_FLD 1200
#define IDC_SET_SPAM 1201
#define IDC_SPAM_FOLDER 1202
#define IDC_SET_SPAM_FOLDER 1203
#define IDC_PASSWORD 1204
+#define IDS_ERROR_SERVER_CONNECT 1205
#define IDS_ERROR_PRINT_FAILED 1214
#define IDS_ASK_ACCOUNT_PASSWORD 1215
#define IDS_SELECT_IO 1216
#define IDC_GUESTS 1217
#define IDS_DONT_SHOW_AGAIN 1218
#define IDS_INSTALL 1219
#define IDC_1220 1220
#define IDS_EDIT_MAIL_STORES 1224
#define IDS_ERROR_CANT_CREATE_FOLDER 1225
#define IDS_ERROR_CANT_CREATE_DB 1226
#define IDS_SHOW_CONSOLE 1227
#define IDC_STATUS 1228
#define IDS_SELECT_ACCOUNT 1229
#define IDS_1235 1235
#define IDC_MONDAY 1238
#define IDC_TUESDAY 1239
#define IDS_MONTHS 1246
#define IDC_NEVER 1249
#define IDC_1250 1250
#define IDC_AFTER 1251
#define IDC_AFTER_COUNT 1252
#define IDC_1254 1254
#define IDC_ON 1255
#define IDC_ON_DATE 1256
#define IDC_SUMMARY 1258
#define IDS_ALWAYS_SHOW 1260
#define IDS_OPEN_SOURCE 1262
#define IDS_DEFAULT 1263
#define IDS_IMPORT_SETTINGS_Q 1264
#define IDS_IMPORT_SETTINGS_DONE 1265
#define IDS_DELETE_ATTACHMENTS 1266
#define IDS_LANGUAGE 1267
#define IDS_1268 1268
#define IDS_SHOW_REMOTE_CONTENT 1274
#define IDC_DELETE_DAYS 1275
#define IDS_RESIZE_IMGS 1277
#define IDD_REMOTE_CONTENT 1279
#define IDC_WHITELIST 1282
#define IDS_REMOTE_CONTENT_MSG 1287
#define IDS_GNUPG_ERR_SAVE_FAILED 1291
#define IDS_MESSAGES_SENT 1293
#define IDS_MAIL_MERGE_EMPTY 1294
#define IDS_GNUPG_ERR_NO_OUTPUT 1295
#define IDS_FILTER_MAILINGLIST 1299
#define IDC_ADVANCED 1300
#define IDS_ACTION_COPY 1301
#define IDC_PAGE_PARTIAL 1302
#define IDD_CSV_EXPORT 1304
#define IDS_1305 1305
#define IDS_EXPORT_MSG 1312
#define IDS_MARK_ALL_READ 1313
#define IDD_MANAGE_FOLDERS 1314
#define IDC_MS_TBL 1315
#define IDC_OPEN_MS 1316
#define IDC_CLOSE_MS 1317
#define IDC_CREATE_MS 1318
#define IDC_CONVERT_MS 1320
#define IDC_REPAIR_MS 1321
#define IDC_MAIL_STORES 1324
#define IDS_1325 1325
#define IDS_1326 1326
#define IDC_MSG_ID 1327
#define IDC_FILTER 1328
#define IDS_GNUPG_ERR_WRONG_SEGS 1329
#define IDS_RELATIVE_TIMES 1333
#define IDS_1335 1335
#define IDS_CHECKING_OBJECTS 1336
#define IDS_ERROR_FOLDER_NOT_LOADED 1337
#define IDS_1338 1338
#define IDS_1339 1339
#define IDS_1340 1340
#define IDS_1341 1341
#define IDD_REPLICATE 1342
-#define IDC_SRC 1346
-#define IDC_DST 1348
#define IDM_CHECK_UPDATE 1350
#define IDS_1353 1353
#define IDS_1354 1354
#define IDS_ERROR_FONT_SETTINGS 1356
#define IDC_SEC_AUTH 1357
#define IDC_EDIT_CONTROL 1359
#define IDC_DELETE_LARGER 1360
#define IDC_DELETE_SIZE 1361
#define IDC_CLEAR 1364
#define IDS_ADD_TO_CAL 1367
#define IDS_ADD_TO_DICTIONARY 1368
#define IDS_ERROR_CANT_ADD_DICT 1370
#define IDS_SPELL_CHECK 1371
#define IDS_SPELL_DICT 1372
#define IDS_MAILSTORE_UPGRADE_Q 1373
#define IDS_ERROR_IMPORT 1374
#define IDS_ERROR_REPLICATION_FAILED 1375
#define IDS_ERROR_IMPORT_COUNT 1388
#define IDS_GNUPG_ERR_NOT_INSTALLED 1458
#define IDD_FOLDER_SELECT 1469
#define IDS_REPARSE 1471
#define IDD_FOLDER_FORMAT 1472
#define IDS_GNUPG_PSW_PROMPT 1473
#define IDC_V1 1481
#define IDC_V2 1482
#define IDC_UP 1483
#define IDS_GROWL_SUPPORT 1485
#define IDS_GROWL 1486
#define IDC_MSG_DEL_ACTION 1487
#define IDC_MSG_DEL_NEXT 1488
#define IDC_MSG_DEL_CLOSE 1489
#define IDC_MSG_DEL_PREV 1490
#define IDC_RECEIVE_LOG 1514
#define IDS_EMAIL_PROGRESS 1516
#define IDS_COPY_LOG_TO_CLIP 1517
#define IDC_SMTP_PORT 1519
#define IDS_RESIZE_MAX_PX 1522
#define IDS_RESIZING 1523
#define IDS_RESIZE_MAX_SIZE 1524
#define IDS_UPGRADE_KEY 1525
#define IDC_UPGRADE 1526
#define IDS_MAIL2_DEPRECATED 1527
#define IDS_SSL_DEBUG 1528
#define IDS_CONSOLE 1529
#define IDC_SPELL_LANG 1531
#define IDC_WIDTH 1535
#define IDC_SLIDE 1537
#define IDC_BAYES_DELETE_ON_SERVER 1541
#define IDC_BAYES_DELETE_ATTACHMENTS 1543
#define IDC_TENTATIVE 1548
#define IDS_DELETING 1549
#define IDS_DONT_SHOW_EMOJI 1550
#define IDS_HELP 1551
#define IDS_DESKTOP 1552
#define IDS_PORTABLE 1553
#define IDC_BTNS_TABLE 1556
#define IDS_GNUPG_CHECKING 1558
#define IDS_GNUPG_ERR_NO_SIGN_ENC 1561
#define IDS_GNUPG_ERR_CANT_START 1565
#define IDS_GNUPG_ERR_CODE 1568
#define IDS_GNUPG_ERR_NO_CONTENT 1570
#define IDS_GNUPG_ERR_READ_FAIL 1572
#define IDS_GNUPG_ERR_NO_MAIL 1574
#define IDS_GNUPG_ERR_NO_ROOT 1578
#define IDS_GNUPG_ERR_WRONG_MIME 1580
#define IDS_GNUPG_ERR_NO_ENC_MIME 1581
#define IDS_GNUPG_ERR_NO_ID 1582
#define IDS_GNUPG_DECRYPT_CANCEL 1583
#define IDS_GNUPG_ERR_NO_DATA 1584
#define IDS_GNUPG_ERR_DECRYPT_FAIL 1585
#define IDS_GNUPG_ERR_NO_SENDER_EMAIL 1586
#define IDS_GNUPG_ERR_NO_PRIV_KEY 1587
#define IDS_GNUPG_ERR_NO_PUB_KEY 1588
#define IDS_GNUPG_ERR_IMPORT_FAIL 1589
#define IDS_GNUPG_ERR_SIGN_ENC_CANCEL 1590
#define IDS_GNUPG_ERR_TEMP_WRITE 1591
#define IDS_GNUPG_ERR_EXPORT_TEMP 1592
#define IDS_GNUPG_ERR_NEW_ATTACH_FAIL 1593
#define IDS_GNUPG_ERR_REPARENT 1594
#define IDS_GNUPG_ERR_RECIP_NO_EMAIL 1595
#define IDS_GNUPG_ERR_WRITE 1596
#define IDS_GNUPG_ERR_ENCRYPT_FAIL 1597
#define IDS_GNUPG_ERR_ONE_OR_MORE 1598
#define IDS_GNUPG_DECRYPT_OK 1599
#define IDS_GNUPG_ENCRYPTED 1600
#define IDS_GNUPG_ENCRYPTED_AND_SIGNED 1601
#define IDS_GNUPG_SIGNED 1602
#define IDS_GNUPG_GOOD_SIG 1603
#define IDS_GNUPG_BAD_SIG 1604
#define IDS_GNUPG_SIG_MSG 1605
#define IDS_GNUPG_SIG_NO_ID 1606
#define IDS_GNUPG_ERR_INVALID_DECRYPTION 1607
#define IDS_GNUPG_CHECK_MSG 1608
#define IDS_SHOW_SCRIPT_DEBUGGER 1609
#define IDC_ACC_LOG 1613
#define IDC_1615 1615
#define IDC_1616 1616
#define IDS_SHOW_SIZE_IN_KIB 1617
#define IDS_GNUPG_GET_GPG 1619
#define IDC_START_REL 1620
#define IDC_END_REL 1621
#define IDS_YESTERDAY 1622
#define IDC_1626 1626
#define IDS_MBOX_SELECT_FOLDER 2000
#define IDS_MBOX_READING 2001
#define IDS_MBOX_EXPORT 2002
#define IDS_MBOX_EXPORT_FOLDER 2003
#define IDS_MBOX_WRITING 2004
#define IDS_STORE2_ERROR_COMPACT 2005
#define IDS_STORE2_DISCARD_FIX 2006
#define IDS_STORE2_SAVE_DEBUG 2007
#define IDS_NAME 2008
#define IDS_NO_MAIL_TO_SEND 2009
#define IDS_NO_OUTGOING_FOLDER 2010
#define IDS_ERROR_NO_NAME_EMAIL 2011
#define IDS_APPNAME 2012
#define IDS_SURNAME 2013
#define IDC_SET_CALENDER 2014
#define IDC_NEW_MAIL_NOTIFY 2015
#define IDD_NEW_MAIL_NOTIFY 2016
#define IDC_NEW_MAIL 2017
#define IDC_REMEMBER_PSW 2020
#define IDS_36 2021
#define IDS_37 2022
#define IDC_OPEN 2023
#define IDC_GLYPH_SUB 2031
#define IDC_FILTERS 2032
#define IDD_OUTLOOK_IMPORT 2040
#define IDD_WARN_DEFAULT 2041
#define IDC_POP_RECIP 2042
#define IDD_ADDCONTACT 2043
#define IDD_CONTACT 2044
#define IDD_FOLDER_NAME 2045
#define IDD_POPVIEW 2047
#define IDC_FPR_NONE 3000
#define IDC_FPR_USER 3001
#define IDC_FPR_ADMIN 3002
#define IDC_FPW_NONE 3003
#define IDC_FPW_USER 3004
#define IDC_FPW_ADMIN 3005
#define IDM_BOUNCE 40000
#define IDM_SOFTWARE_KEY 40001
#define IDM_UPGRADE_MAIL_STORES 40002
#define IDM_OPTIONS 40004
#define IDM_PRINT 40005
#define IDM_PRINTSETUP 40006
#define IDM_EXIT 40007
#define IDM_NEW_EMAIL 40008
#define IDM_REPLY 40009
#define IDM_REPLY_ALL 40010
#define IDM_FORWARD 40011
#define IDM_SEND_MAIL 40012
#define IDM_NEW_CONTACT 40014
#define IDM_ABOUT 40019
#define IDM_IMPORT_NS_CONTACTS 40020
#define IDM_IMPORT_OUTLOOK_PAB 40025
#define IDM_IMPORT_OUTLOOK_ITEMS 40027
#define IDM_NEW_FILTER 40038
#define IDM_IMPORT_TEXT_MBOX 40047
#define IDM_EXPORT_TEXT_MBOX 40048
#define IDM_IMP_MBX_EMAIL 40049
#define IDM_IMP_DBX_EMAIL 40050
#define IDM_NEW_DRAFT 40060
#define IDM_NO_TEMPLATES 40061
#define IDM_FILTER_CURRENT_FOLDER 41010
#define IDM_IMP_EUDORA_ADDR 41043
#define IDM_IMP_MOZILLA_ADDR 41045
#define IDM_FIND 41055
#define IDM_IMPORT_CSV 41065
#define IDM_WORK_OFFLINE 41075
#define IDM_EXPORT_CSV 41085
#define IDM_NEW_GROUP 41095
#define IDM_SECURITY 41105
#define IDM_BAYES_SETTINGS 41116
#define IDM_BAYES_CHECK 41117
#define IDM_BUILD_BAYES_DB 41118
#define IDM_LOGOUT 41129
#define IDM_FOLDERS_DOWN_LEFT 41142
#define IDM_PREVIEW_ALONG_BOTTOM 41143
#define IDM_LAYOUT1 41144
#define IDM_LAYOUT2 41145
#define IDM_LAYOUT3 41146
#define IDM_EXPORT_OUTLOOK_ITEMS 41147
#define IDM_CRASH 41148
#define IDM_TUTORIALS 41149
#define IDM_DEBUG_INFO 41150
#define IDM_VERSION_HISTORY 41151
#define IDM_MEMECODE 41152
#define IDM_SET_READ 41155
#define IDM_IMP_MOZILLA_MAIL 41156
#define IDM_DEBUG_FILTERS 41157
#define IDM_FILTERS_DISABLE 41158
#define IDM_DUMP_MEM 41159
#define IDM_FILTER_SELECTION 41160
#define IDM_EXPORT_SCRIBE 41161
#define IDM_TOOLS_MENU 41163
#define IDM_REPLICATE 41164
#define IDM_EXPORT 41165
#define IDM_COPY 'copy'
#define IDM_CUT 'cut '
#define IDM_PASTE 'past'
diff --git a/src/ImpExp_Mbox.cpp b/src/ImpExp_Mbox.cpp
--- a/src/ImpExp_Mbox.cpp
+++ b/src/ImpExp_Mbox.cpp
@@ -1,258 +1,264 @@
#include
#include
#include
#include
#include "Scribe.h"
#include "lgi/common/NetTools.h"
#include "lgi/common/Edit.h"
#include "lgi/common/TextLabel.h"
#include "lgi/common/TextFile.h"
#include "resdefs.h"
#include "lgi/common/LgiRes.h"
#include "lgi/common/FileSelect.h"
///////////////////////////////////////////////////////////////////////////
-ChooseFolderDlg::ChooseFolderDlg
+ImportExportDlg::ImportExportDlg
(
ScribeWnd *parent,
bool IsExport,
const char *Title,
const char *Msg,
char *DefFolder,
int FolderType,
LString::Array *Files
)
{
Type = FolderType;
SetParent(App = parent);
- DestFolder = 0;
Export = IsExport;
- Lst = 0;
if (LoadFromResource(Export ? IDD_FILES_EXPORT : IDD_FILES_IMPORT))
{
Name(Title);
- if (GetViewById(IDC_FOLDER, Folder))
+ if (GetViewById(IDC_SRC, Src))
{
- Folder->Name(DefFolder ? DefFolder : (char*)"/");
- Folder->Enabled(false);
- }
-
- SetCtrlName(IDC_MSG, Msg);
-
- if (GetViewById(IDC_FILES, Lst))
- {
+ Src->ShowColumnHeader(false);
if (Files)
{
for (unsigned i=0; iLength(); i++)
InsertFile((*Files)[i]);
}
}
+
+ if (GetViewById(IDC_DEST, Dst))
+ {
+ Dst->Name(DefFolder ? DefFolder : (char*)"/");
+ Dst->Enabled(false);
+ }
+
+ SetCtrlName(IDC_MSG, Msg);
}
MoveToCenter();
}
-void ChooseFolderDlg::InsertFile(const char *f)
+void ImportExportDlg::InsertFile(const char *f)
{
- if (!Lst)
+ if (!Src)
return;
bool Has = false;
- for (auto n : *Lst)
+ for (auto n : *Src)
{
- char Path[MAX_PATH_LEN];
- LMakePath(Path, sizeof(Path), n->GetText(0), n->GetText(1));
- if (_stricmp(Path, f) == 0)
+ auto path = n->GetText(0);
+ if (Stricmp(path, f) == 0)
{
Has = true;
break;
}
}
if (Has)
return;
- LListItem *n = new LListItem;
+ LListItem *n = new LListItem(f);
if (!n)
return;
-
- auto parts = LString(f).RSplit(DIR_STR, 1);
- if (parts.Length() == 2)
- {
- n->SetText(parts[0], 0);
- n->SetText(parts[1], 1);
- Lst->Insert(n);
- }
- else delete n;
+ Src->Insert(n);
}
-int ChooseFolderDlg::OnNotify(LViewI *Ctrl, LNotification n)
+int ImportExportDlg::OnNotify(LViewI *Ctrl, LNotification n)
{
+ if (!Src || !DestFolder)
+ return 0;
+
switch (Ctrl->GetId())
{
- case IDC_FILES:
+ case IDC_ADD:
{
- if (Lst && n.Type == LNotifyDeleteKey)
+ if (Export)
+ {
+ // Pick scribe folders to export:
+ auto Dlg = new FolderDlg(this, App, Type);
+ Dlg->DoModal([this, Dlg](auto dlg, auto ctrlId)
+ {
+ if (ctrlId)
+ {
+ auto f = Dlg->Get();
+ if (f)
+ InsertFile(f);
+ }
+ delete dlg;
+ });
+ }
+ else
{
- List Sel;
- if (Lst->GetSelection(Sel))
+ // Pick external MBOX files to import:
+ auto s = new LFileSelect(this);
+
+ s->MultiSelect(true);
+ s->Type("All Files", LGI_ALL_FILES);
+ s->Type("MBOX Files", "*.mbx;*.mbox");
+ s->Type("Outlook Express Folders", "*.mbx;*.dbx");
+ s->Type("Mozilla Address Book", "*.mab");
+ s->Type("Eudora Address Book", "NNdbase.txt");
+ s->Open([this](auto dlg, auto status)
{
- Sel.DeleteObjects();
- }
+ if (status)
+ {
+ for (int i=0; iLength(); i++)
+ InsertFile((*dlg)[i]);
+ }
+ delete dlg;
+ });
}
break;
}
- case IDC_REMOVE_FILES:
+ case IDC_SRC:
{
- if (Lst)
+ if (n.Type == LNotifyDeleteKey)
{
List Sel;
- if (Lst->GetSelection(Sel))
- {
+ if (Src->GetSelection(Sel))
Sel.DeleteObjects();
- }
- }
+ }
+ break;
+ }
+ case IDC_DEL:
+ {
+ List Sel;
+ if (Src->GetSelection(Sel))
+ Sel.DeleteObjects();
break;
}
- case IDC_PICK_FILES:
+ case IDC_SET_DEST:
{
- if (!Lst)
- break;
-
- auto s = new LFileSelect(this);
-
- s->MultiSelect(!Export);
- s->Type("All Files", LGI_ALL_FILES);
- s->Type("MBOX Files", "*.mbx;*.mbox");
- s->Type("Outlook Express Folders", "*.mbx;*.dbx");
- s->Type("Mozilla Address Book", "*.mab");
- s->Type("Eudora Address Book", "NNdbase.txt");
- s->Open([this](auto dlg, auto status)
+ if (Export)
{
- if (status)
- {
- if (Export)
- Lst->Empty();
+ // Pick external folder for writing to:
+ auto s = new LFileSelect(this);
- for (int i=0; iLength(); i++)
- InsertFile((*dlg)[i]);
- }
- delete dlg;
- });
- break;
- }
- case IDC_SET_FOLDER:
- {
- if (!Folder)
- break;
-
- auto Dlg = new FolderDlg(this, App, Type);
- Dlg->DoModal([this, Dlg](auto dlg, auto ctrlId)
+ s->MultiSelect(!Export);
+ s->OpenFolder( [this](auto dlg, auto status)
+ {
+ if (status)
+ {
+ for (int i=0; iLength(); i++)
+ InsertFile((*dlg)[i]);
+ }
+ delete dlg;
+ });
+ }
+ else
{
- if (ctrlId)
+ // Pick scribe folder for writing to:
+ auto Dlg = new FolderDlg(this, App, Type);
+ Dlg->DoModal([this, Dlg](auto dlg, auto ctrlId)
{
- auto f = Dlg->Get();
- if (f)
- this->Folder->Name(f);
- }
- delete dlg;
- });
+ if (ctrlId)
+ {
+ auto f = Dlg->Get();
+ if (f && Dst)
+ Dst->Name(f);
+ }
+ delete dlg;
+ });
+ }
break;
}
case IDOK:
{
- if (Lst)
- {
- for (auto n : *Lst)
- {
- char Path[MAX_PATH_LEN];
- LMakePath(Path, sizeof(Path), n->GetText(0), n->GetText(1));
- SrcFiles.Add(Path);
- }
- }
-
- if (Folder)
- {
- DestFolder = NewStr(Folder->Name());
- }
+ for (auto n : *Src)
+ SrcFiles.Add(n->GetText());
+ DestFolder = Dst->Name();
+ IncSubFolders = GetCtrlValue(IDC_SUB_FOLDERS) > 0;
EndModal(1);
break;
}
case IDCANCEL:
{
EndModal(0);
break;
}
}
return 0;
}
///////////////////////////////////////////////////////////////////////////
void Import_UnixMBox(ScribeWnd *Parent)
{
ScribeFolder *Cur = Parent->GetCurrentFolder();
LString Path;
if (Cur)
Path = Cur->GetPath();
- auto Dlg = new ChooseFolderDlg(Parent, false, LLoadString(IDS_MBOX_IMPORT), LLoadString(IDS_MBOX_SELECT_FOLDER), Path);
- Dlg->DoModal([Dlg, Parent](auto dlg, auto ctrlId)
+ auto Dlg = new ImportExportDlg(Parent, false, LLoadString(IDS_MBOX_IMPORT), LLoadString(IDS_MBOX_SELECT_FOLDER), Path);
+ Dlg->DoModal([Dlg, Parent](auto dlg, auto ok)
{
- if (ctrlId && Dlg->DestFolder)
+ if (ok && Dlg->DestFolder)
{
- ScribeFolder *Folder = Parent->GetFolder(Dlg->DestFolder);
+ auto Folder = Parent->GetFolder(Dlg->DestFolder);
if (Folder)
{
for (auto File: Dlg->SrcFiles)
{
LAutoPtr F(new LTextFile);
if (F->Open(File, O_READ))
Folder->Import(Folder->AutoCast(F), sMimeMbox);
}
}
}
delete dlg;
});
}
void Export_UnixMBox(ScribeWnd *Parent)
{
ScribeFolder *Cur = Parent->GetCurrentFolder();
LString Path;
if (Cur)
Path = Cur->GetPath();
- auto Dlg = new ChooseFolderDlg(Parent,
+ auto Dlg = new ImportExportDlg(Parent,
true,
LLoadString(IDS_MBOX_EXPORT),
LLoadString(IDS_MBOX_EXPORT_FOLDER),
Path);
Dlg->DoModal([Dlg, Parent](auto dlg, auto ctrlId)
{
if (ctrlId && Dlg->DestFolder)
{
ScribeFolder *Folder = Parent->GetFolder(Dlg->DestFolder);
if (Folder)
{
for (auto File: Dlg->SrcFiles)
{
if (!LFileExists(File) ||
LgiMsg( Parent,
LLoadString(IDS_ERROR_FILE_EXISTS),
AppName,
MB_YESNO,
File.Get()) == IDYES)
{
LAutoPtr F(new LFile);
if (F->Open(File, O_WRITE))
Folder->Export(Folder->AutoCast(F), sMimeMbox);
}
}
}
}
delete dlg;
});
}
diff --git a/src/Imp_Eudora.cpp b/src/Imp_Eudora.cpp
--- a/src/Imp_Eudora.cpp
+++ b/src/Imp_Eudora.cpp
@@ -1,180 +1,180 @@
#include "Scribe.h"
#include "resdefs.h"
#include "lgi/common/LgiRes.h"
char EudoraPathKey[] = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\Eudora.exe";
bool ImportEudoraAddresss(ScribeWnd *App, ScribeFolder *Folder, char *File)
{
if (!App || !Folder || !File)
return false;
LFile f(File);
if (!f)
return false;
auto Text = f.Read();
if (!Text)
return false;
bool Status = false;
char *Alias = 0;
char *Address = 0;
auto L = Text.SplitDelimit("\r\n");
for (unsigned i=0; i 2)
{
if (_stricmp(S[0], "alias") == 0)
{
DeleteArray(Alias);
Alias = NewStr(S[1]);
DeleteArray(Address);
Address = NewStr(S[2]);
}
else if (_stricmp(S[0], "note") == 0)
{
if (Alias && Address)
{
Contact *c = new Contact(App);
if (c)
{
c->App = App;
// set basic information
c->Set(OPT_Nick, Alias);
c->Set(OPT_Email, Address);
// parse through all the tags..
char *n = 0;
for (char *s = strchr(L[i], '<'); s && *s; s = n)
{
char *Var = ++s;
n = strchr(Var, '>');
if (n)
{
*n++ = 0;
char *Val = strchr(Var, ':');
if (Val)
{
*Val++ = 0;
#define Map(From, To) else if (_stricmp(Var, From) == 0) c->Set(To, Val)
if (_stricmp(Var, "first") == 0)
{
c->Set(OPT_First, Val);
}
Map("last", OPT_Last);
Map("address", OPT_HomeStreet);
Map("city", OPT_HomeSuburb);
Map("state", OPT_HomeState);
Map("country", OPT_HomeCountry);
Map("zip", OPT_HomePostcode);
Map("phone", OPT_HomePhone);
Map("fax", OPT_HomeFax);
Map("mobile", OPT_HomeMobile);
Map("web", OPT_HomeWebPage);
// Map("title", );
Map("company", OPT_Company);
Map("address2", OPT_WorkStreet);
Map("city2", OPT_WorkSuburb);
Map("country2", OPT_WorkCountry);
Map("zip2", OPT_WorkPostcode);
Map("phone2", OPT_WorkPhone);
Map("fax2", OPT_WorkFax);
Map("web2", OPT_WorkWebPage);
else if (_stricmp(Var, "name") == 0)
{
char *Sp = strchr(Val, ' ');
if (Sp)
{
*Sp++ = 0;
c->Set(OPT_First, Val);
c->Set(OPT_Last, Sp);
}
else
{
c->Set(OPT_First, Val);
}
}
else if (_stricmp(Var, "otheremail") == 0)
{
}
}
}
char *Next = strchr(n, '<');
if (!Next && strlen(n) > 0)
{
c->Set(OPT_Note, n);
n = 0;
}
else n = Next;
}
Status |= (Folder->WriteThing(c, NULL) != Store3Error);
}
}
}
}
}
return Status;
}
void Import_EudoraAddressBook(ScribeWnd *App)
{
char Str[256] = "/";
#ifdef WIN32
HKEY hKey;
if (RegOpenKeyA(HKEY_LOCAL_MACHINE, EudoraPathKey, &hKey) == ERROR_SUCCESS)
{
DWORD Type = 0;
DWORD Size = sizeof(Str);
RegQueryValueExA(hKey, "Path", 0, &Type, (uchar*)Str, &Size);
RegCloseKey(hKey);
}
#endif
LString::Array Files;
LMakePath(Str, sizeof(Str), Str, "NNdbase.txt");
if (LFileExists(Str))
{
Files.Add(Str);
}
// Get default path..
char DefaultFolder[256] = "/Contacts";
ScribeFolder *f = App->GetCurrentFolder();
if (!f || f->GetItemType() != MAGIC_CONTACT)
{
f = App->GetFolder(FOLDER_CONTACTS);
}
if (f && f->GetItemType() == MAGIC_CONTACT)
{
auto p = f->GetPath();
if (p)
{
strcpy_s(DefaultFolder, sizeof(DefaultFolder), p);
}
}
// Ask user...
- auto Dlg = new ChooseFolderDlg(App,
+ auto Dlg = new ImportExportDlg(App,
false,
"Eudora",
LLoadString(IDS_SELECT_IO),
DefaultFolder,
MAGIC_CONTACT,
&Files);
Dlg->DoModal([App, Dlg](auto dlg, auto id)
{
if (id && Dlg->SrcFiles[0])
ImportEudoraAddresss(App, App->GetFolder(Dlg->DestFolder), Dlg->SrcFiles[0]);
delete dlg;
});
}
diff --git a/src/Imp_Mozilla.cpp b/src/Imp_Mozilla.cpp
--- a/src/Imp_Mozilla.cpp
+++ b/src/Imp_Mozilla.cpp
@@ -1,607 +1,607 @@
// #include
#include "Scribe.h"
#include "lgi/common/Map.h"
#include "resdefs.h"
#include "lgi/common/TextFile.h"
#include "lgi/common/LgiRes.h"
#include "lgi/common/FileSelect.h"
#include "v3.6.14/sqlite3.h"
void ToRecord(LMap &r, char *Str)
{
char *n;
for (char *s=strchr(Str, '('); s && *s; s=n)
{
n = strchr(s, ')');
if (n)
{
s++;
*n++ = 0;
if (*s++ == '^')
{
int a = htoi(s);
s = strchr(s, '^');
if (s)
{
int b = htoi(++s);
r[a] = b;
}
}
n = strchr(n, '(');
}
}
}
void ToMap(LMap &m, char *Str)
{
char *n;
for (char *s=strchr(Str, '('); s && *s; s=n)
{
n = strchr(s, ')');
if (n)
{
s++;
*n++ = 0;
char *e = strchr(s, '=');
if (e)
{
*e++ = 0;
int Var = htoi(s);
if (Var > 0)
{
m[Var] = e;
}
}
n = strchr(n, '(');
}
}
}
char *Decode(char *s)
{
// static const char *Cp = "iso-8859-1";
if (s && strchr(s, '$'))
{
char Hex[3] = {0, 0, 0};
LStringPipe p(4 << 10);
char *b = s;
while (*s)
{
if (*s == '$')
{
if (b < s)
{
p.Push(b, (int) (s - b));
}
s++;
if (s[0] && s[1])
{
Hex[0] = *s++;
Hex[1] = *s++;
char c = htoi(Hex);
p.Push(&c, 1);
/*
if (*s == '$')
{
s++;
if (s[0] && s[1])
{
Hex[0] = *s++;
Hex[1] = *s++;
char16 c = c1 | (htoi(Hex) << 8);
char *Utf8 = WideToUtf8(&c, sizeof(c));
if (Utf8)
{
p.Push(Utf8);
DeleteArray(Utf8);
}
}
}
*/
b = s;
}
else break;
}
else s++;
}
if (b < s)
{
p.Push(b, (int) (s - b));
}
return p.NewStr();
}
return NewStr(s);
}
struct Sqlite
{
bool Open;
sqlite3 *Db;
Sqlite(const char *File)
{
Open = Check(sqlite3_open(File, &Db));
}
~Sqlite()
{
if (Open)
sqlite3_close(Db);
}
bool Check(int Code, const char *Sql = NULL)
{
if (Code == SQLITE_OK ||
Code == SQLITE_DONE)
return true;
const char *Err = sqlite3_errmsg(Db);
LgiTrace("%s:%i - Sqlite error %i: %s\n%s",
_FL,
Code, Err,
Sql?Sql:(char*)"",
Sql?"\n":"");
LAssert(!"Db Error");
return false;
}
};
bool ImportMozillaAddresss(ScribeWnd *App, ScribeFolder *Folder, char *File)
{
if (!App || !Folder || !File)
{
LgiTrace("%s:%i - Param error.\n", _FL);
return false;
}
auto Ext = LGetExtension(File);
if (!Ext)
{
LgiTrace("%s:%i - No extension '%s'.\n", _FL, File);
return false;
}
bool Status = false;
if (!stricmp(Ext, "mab"))
{
char *Text = LReadTextFile(File);
if (Text)
{
int Angle = 0;
int Square = 0;
List Blocks;
List Records;
char *StartAngle = 0;
char *StartSquare = 0;
// Parse MAB...
for (char *s=Text; s && *s; s++)
{
if (*s == '<')
{
if (_strnicmp(s+1, "!--", 3) == 0)
{
char *e = strstr(s, "-->");
if (e) s = e + 3;
else break;
}
else
{
Angle++;
if (Angle == 1)
StartAngle = s + 1;
}
}
else if (*s == '>')
{
if (Angle > 0)
{
if (StartAngle && Angle == 1)
Blocks.Insert(NewStr(StartAngle, s - StartAngle));
Angle--;
}
}
else if (Angle == 0)
{
if (*s == '[')
{
if (++Square == 1)
StartSquare = s + 1;
}
else if (*s == ']')
{
if (StartSquare && Square == 1)
Records.Insert(NewStr(StartSquare, s - StartSquare));
Square--;
}
}
}
// Parse blocks..
bool Fields = true;
LMap Field;
LMap Data;
for (auto b: Blocks)
{
if (Fields)
{
ToMap(Field, b);
Fields = false;
}
else
{
ToMap(Data, b);
}
}
for (auto r: Records)
{
LMap Record;
ToRecord(Record, r);
int Flds = 0;
Contact *c = new Contact(App);
if (c)
{
c->App = App;
#define MapField(From, To) \
{ \
char *d = Data[Record[Field.Reverse((char*)From)]]; \
if (d) \
{ \
char *s = Decode(d); \
if (s) \
{ \
c->Set(To, s); \
Flds++; \
DeleteArray(s); \
} \
} \
}
MapField("FirstName", OPT_First);
MapField("LastName", OPT_Last);
MapField("NickName", OPT_Nick);
MapField("PrimaryEmail", OPT_Email);
MapField("WorkPhone", OPT_WorkPhone);
MapField("HomePhone", OPT_HomePhone);
MapField("FaxNumber", OPT_HomeFax);
MapField("CellularNumber", OPT_HomeMobile);
MapField("HomeAddress", OPT_HomeStreet);
MapField("HomeAddress2", OPT_HomeSuburb);
MapField("HomeState", OPT_HomeState);
MapField("HomeZipCode", OPT_HomePostcode);
MapField("HomeCountry", OPT_HomeCountry);
MapField("WorkAddress", OPT_WorkStreet);
MapField("WorkAddress2", OPT_WorkSuburb);
MapField("WorkState", OPT_WorkState);
MapField("WorkZipCode", OPT_WorkPostcode);
MapField("WorkCountry", OPT_WorkCountry);
MapField("Company", OPT_Company);
MapField("WebPage1", OPT_WorkWebPage);
MapField("WebPage2", OPT_HomeWebPage);
MapField("CustomFields", OPT_CustomFields);
MapField("Notes", OPT_Note);
// HomeCity
// WorkCity
// JobTitle
// Department
// BirthYear
// BirthMonth
// BirthDay
// LastModifiedDate
// RecordKey
// AddrCharSet
// LastRecordKey
// ListName
// ListNickName
// ListDescription
// ListTotalAddresses
// LowercaseListName
// SecondEmail
// PreferMailFormat
// PagerNumber
#undef MapField
if (Flds > 0)
{
Folder->WriteThing(c, NULL);
Status = true;
}
else
{
DeleteObj(c);
}
}
}
}
else
{
LgiMsg( App,
"Couldn't read from '%s'\n"
"Is Mozilla still open?",
AppName,
MB_OK,
File);
}
}
else if (!stricmp(Ext, "sqlite"))
{
Sqlite s(File);
if (!s.Open)
{
LgiTrace("%s:%i - Failed to open '%s'\n", _FL, File);
return false;
}
LString Sql = "select * from properties";
sqlite3_stmt *Stmt = NULL;
if (!s.Check(sqlite3_prepare_v2(s.Db, Sql, -1, &Stmt, 0), Sql))
return false;
LHashTbl, Contact*> Map;
int r;
while ((r = sqlite3_step(Stmt)) == SQLITE_ROW)
{
const char *card = (const char *)sqlite3_column_text(Stmt, 0);
auto name = sqlite3_column_text(Stmt, 1);
auto value = sqlite3_column_text(Stmt, 2);
Contact *c = Map.Find(card);
if (!c)
{
if ((c = new Contact(App)))
Map.Add(card, c);
}
if (!c || !name || !value)
continue;
printf("%s, %s, %s\n", card, name, value);
#define MapField(src, dst) \
if (!stricmp((const char*)name, src)) c->Set(dst, (char*)value)
MapField("FirstName", OPT_First);
MapField("LastName", OPT_Last);
MapField("NickName", OPT_Nick);
MapField("PrimaryEmail", OPT_Email);
MapField("WorkPhone", OPT_WorkPhone);
MapField("HomePhone", OPT_HomePhone);
MapField("FaxNumber", OPT_HomeFax);
MapField("CellularNumber", OPT_HomeMobile);
MapField("HomeAddress", OPT_HomeStreet);
MapField("HomeAddress2", OPT_HomeSuburb);
MapField("HomeState", OPT_HomeState);
MapField("HomeZipCode", OPT_HomePostcode);
MapField("HomeCountry", OPT_HomeCountry);
MapField("WorkAddress", OPT_WorkStreet);
MapField("WorkAddress2", OPT_WorkSuburb);
MapField("WorkState", OPT_WorkState);
MapField("WorkZipCode", OPT_WorkPostcode);
MapField("WorkCountry", OPT_WorkCountry);
MapField("Company", OPT_Company);
MapField("WebPage1", OPT_WorkWebPage);
MapField("WebPage2", OPT_HomeWebPage);
MapField("CustomFields", OPT_CustomFields);
MapField("Notes", OPT_Note);
#undef MapField
}
s.Check(sqlite3_finalize(Stmt), 0);
Status = Map.Length() > 0;
for (auto p: Map)
Folder->WriteThing(p.value, NULL);
}
else
{
LgiTrace("%s:%i - Unsupported address book format '%s'\n", _FL, File);
}
return Status;
}
void Import_MozillaAddressBook(ScribeWnd *App)
{
LArray FindFiles;
LArray Ext;
LFile::Path Str(
#ifdef LINUX
LSP_HOME
#else
LSP_USER_APP_DATA
#endif
);
#ifdef LINUX
Str += ".thunderbird";
#endif
Ext.Add("abook.mab");
Ext.Add("abook.sqlite");
LgiTrace("Searching '%s' for thunderbird address book files.\n", Str.GetFull().Get());
LRecursiveFileSearch(Str, &Ext, &FindFiles);
// Get default path..
char DefaultFolder[256] = "/Contacts";
ScribeFolder *f = App->GetCurrentFolder();
if (!f || f->GetItemType() != MAGIC_CONTACT)
{
f = App->GetFolder(FOLDER_CONTACTS);
}
if (f && f->GetItemType() == MAGIC_CONTACT)
{
auto p = f->GetPath();
if (p)
strcpy_s(DefaultFolder, sizeof(DefaultFolder), p);
}
LString::Array Files;
for (auto f: FindFiles)
Files.Add(f);
FindFiles.DeleteArrays();
// Ask user...
- auto Dlg = new ChooseFolderDlg( App,
+ auto Dlg = new ImportExportDlg( App,
false,
AppName,
"Select input files and destination directory",
DefaultFolder,
MAGIC_CONTACT,
&Files);
Dlg->DoModal([App, Dlg](auto dlg, auto id)
{
if (id && Dlg->SrcFiles[0])
ImportMozillaAddresss(App, App->GetFolder(Dlg->DestFolder), Dlg->SrcFiles[0]);
delete dlg;
});
}
#ifndef WIN32
int GetPrivateProfileStringA(const char *lpAppName,
const char *lpKeyName,
const char *lpDefault,
char *lpReturnedString,
int nSize,
const char *lpFileName)
{
// FIXME
LAssert(0);
return 0;
}
#endif
void Import_MozillaMail(ScribeWnd *App)
{
char Path[MAX_PATH_LEN];
if (!LGetSystemPath(LSP_USER_APP_DATA, Path, sizeof(Path)))
return;
LMakePath(Path, sizeof(Path), Path, "Thunderbird\\profiles.ini");
if (!LFileExists(Path))
{
LgiMsg(App, LLoadString(IDS_ERROR_FILE_DOESNT_EXIST), AppName, MB_OK, Path);
return;
}
char s[128];
if (GetPrivateProfileStringA("Profile0", "Path", "", s, sizeof(s), Path) <= 0)
return;
if (LIsRelativePath(s))
{
LTrimDir(Path);
LMakePath(Path, sizeof(Path), Path, s);
}
else
{
strcpy_s(Path, sizeof(Path), s);
}
LMakePath(Path, sizeof(Path), Path, "Mail");
if (!LDirExists(Path))
{
LgiMsg(App, LLoadString(IDS_ERROR_FOLDER_DOESNT_EXIST), AppName, MB_OK, Path);
return;
}
LArray FindFiles;
if (!LRecursiveFileSearch(Path, 0, &FindFiles))
return;
// Clear out index files...
for (unsigned i=0; iGetCurrentFolder();
LString CurPath;
if (Cur)
CurPath = Cur->GetPath();
- auto Dlg = new ChooseFolderDlg(App,
+ auto Dlg = new ImportExportDlg(App,
false,
"Mozilla/Thunderbird",
LLoadString(IDS_IMPORT),
CurPath,
MAGIC_MAIL,
&Files);
Dlg->DoModal([App, Dlg](auto dlg, auto id)
{
if (id && Dlg->DestFolder)
{
ScribeFolder *Dest = App->GetFolder(Dlg->DestFolder);
if (Dest)
{
for (auto Src: Dlg->SrcFiles)
{
char *Name = strrchr(Src, DIR_CHAR);
if (!Name) Name = Src;
else Name++;
ScribeFolder *Child = Dest->CreateSubDirectory(Name, MAGIC_MAIL);
if (Child)
{
LAutoPtr f(new LTextFile);
if (f->Open(Src, O_READ))
{
Child->Import(Child->AutoCast(f), sMimeMbox);
}
}
}
Dest->Expanded(true);
}
else LgiMsg(App,
LLoadString(IDS_ERROR_FOLDER_DOESNT_EXIST),
AppName,
MB_OK,
Dlg->DestFolder.Get());
}
delete dlg;
});
}
diff --git a/src/Imp_OutlookExpress.cpp b/src/Imp_OutlookExpress.cpp
--- a/src/Imp_OutlookExpress.cpp
+++ b/src/Imp_OutlookExpress.cpp
@@ -1,719 +1,719 @@
/*
** FILE: Imp_OutlookExpress.cpp
** AUTHOR: Matthew Allen
** DATE: 4/2/2000
** DESCRIPTION: Scribe importer
**
** Copyright (C) 2000, Matthew Allen
** fret@memecode.com
*/
#include "Scribe.h"
#include "resdefs.h"
#include "lgi/common/ProgressDlg.h"
#include "lgi/common/LgiRes.h"
#include "lgi/common/FileSelect.h"
bool ImportMBX(ScribeWnd *Parent, ScribeFolder *ParentFolder, char *FileName)
{
LFile F;
if (FileName &&
ParentFolder &&
Parent &&
F.Open(FileName, O_READ))
{
char Magic[4] = {'J', 'M', 'F', '6'};
char Buf[4];
if (F.Read(Buf, 4) == 4 &&
memcmp(Magic, Buf, 4) == 0)
{
int32 Msgs;
F.Seek(4, SEEK_CUR);
F >> Msgs;
// Create folder for these messages
char n[256];
strcpy_s(n, sizeof(n), FileName);
char *Ls = 0; // last slash
for (Ls = n+strlen(n)-1; Ls > n; Ls--)
{
if (*Ls == DIR_CHAR)
{
Ls++;
break;
}
}
char *Dot = strchr(Ls, '.');
if (Dot)
{
*Dot = 0;
}
ScribeFolder *Folder = ParentFolder->CreateSubDirectory(Ls, MAGIC_MAIL);
if (Folder)
{
// Skip unknown feilds
F.Seek(4 + 4 + 1 + 63, SEEK_CUR);
// Read the messages
for (int i=0; i> MsgId;
int MsgNum = 0;
F >> MsgNum;
int MsgTotalSize = 0;
F >> MsgTotalSize;
int MsgTextSize;
F >> MsgTextSize;
// Create item
Mail *m = dynamic_cast(Parent->CreateItem(MAGIC_MAIL, Folder, false));
if (m)
{
LMemStream Text(&F, 0, MsgTextSize);
// Decode email into fields
m->OnAfterReceive(&Text);
m->SetFlags(MAIL_RECEIVED |
MAIL_READ |
((m->HasAttachments()) ? MAIL_ATTACHMENTS : 0) );
}
// Seek to the next message
F.Seek(MsgStartPos+MsgTotalSize, SEEK_SET);
}
}
else
{
return false;
}
return true;
}
}
return false;
}
#ifdef WIN32
#pragma pack(push, 1)
#endif
class DbxMessagePtr
{
public:
uint MessagePos;
uint TablePos;
char Reserved[4];
};
// #define DbxTableSize 0x27c
class DbxTable
{
public:
// Header
/*
uint FilePos;
char Reserved1[13];
uint Msgs;
char Reserved2[3];
*/
uint FilePos;
char Reserved1[4];
uint ListPtr;
uint NextPtr;
char Reserved2[8];
};
class DbxMsgTable
{
public:
uint FilePos;
uint MessageLength;
char Reserved[17];
uint FirstBlockPos;
};
class DbxMsgBlock
{
public:
/*
uint FilePos;
uint Flags;
uint BlockSize;
uint NextBlockPos;
*/
uint FilePos;
uint Increase;
uint Include;
uint Next;
uint UseNet;
};
#ifdef WIN32
#pragma pack(pop)
#endif
class ImportDBX
{
private:
ScribeWnd *Parent = NULL;
ScribeFolder *ParentFolder = NULL;
LFile F;
uint64 FileSize = 0;
List Used;
class MsgInfo
{
public:
int Pos;
bool IsNews;
MsgInfo(int p, bool n)
{
Pos = p;
IsNews = n;
}
};
List MsgList;
bool IsUsed(ssize_t i)
{
for (auto n: Used)
{
if (*n == i) return true;
}
return false;
}
void SetUsed(ssize_t i)
{
if (!IsUsed(i))
{
Used.Insert(new ssize_t(i));
}
}
bool ReadMessage(ssize_t Pos, bool IsNews, ScribeFolder *Folder)
{
bool Status = false;
if (!IsUsed(Pos) &&
F.Seek(Pos, SEEK_SET))
{
LTempStream Text(ScribeTempPath());
SetUsed(Pos);
while (Pos > 0)
{
DbxMsgBlock Msg;
ZeroObj(Msg);
F.Read(&Msg, sizeof(Msg)-4);
if (Pos != Msg.FilePos)
{
break;
}
Pos += sizeof(Msg)-4;
// int Next = Pos + Msg.Next;
auto End = Pos + Msg.Include;
char Buf[1024];
while (Pos < End)
{
ssize_t R = F.Read(Buf, MIN(End-Pos, sizeof(Buf)));
Text.Write(Buf, R);
Pos += R;
}
F.Seek(Msg.Next, SEEK_SET);
Pos = Msg.Next;
}
Mail *m = new Mail(Parent);
if (m)
{
m->App = Parent;
m->OnAfterReceive(&Text);
m->SetFlags(MAIL_RECEIVED |
MAIL_READ |
// ((MsgTable.Reserved[3] == 3) ? MAIL_READ : 0) |
(m->GetAttachments(0) ? MAIL_ATTACHMENTS : 0));
m->SetDirty();
m->Save(Folder);
Status = true;
}
}
return Status;
}
bool ReadMsgHeader(int Pos)
{
bool Status = false;
if (!IsUsed(Pos) &&
F.Seek(Pos, SEEK_SET))
{
SetUsed(Pos);
DbxMsgBlock Msg;
F.Read(&Msg, sizeof(Msg));
if (Msg.FilePos == Pos)
{
Pos += sizeof(Msg);
int32 Self, MsgPos = 0, NewsPost = false, Count = 0;
do
{
F.Read(&Self, sizeof(Self));
if ((Self & 0xff) == 0x84)
{
if (!MsgPos)
{
MsgPos = Self >> 8;
}
}
if ((Self & 0xff) == 0x83)
{
NewsPost = true;
}
Count++;
}
while ((Self & 0x7f) > 0);
if (MsgPos)
{
MsgList.Insert(new MsgInfo(MsgPos, NewsPost != 0));
Status |= true;
}
else
{
F.Read(&Self, sizeof(Self));
F.Read(&MsgPos, sizeof(MsgPos));
MsgList.Insert(new MsgInfo(MsgPos, NewsPost != 0));
Status |= true;
}
}
}
return Status;
}
bool ReadTable(ssize_t Pos)
{
bool Status = false;
if (Pos > 0 &&
(uint64)Pos < FileSize &&
!IsUsed(Pos))
{
DbxTable Tbl;
F.Seek(Pos, SEEK_SET);
SetUsed(Pos);
F.Read(&Tbl, sizeof(Tbl));
if (Tbl.FilePos == Pos)
{
Pos += sizeof(Tbl);
Status |= ReadTable(Tbl.NextPtr);
Status |= ReadTable(Tbl.ListPtr);
F.Seek(Pos, SEEK_SET);
while (true)
{
bool Actioned = false;
DbxMessagePtr Node;
F.Seek(Pos, SEEK_SET);
Pos += F.Read(&Node, sizeof(Node));
if (Node.MessagePos > 0 &&
Node.MessagePos < FileSize &&
Node.MessagePos != Node.TablePos)
{
Actioned |= ReadMsgHeader(Node.MessagePos);
}
if (Node.TablePos > 0 &&
Node.TablePos < FileSize &&
Node.TablePos != Node.MessagePos)
{
Actioned |= ReadTable(Node.TablePos);
}
if (Actioned)
{
Status = true;
}
else break;
}
}
}
return Status;
}
void Clean()
{
Used.DeleteObjects();
MsgList.DeleteObjects();
}
public:
ImportDBX(ScribeWnd *parent)
{
Parent = parent;
ParentFolder = 0;
}
~ImportDBX()
{
Clean();
}
bool Import(ScribeFolder *parentFolder, char *FileName)
{
Clean();
ParentFolder = parentFolder;
if (sizeof(DbxTable) != 24)
{
// packing is screwed
LgiMsg(Parent, "Compiled object 'DbxTable' is the wrong size", AppName, MB_OK);
return false;
}
LProgressDlg Dlg(Parent);
Dlg.SetDescription("Reading tables...");
Dlg.SetRange(1);
LProgressPane *Import = Dlg.Push();
if (Import)
Import->SetDescription("Importing messages...");
if (Parent &&
ParentFolder &&
FileName &&
F.Open(FileName, O_READ))
{
FileSize = F.GetSize();
// Create folder for these messages
char n[256];
strcpy_s(n, sizeof(n), FileName);
char *Ls = strrchr(n, DIR_CHAR); // last slash
if (Ls)
{
Ls++;
char *Dot = strchr(Ls, '.');
if (Dot)
{
*Dot = 0;
}
// Make sure the path is unique, no overwriting previous folders
LStringPipe NewPath(256);
auto ParentPath = ParentFolder->GetPath();
NewPath.Print("/%s/%s", ParentPath.Get(), Ls);
auto BaseNewPath = NewPath.NewLStr();
LString NewPathStr = BaseNewPath.Get();
while (Parent->GetFolder(NewPathStr))
{
char *Num = NewPathStr.Get() + BaseNewPath.Length() - 1;
for (; Num>NewPathStr && IsDigit(*Num); Num--)
;
if (!IsDigit(*Num))
Num++;
int i = atoi(Num);
NewPath.Print("%s%i", BaseNewPath.Get(), i+1);
NewPathStr = NewPath.NewLStr();
}
// Create output folder
ScribeFolder *Folder = ParentFolder->CreateSubDirectory(Ls, MAGIC_MAIL);
if (Folder)
{
// Read header
F.Seek(0x30, SEEK_SET);
uint TableLoc;
F >> TableLoc;
if (!TableLoc) TableLoc = 0x1e254;
ReadTable(TableLoc);
// Import msg list
Dlg.Value(1);
if (Import)
{
Import->SetRange(MsgList.Length());
Import->SetType("Mail");
}
for (auto i: MsgList)
{
ReadMessage(i->Pos, i->IsNews, Folder);
if (Import)
Import->Value(Import->Value()+1);
}
/*
List Msgs;
while (Tbl)
{
int Pos = F.GetPosition();
F.Read(Tbl, DbxTableSize);
if (Tbl->FilePos == Pos)
{
for (int i=0; iMsgs; i++)
{
Msgs.Insert(new int(Tbl->Msg[i].MessagePos));
}
}
if (Tbl->Msgs < 76 ||
Tbl->FilePos != Pos)
{
DeleteArray(Tbl);
}
}
*/
// Read in messages
/*
for (int *MsgPos = Msgs.First(); MsgPos; MsgPos = Msgs.Next())
{
bool Read = true;
DbxMsgTable MsgTable;
F.Seek(*MsgPos, SEEK_SET);
F.Read(&MsgTable, sizeof(MsgTable));
if (MsgTable.FilePos == *MsgPos)
{
// Read in blocks
GBytePipe Msg;
DbxMsgBlock MsgBlock;
MsgBlock.NextBlockPos = MsgTable.FirstBlockPos;
while (MsgBlock.NextBlockPos)
{
int SeekTo = MsgBlock.NextBlockPos & 0xFFFFFF;
F.Seek(SeekTo, SEEK_SET);
F.Read(&MsgBlock, sizeof(MsgBlock));
if (MsgBlock.FilePos == SeekTo)
{
uchar *Data = new uchar[MsgBlock.BlockSize];
if (Data)
{
F.Read(Data, MsgBlock.BlockSize);
Msg.Push(Data, MsgBlock.BlockSize);
DeleteArray(Data);
}
}
}
// Msg contains email...
int Size = Msg.Sizeof();
if (Size > 0)
{
Mail *m = dynamic_cast(Parent->CreateItem(MAGIC_MAIL, Folder, false));
if (m)
{
m->Text = new char[Size+1];
if (m->Text)
{
// Read data into item
Msg.Pop((uchar*) m->Text, Size);
m->Text[Size] = 0;
// Decode email into fields
m->OnAfterReceive();
m->SetFlags(MAIL_RECEIVED |
((MsgTable.Reserved[3] == 3) ? MAIL_READ : 0) |
((m->Store->GetChild() != 0) ? MAIL_ATTACHMENTS : 0));
m->StoreDirty = true;
}
}
}
}
}
*/
return true;
}
}
}
return false;
}
};
struct ImportOe : public LProgressDlg
{
ScribeWnd *App;
bool v5;
int Imported = 0, TotalFiles = 0;
LString Dir;
LArray Files;
LArray Ext;
LString::Array FileArr;
ScribeFolder *Folder = NULL;
ImportOe(ScribeWnd *app, bool ver5) :
App(app),
v5(ver5),
LProgressDlg(app)
{
// Get the base directory
Dir = LGetSystemPath(LSP_LOCAL_APP_DATA);
if (!Dir)
// Just in case
Dir = LGetSystemPath(LSP_OS);
// Search for the folder files
Ext.Add(v5 ? "*.dbx" : "*.mbx");
DoFileSearch();
if (Files.Length() == 0)
{
if (LgiMsg( App,
"%i outlook express data files found in:\n"
"%s\n"
"Do you want to select a different directory to search?",
AppName,
MB_YESNO,
(int)Files.Length(),
Dir.Get()) == IDYES)
{
auto Select = new LFileSelect(App);
Select->OpenFolder([this](auto s, auto ok)
{
if (ok)
{
Dir = s->Name();
DoFileSearch();
ProcessFiles();
}
delete s;
});
}
}
else ProcessFiles();
}
void DoFileSearch()
{
LRecursiveFileSearch(Dir, &Ext, &Files);
}
void ProcessFiles()
{
if (Files.Length() == 0)
return;
// Strip files
for (unsigned i=0; iGetCurrentFolder();
LString CurrentPath;
if (Current)
CurrentPath = Current->GetPath();
- auto Dlg = new ChooseFolderDlg( App,
+ auto Dlg = new ImportExportDlg( App,
false,
AppName,
LLoadString(IDS_OE_IMPORT),
CurrentPath,
MAGIC_MAIL,
&FileArr);
Dlg->DoModal([this, Dlg](auto dlg, auto id)
{
LAutoPtr mem(dlg);
if (!id)
return;
ScribeFolder *Folder = App->GetFolder(Dlg->DestFolder);
if (!Folder)
{
LgiMsg(App, "Error locating that folder.", AppName, MB_OK);
return;
}
SetDescription("Importing folders...");
SetRange(FileArr.Length());
FileArr = Dlg->SrcFiles;
TotalFiles = (int)FileArr.Length();
SetPulse(100); // Start processing... each timeout allows the message loop to run a bit
});
};
void OnFinished()
{
LgiMsg( App,
"%i of %i %s files imported successfully.",
AppName,
MB_OK,
Imported,
TotalFiles,
(v5) ? (char*)"DBX" : (char*)"MBX");
delete this;
}
void OnPulse()
{
if (FileArr.Length())
{
auto FileName = FileArr.Last();
FileArr.PopLast();
if (v5)
{
ImportDBX Filter(App);
if (Filter.Import(Folder, FileName))
Imported++;
}
else
{
if (ImportMBX(App, Folder, FileName))
Imported++;
}
(*this)++; // inc progress bar...
}
else OnFinished();
}
};
void Import_OutlookExpress(ScribeWnd *Parent, bool v5)
{
new ImportOe(Parent, v5);
}
diff --git a/src/Scribe.h b/src/Scribe.h
--- a/src/Scribe.h
+++ b/src/Scribe.h
@@ -1,2582 +1,2583 @@
/*hdr
** FILE: Scribe.h
** AUTHOR: Matthew Allen
** DATE: 22/10/97
** DESCRIPTION: Scribe email application
**
** Copyright (C) 1998-2003 Matthew Allen
** fret@memecode.com
*/
// Includes
#include
#include
#include "lgi/common/Lgi.h"
#include "lgi/common/DragAndDrop.h"
#include "lgi/common/DateTime.h"
#include "lgi/common/Password.h"
#include "lgi/common/vCard-vCal.h"
#include "lgi/common/WordStore.h"
#include "lgi/common/SharedMemory.h"
#include "lgi/common/XmlTreeUi.h"
#include "lgi/common/Mime.h"
#include "lgi/common/OptionsFile.h"
#include "lgi/common/TextLog.h"
#include "lgi/common/Menu.h"
#include "lgi/common/ToolBar.h"
#include "lgi/common/Combo.h"
#include "lgi/common/Printer.h"
// Gui controls
#include "lgi/common/Panel.h"
#include "lgi/common/DocView.h"
#include "lgi/common/List.h"
#include "lgi/common/Tree.h"
#include "lgi/common/ListItemCheckBox.h"
// Storage
#include "lgi/common/Store3.h"
// App Includes
#include "ScribeInc.h"
#include "ScribeUtils.h"
#include "ScribeDefs.h"
#include "DomType.h"
class ListAddr;
// The field definition type
struct ItemFieldDef
{
const char *DisplayText;
ScribeDomType Dom;
LVariantType Type;
int FieldId; // Was 'Id'
int CtrlId;
const char *Option;
bool UtcConvert;
};
////////////////////////////////////////////////////////////////////////////////////////////
// Classes
class MailTree;
class LMailStore;
class ScribeWnd;
class Thing;
class Mail;
class Contact;
class ThingUi;
class MailUi;
class ScribeFolder;
class ContactUi;
class FolderPropertiesDlg;
class ScribeAccount;
class Filter;
class Attachment;
class Calendar;
class CalendarSource;
class AttachmentList;
struct ItemFieldDef;
class FolderDlg;
class Filter;
class ContactGroup;
class ScribeBehaviour;
class AccountletThread;
class ThingList;
class LSpellCheck;
////////////////////////////////////////////////////////////////////////
// Scripting support
#include "lgi/common/Scripting.h"
/// Script callback types. See 'api.html' in the Scripts folder for more details.
enum LScriptCallbackType
{
LCallbackNull,
LToolsMenu,
LThingContextMenu,
LThingUiToolbar,
LApplicationToolbar,
LMailOnBeforeSend, // "OnBeforeMailSend"
LMailOnAfterReceive,
LBeforeInstallBar,
LInstallComponent,
LFolderContextMenu,
LOnTimer,
LRenderMail,
LOnLoad
};
struct LScript;
struct LScriptCallback
{
LScriptCallbackType Type = LCallbackNull;
LScript *Script = NULL;
LFunctionInfo *Func = NULL;
int Param = 0;
double fParam = 0.0;
LVariant Data;
uint64 PrevTs = 0;
bool OnSecond = false;
};
struct LScript
{
LAutoPtr Code;
LArray Callbacks;
};
typedef void (*ConsoleClosingCallback)(class LScriptConsole *Console, void *user_data);
class LScriptConsole : public LWindow
{
ScribeWnd *App;
LTextLog *Txt;
ConsoleClosingCallback Callback;
void *CallbackData;
bool OnViewKey(LView *v, LKey &k);
public:
LScriptConsole(ScribeWnd *app, ConsoleClosingCallback callback, void *callback_data);
~LScriptConsole();
void Write(const char *s, int64 Len);
bool OnRequestClose(bool OsShuttingDown);
};
/// This class is a wrapper around a user interface element used for
/// Scripting. The script engine needs to be able to store information
/// pertaining to the menu item's callbacks along with the sub menu.
class LScriptUi : public LDom
{
public:
LScriptUi *Parent;
LSubMenu *Sub;
LToolBar *Toolbar;
LArray Callbacks;
LArray Subs;
LScriptUi()
{
Parent = 0;
Sub = 0;
Toolbar = 0;
}
LScriptUi(LSubMenu *s)
{
Parent = 0;
Toolbar = 0;
Sub = s;
}
LScriptUi(LToolBar *t)
{
Parent = 0;
Toolbar = t;
Sub = 0;
}
~LScriptUi()
{
Subs.DeleteObjects();
}
bool GetVariant(const char *Name, LVariant &Value, const char *Arr = NULL) override
{
if (Sub)
return Sub->GetVariant(Name, Value, Arr);
LDomProperty Method = LStringToDomProp(Name);
if (Method == ObjLength)
{
if (Toolbar)
Value = (int64)Toolbar->Length();
}
else return false;
return true;
}
bool CallMethod(const char *MethodName, LVariant *ReturnValue, LArray &Args) override
{
if (Sub)
return Sub->CallMethod(MethodName, ReturnValue, Args);
return false;
}
bool SetupCallbacks(ScribeWnd *App, ThingUi *Parent, Thing *t, LScriptCallbackType Type);
bool ExecuteCallbacks(ScribeWnd *App, ThingUi *Parent, Thing *t, int Cmd);
};
class LScribeScript : public LScriptContext
{
LScriptEngine *Eng;
struct LScribeScriptPriv *d;
public:
ScribeWnd *App;
static LScribeScript *Inst;
LScribeScript(ScribeWnd *app);
~LScribeScript();
void ShowScriptingWindow(bool show);
LAutoString GetDataFolder() override;
LStream *GetLog() override;
LHostFunc *GetCommands() override;
LString GetIncludeFile(const char *FileName) override;
// System
void SetEngine(LScriptEngine *eng);
bool MsgBox(LScriptArguments &Args);
// Paths
bool GetSystemPath(LScriptArguments &Args);
bool GetScribeTempPath(LScriptArguments &Args);
bool JoinPath(LScriptArguments &Args);
// Folders
bool GetFolder(LScriptArguments &Args);
bool GetSourceFolders(LScriptArguments &Args);
bool CreateSubFolder(LScriptArguments &Args);
bool LoadFolder(LScriptArguments &Args);
bool FolderSelect(LScriptArguments &Args);
bool BrowseFolder(LScriptArguments &Args);
// Things
bool CreateThing(LScriptArguments &Args);
bool MoveThing(LScriptArguments &Args);
bool SaveThing(LScriptArguments &Args);
bool DeleteThing(LScriptArguments &Args);
bool ShowThingWindow(LScriptArguments &Args);
bool FilterDoActions(LScriptArguments &Args);
bool LookupContact(LScriptArguments &Args);
// Callbacks
bool AddToolsMenuItem(LScriptArguments &Args);
bool AddCallback(LScriptArguments &Args);
// UI
bool MenuAddItem(LScriptArguments &Args);
bool MenuAddSubmenu(LScriptArguments &Args);
bool ToolbarAddItem(LScriptArguments &Args);
};
////////////////////////////////////////////////////////////////////////
class ScribePassword
{
class ScribePasswordPrivate *d;
public:
ScribePassword(LOptionsFile *p, const char *opt, int check, int pwd, int confirm);
~ScribePassword();
bool IsOk();
bool Load(LView *dlg);
bool Save();
void OnNotify(LViewI *c, LNotification &n);
};
-class ChooseFolderDlg : public LDialog
+class ImportExportDlg : public LDialog
{
- ScribeWnd *App;
- LEdit *Folder;
- int Type;
- bool Export;
- LList *Lst;
+ ScribeWnd *App = NULL;
+ int Type = 0;
+ bool Export = false;
+ LList *Src = NULL;
+ LEdit *Dst = NULL;
void InsertFile(const char *f);
public:
LString DestFolder;
LString::Array SrcFiles;
+ bool IncSubFolders = false;
- ChooseFolderDlg
+ ImportExportDlg
(
ScribeWnd *parent,
bool IsExport,
const char *Title,
const char *Msg,
char *DefFolder = NULL,
int FolderType = MAGIC_MAIL,
LString::Array *Files = NULL
);
int OnNotify(LViewI *Ctrl, LNotification n);
};
#define IoProgressImplArgs LAutoPtr stream, const char *mimeType, IoProgressCallback cb
#define IoProgressFnArgs IoProgressImplArgs = NULL
#define IoProgressError(err) \
{ \
IoProgress p(Store3Error, err); \
if (cb) cb(&p, stream); \
return p; \
}
#define IoProgressSuccess() \
{ \
IoProgress p(Store3Success); \
if (cb) cb(&p, stream); \
return p; \
}
#define IoProgressNotImpl() \
{ \
IoProgress p(Store3NotImpl); \
if (cb) cb(&p, stream); \
return p; \
}
class ScribeClass ThingType :
public LDom,
public LDataUserI
{
bool Dirty = false;
bool WillDirty = true;
bool Loaded = false;
protected:
bool OnError(const char *File, int Line)
{
_lgi_assert(false, "Object Missing", File, Line);
return false;
}
// Callbacks
struct ThingEventInfo
{
const char *File = NULL;
int Line = 0;
std::function Callback;
};
LArray OnLoadCallbacks;
public:
#ifdef _DEBUG
bool _debug = false;
#endif
struct IoProgress;
typedef std::function IoProgressCallback;
struct IoProgress
{
// This is the main result to look at:
// Store3NotImpl - typically means the mime type is wrong.
// Store3Error - an error occured.
// Store3Delayed - means the operation will take a long time.
// However progress is report via 'prog' if not NULL.
// And the 'onComplete' handler will be called at the end.
// Store3Success - the operation successfully completed.
Store3Status status = Store3NotImpl;
// Optional progress for the operation. Really only relevant for
// status == Store3Delayed.
Progress *prog = NULL;
// Optional error message for the operation. Relevant if
// status == Store3Error.
LString errMsg;
IoProgress(Store3Status s, const char *err = NULL)
{
status = s;
if (err)
errMsg = err;
}
operator bool()
{
return status > Store3Error;
}
};
template
LAutoPtr AutoCast(LAutoPtr ap)
{
return LAutoPtr(ap.Release());
}
static LArray DirtyThings;
ScribeWnd *App = NULL;
ThingType();
virtual ~ThingType();
virtual Store3ItemTypes Type() { return MAGIC_NONE; }
bool GetDirty() { return Dirty; }
virtual bool SetDirty(bool b = true);
void SetWillDirty(bool c) { WillDirty = c; }
virtual bool Save(ScribeFolder *Into) { return false; }
virtual void OnProperties(int Tab = -1) {}
virtual ScribeFolder *GetFolder() = 0;
virtual Store3Status SetFolder(ScribeFolder *f, int Param = -1) = 0;
virtual bool IsPlaceHolder() { return false; }
// Events
void WhenLoaded(const char *file, int line, std::function Callback, int index = -1);
bool IsLoaded(int Set = -1);
// Printing
virtual void OnPrintHeaders(struct ScribePrintContext &Context) { LAssert(!"Impl me."); }
virtual void OnPrintText(ScribePrintContext &Context, LPrintPageRanges &Pages) { LAssert(!"Impl me."); }
virtual int OnPrintHtml(ScribePrintContext &Context, LPrintPageRanges &Pages, LSurface *RenderedHtml) { LAssert(!"Impl me."); return 0; }
};
class MailContainerIter;
class ScribeClass MailContainer
{
friend class MailContainerIter;
List Iters;
public:
virtual ~MailContainer();
virtual size_t Length() { return 0; }
virtual ssize_t IndexOf(Mail *m) { return -1; }
virtual Mail *operator [](size_t i) { return NULL; }
};
class ScribeClass MailContainerIter
{
friend class MailContainer;
protected:
MailContainer *Container;
public:
MailContainerIter();
~MailContainerIter();
void SetContainer(MailContainer *c);
};
class ScribeClass ThingStorage // External storage information
{
public:
int Data;
ThingStorage() { Data = 0; }
virtual ~ThingStorage() {}
};
class ThingUi;
class ScribeClass Thing :
public ThingType,
public LListItem,
public LDragDropSource,
public LRefCount
{
friend class ScribeWnd;
friend class ScribeFolder;
ScribeFolder *_ParentFolder = NULL;
protected:
LArray FieldArray;
LAutoString DropFileName;
// This structure allows the app to move objects between
// mail stores. After a delayed write to the new mail store
// the old item needs to be removed. This keeps track of
// where that old item is. Don't assume that the Obj pointer
// is valid... check in the folder's items first.
struct ThingReference
{
LString Path;
Thing *Obj;
ThingReference()
{
Obj = NULL;
}
} DeleteOnAdd;
public:
ThingStorage *Data = NULL;
Thing(ScribeWnd *app, LDataI *object = 0);
~Thing();
// Dom
bool CallMethod(const char *MethodName, LVariant *ReturnValue, LArray &Args) override;
// D'n'd
bool GetData(LArray &Data) override;
bool GetFormats(LDragFormats &Formats) override;
bool OnBeginDrag(LMouse &m) override;
// Import / Export
virtual bool GetFormats(bool Export, LString::Array &MimeTypes) { return false; }
// Anything implementing 2 functions should mostly be using one of these
// to "return" an IoProgress and process any callback:
// IoProgressError(msg)
// IoProgressNotImpl()
// IoProgressSuccess()
virtual IoProgress Import(IoProgressFnArgs) = 0;
virtual IoProgress Export(IoProgressFnArgs) = 0;
/// This exports all the selected items
void ExportAll(LViewI *Parent, const char *ExportMimeType, std::function Callback);
// UI
bool OnKey(LKey &k) override;
// Thing
ScribeFolder *GetFolder() override { return _ParentFolder; }
void SetParentFolder(ScribeFolder *f);
Store3Status SetFolder(ScribeFolder *f, int Param = -1) override;
LDataI *DefaultObject(LDataI *arg = 0);
virtual ThingUi *DoUI(MailContainer *c = 0) { return NULL; }
virtual ThingUi *GetUI() { return 0; }
virtual bool SetUI(ThingUi *ui = 0) { return false; }
virtual uint32_t GetFlags() { return 0; }
virtual void OnCreate() override;
virtual bool OnDelete();
virtual int *GetDefaultFields() { return 0; }
virtual const char *GetFieldText(int Field) { return 0; }
virtual void DoContextMenu(LMouse &m, LView *Parent = 0) {}
virtual Thing &operator =(Thing &c) { LAssert(0); return *this; }
virtual char *GetDropFileName() = 0;
virtual bool GetDropFiles(LString::Array &Files) { return false; }
virtual void OnSerialize(bool Write) {}
virtual void Reparse();
// Interfaces
virtual Mail *IsMail() { return 0; }
virtual Contact *IsContact() { return 0; }
virtual ContactGroup *IsGroup() { return 0; }
virtual Filter *IsFilter() { return 0; }
virtual Attachment *IsAttachment() { return 0; }
virtual Calendar *IsCalendar() { return 0; }
void SetFieldArray(LArray &i) { FieldArray = i; }
void OnMove();
bool SetField(int Field, int n);
bool SetField(int Field, double n);
bool SetField(int Field, char *n);
bool SetField(int Field, LDateTime &n);
bool SetDateField(int Field, LVariant &v);
bool GetField(int Field, int &n);
bool GetField(int Field, double &n);
bool GetField(int Field, const char *&n);
bool GetField(int Field, LDateTime &n);
bool GetDateField(int Field, LVariant &v);
bool DeleteField(int Field);
};
class ThingUi : public LWindow
{
friend class MailUiGpg;
bool _Dirty;
char *_Name;
protected:
Thing *_Item;
bool _Running;
void SetItem(Thing *i) { _Item = i; }
public:
ScribeWnd *App;
static LArray All;
ThingUi(Thing *item, const char *name);
~ThingUi();
virtual bool SetDirty(bool d, bool ui = true);
bool IsDirty() { return _Dirty; }
bool OnRequestClose(bool OsShuttingDown);
bool OnViewKey(LView *v, LKey &k);
virtual void OnDirty(bool Dirty) {}
virtual void OnLoad() = 0;
virtual void OnSave() = 0;
virtual void OnChange() {}
virtual AttachmentList *GetAttachments() { return 0; }
virtual bool AddRecipient(AddressDescriptor *Addr) { return false; }
};
class ThingFilter
{
public:
virtual bool TestThing(Thing *Thing) = 0;
};
class ScribeClass Attachment : public Thing
{
friend class Mail;
protected:
Mail *Msg;
Mail *Owner;
bool IsResizing;
LString Buf;
// D'n'd
LAutoString DropSourceFile;
bool GetFormats(LDragFormats &Formats) override;
bool GetData(LArray &Data) override;
void _New(LDataI *object);
public:
enum Encoding
{
OCTET_STREAM,
PLAIN_TEXT,
BASE64,
QUOTED_PRINTABLE,
};
Attachment(ScribeWnd *App, Attachment *import = 0);
Attachment(ScribeWnd *App, LDataI *object, const char *import = 0);
~Attachment();
bool ImportFile(const char *FileName);
bool ImportStream(const char *FileName, const char *MimeType, LAutoStreamI Stream);
Thing &operator =(Thing &c) override;
LDATA_INT64_PROP(Size, FIELD_SIZE);
LDATA_STR_PROP(Name, FIELD_NAME);
LDATA_STR_PROP(MimeType, FIELD_MIME_TYPE);
LDATA_STR_PROP(ContentId, FIELD_CONTENT_ID);
LDATA_STR_PROP(Charset, FIELD_CHARSET);
LDATA_STR_PROP(InternetHeaders, FIELD_INTERNET_HEADER);
// LDom support
bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL) override;
bool CallMethod(const char *MethodName, LVariant *Ret, LArray &Args) override;
void OnOpen(LView *Parent, char *Dest = 0);
void OnDeleteAttachment(LView *Parent, bool Ask);
void OnSaveAs(LView *Parent);
void OnMouseClick(LMouse &m) override;
bool OnKey(LKey &k) override;
Store3ItemTypes Type() override { return MAGIC_ATTACHMENT; }
bool Get(char **ptr, ssize_t *size);
bool Set(char *ptr, ssize_t size);
bool Set(LAutoStreamI Stream);
Attachment *IsAttachment() override { return this; }
LAutoString MakeFileName();
bool GetIsResizing();
void SetIsResizing(bool b);
bool IsMailMessage();
bool IsVCalendar();
bool IsVCard();
// The owner is the mail that this is attached to
Mail *GetOwner() { return Owner; }
void SetOwner(Mail *msg);
// The msg is the mail that this message/rfc822 attachment is rendered into
Mail *GetMsg();
void SetMsg(Mail *m);
LStreamI *GotoObject(const char *file, int line);
IoProgress Import(IoProgressFnArgs) override { return Store3Error; }
IoProgress Export(IoProgressFnArgs) override { return Store3Error; }
bool SaveTo(char *FileName, bool Quite = false, LView *Parent = 0);
const char *GetText(int i) override;
char *GetDropFileName() override;
bool GetDropFiles(LString::Array &Files) override;
};
class ScribeClass Contact :
public Thing
{
friend class ContactUi;
protected:
class ContactPriv *d = NULL;
ContactUi *Ui = NULL;
public:
static List Everyone;
static Contact *LookupEmail(const char *Email);
static LHashTbl, int> PropMap;
static int DefaultContactFields[];
Contact(ScribeWnd *app, LDataI *object = 0);
~Contact();
LDATA_STR_PROP(First, FIELD_FIRST_NAME);
LDATA_STR_PROP(Last, FIELD_LAST_NAME);
LDATA_STR_PROP(Email, FIELD_EMAIL);
bool Get(const char *Opt, const char *&Value);
bool Set(const char *Opt, const char *Value);
bool Get(const char *Opt, int &Value);
bool Set(const char *Opt, int Value);
// operators
Thing &operator =(Thing &c) override;
Contact *IsContact() override { return this; }
// Dom
bool GetVariant(const char *Name, LVariant &Value, const char *Array = 0) override;
bool SetVariant(const char *Name, LVariant &Value, const char *Array = 0) override;
bool CallMethod(const char *MethodName, LVariant *ReturnValue, LArray &Args) override;
// Events
void OnMouseClick(LMouse &m) override;
// Printing
void OnPrintHeaders(struct ScribePrintContext &Context) override;
void OnPrintText(ScribePrintContext &Context, LPrintPageRanges &Pages) override;
// Misc
Store3ItemTypes Type() override { return MAGIC_CONTACT; }
ThingUi *DoUI(MailContainer *c = 0) override;
int Compare(LListItem *Arg, ssize_t Field) override;
bool IsAssociatedWith(char *PluginName);
char *GetLocalTime(const char *TimeZone = 0);
// Email address
int GetAddrCount();
LString::Array GetEmails();
LString GetAddrAt(int i);
bool HasEmail(LString email);
// Serialization
bool Save(ScribeFolder *Into = 0) override;
// ListItem
const char *GetText(int i) override;
int *GetDefaultFields() override;
const char *GetFieldText(int Field) override;
int GetImage(int Flags = 0) override { return ICON_CONTACT; }
// Import/Export
bool GetFormats(bool Export, LString::Array &MimeTypes) override;
IoProgress Import(IoProgressFnArgs) override;
IoProgress Export(IoProgressFnArgs) override;
char *GetDropFileName() override;
bool GetDropFiles(LString::Array &Files) override;
};
#define ContactGroupObj "ContactGroup"
#define ContactGroupName "Name"
#define ContactGroupList "List"
#define ContactGroupDateModified "DateModified"
extern ItemFieldDef GroupFieldDefs[];
class ContactGroup : public Thing
{
friend class GroupUi;
class GroupUi *Ui;
class ContactGroupPrivate *d;
LString DateCache;
public:
LDateTime UsedTs;
LDATA_STR_PROP(Name, FIELD_GROUP_NAME);
ContactGroup(ScribeWnd *app, LDataI *object = 0);
~ContactGroup();
// operators
Thing &operator =(Thing &c) override;
ContactGroup *IsGroup() override { return this; }
// Dom
bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL) override;
bool SetVariant(const char *Name, LVariant &Value, const char *Array = NULL) override;
bool CallMethod(const char *MethodName, LVariant *ReturnValue, LArray &Args) override;
// Events
void OnMouseClick(LMouse &m) override;
void OnSerialize(bool Write) override;
// Misc
Store3ItemTypes Type() override { return MAGIC_GROUP; }
ThingUi *DoUI(MailContainer *c = 0) override;
int Compare(LListItem *Arg, ssize_t Field) override;
bool GetAddresses(List &a);
LString::Array GetAddresses();
// Serialization
bool Save(ScribeFolder *Into = 0) override;
// ListItem
const char *GetText(int i) override;
int *GetDefaultFields() override;
const char *GetFieldText(int Field) override;
int GetImage(int Flags = 0) override { return ICON_CONTACT_GROUP; }
// Import / Export
char *GetDropFileName() override;
bool GetDropFiles(LString::Array &Files) override;
bool GetFormats(bool Export, LString::Array &MimeTypes) override;
IoProgress Import(IoProgressFnArgs) override;
IoProgress Export(IoProgressFnArgs) override;
};
struct LGroupMapArray : public LArray
{
LString toString()
{
LString::Array a;
for (auto i: *this)
a.Add(i->GetName());
return LString(",").Join(a);
}
};
class LGroupMap : public LHashTbl,LGroupMapArray*>
{
ScribeWnd *App;
void Index(ContactGroup *grp);
public:
LGroupMap(ScribeWnd *app);
~LGroupMap();
};
/////////////////////////////////////////////////////////////
// Mail threading
//
// See: http://www.jwz.org/doc/threading.html
struct ThingSortParams
{
int SortAscend;
int SortField;
};
class MContainer
{
int Lines;
int Depth;
bool Open;
bool Next;
void Dump(LStream &s, int Depth = 0);
public:
typedef LHashTbl, MContainer*> ContainerHash;
// Container data
Mail *Message;
MContainer *Parent;
LArray Children;
List Refs;
int Index;
// Cache data
LString::Array RefCache;
// Debug
#ifdef _DEBUG
LAutoString MsgId;
#endif
// Methods
MContainer(const char *Id, Mail *m = 0);
~MContainer();
void SetMail(Mail *m);
Mail *GetTop();
bool HasChild(MContainer *m);
void AddChild(MContainer *m);
void RemoveChild(MContainer *m);
int CountMessages();
void Pour(int &index, int depth, int tree, bool next, ThingSortParams *folder);
void OnPaint(LSurface *pDC, LRect &r, LItemColumn *c, LColour Fore, LColour Back, LFont *Font, const char *Txt);
static void Prune(int &ParentIndex, LArray &L);
static void Thread(List &In, LArray &Out);
};
extern int ListItemCompare(LListItem *a, LListItem *b, NativeInt Data);
extern int ContainerIndexer(Thing *a, Thing *b, NativeInt Data);
extern int GetFolderVersion(const char *Path);
extern bool CreateMailHeaders(ScribeWnd *App, LStream &Out, LDataI *Mail, MailProtocol *Protocol);
extern void Base36(char *Out, uint64 In);
////////////////////////////////////////////////////////////
// Thing sorting
// The old way
extern int ContainerCompare(MContainer **a, MContainer **b);
extern int ThingCompare(Thing *a, Thing *b, NativeInt Data);
// The new way
extern int ContainerSorter(MContainer *&a, MContainer *&b, ThingSortParams *Params);
extern int ThingSorter(Thing *a, Thing *b, ThingSortParams *Data);
/////////////////////////////////////////////////////////////
class MailViewOwner : public LCapabilityTarget
{
public:
virtual LDocView *GetDoc(const char *MimeType) = 0;
virtual bool SetDoc(LDocView *v, const char *MimeType) = 0;
};
/////////////////////////////////////////////////////////////
// The core mail object
struct MailPrintContext;
class ScribeClass Mail :
public Thing,
public MailContainer,
public LDefaultDocumentEnv
{
friend class MailUi;
friend class MailPropDlg;
friend class ScribeFolder;
friend class Attachment;
friend class ScribeWnd;
private:
class MailPrivate *d;
static LHashTbl,Mail*> MessageIdMap;
// List item preview
int PreviewCacheX;
List PreviewCache;
int64_t TotalSizeCache;
int64_t FlagsCache;
MailUi *Ui;
int Cursor; // Stores the cursor position in reply/forward format until the UI needs it
Attachment *ParentFile;
List Attachments;
Mail *PreviousMail; // the mail we are replying to / forwarding
void _New();
void _Delete();
bool _GetListItems(List &l, bool All); // All=false is just the selected items
void SetListRead(bool Read);
void SetFlagsCache(int64_t NewFlags, bool IgnoreReceipt, bool UpdateScreen);
// LDocumentEnv impl
List Actions;
bool OnNavigate(LDocView *Parent, const char *Uri) override;
bool AppendItems(LSubMenu *Menu, const char *Param, int Base = 1000) override;
bool OnMenu(LDocView *View, int Id, void *Context) override;
LoadType GetContent(LoadJob *&j) override;
public:
static bool PreviewLines;
static bool AdjustDateTz;
static int DefaultMailFields[];
static bool RunMailPipes;
static List NewMailLst;
constexpr static float MarkColourMix = 0.9f;
uint8_t SendAttempts;
enum NewEmailState
{
NewEmailNone,
NewEmailLoading,
NewEmailFilter,
NewEmailBayes,
NewEmailGrowl,
NewEmailTray
};
NewEmailState NewEmail;
Mail(ScribeWnd *app, LDataI *object = 0);
~Mail();
bool SetObject(LDataI *o, bool InDataDestuctor, const char *File, int Line) override;
LDATA_STR_PROP(Label, FIELD_LABEL);
LDATA_STR_PROP(FwdMsgId, FIELD_FWD_MSG_ID);
LDATA_STR_PROP(BounceMsgId, FIELD_BOUNCE_MSG_ID);
LDATA_STR_PROP(Subject, FIELD_SUBJECT);
LDATA_STR_PROP(Body, FIELD_TEXT);
LDATA_STR_PROP(BodyCharset, FIELD_CHARSET);
LDATA_STR_PROP(Html, FIELD_ALTERNATE_HTML);
LDATA_STR_PROP(HtmlCharset, FIELD_HTML_CHARSET);
LDATA_STR_PROP(InternetHeader, FIELD_INTERNET_HEADER);
LDATA_INT_TYPE_PROP(EmailPriority, Priority, FIELD_PRIORITY, MAIL_PRIORITY_NORMAL);
LDATA_INT32_PROP(AccountId, FIELD_ACCOUNT_ID);
LDATA_INT64_PROP(MarkColour, FIELD_COLOUR);
LDATA_STR_PROP(References, FIELD_REFERENCES);
LDATA_DATE_PROP(DateReceived, FIELD_DATE_RECEIVED);
LDATA_DATE_PROP(DateSent, FIELD_DATE_SENT);
LDATA_INT_TYPE_PROP(Store3State, Loaded, FIELD_LOADED, Store3Loaded);
LVariant GetServerUid();
bool SetServerUid(LVariant &v);
const char *GetFromStr(int id)
{
LDataPropI *From = GetObject() ? GetObject()->GetObj(FIELD_FROM) : 0;
return From ? From->GetStr(id) : NULL;
}
LDataPropI *GetFrom()
{
return GetObject() ? GetObject()->GetObj(FIELD_FROM) : 0;
}
LDataPropI *GetReply()
{
return GetObject() ? GetObject()->GetObj(FIELD_REPLY) : 0;
}
LDataIt GetTo()
{
return GetObject() ? GetObject()->GetList(FIELD_TO) : 0;
}
bool GetAttachmentObjs(LArray &Objs);
LDataI *GetFileAttachPoint();
// Operators
Mail *IsMail() override { return this; }
Thing &operator =(Thing &c) override;
OsView Handle();
ThingUi *GetUI() override;
bool SetUI(ThingUi *ui) override;
// References and ID's
MContainer *Container;
const char *GetMessageId(bool Create = false);
bool SetMessageId(const char *val);
LAutoString GetThreadIndex(int TruncateChars = 0);
static Mail *GetMailFromId(const char *Id);
bool MailMessageIdMap(bool Add = true);
bool GetReferences(LString::Array &Ids);
void GetThread(List &Thread);
LString GetMailRef();
bool ResizeImage(Attachment *a);
// MailContainer
size_t Length() override;
ssize_t IndexOf(Mail *m) override;
Mail *operator [](size_t i) override;
// Dom
bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL) override;
bool SetVariant(const char *Name, LVariant &Value, const char *Array = NULL) override;
bool CallMethod(const char *MethodName, LVariant *ReturnValue, LArray &Args) override;
// Events
void OnCreate() override;
bool OnBeforeSend(struct ScribeEnvelope *Out);
void OnAfterSend();
bool OnBeforeReceive();
bool OnAfterReceive(LStreamI *Msg);
void OnMouseClick(LMouse &m) override;
void OnProperties(int Tab = -1) override;
void OnInspect();
void OnReply(Mail *m, bool All, bool MarkOriginal);
bool OnForward(Mail *m, bool MarkOriginal, int WithAttachments = -1);
bool OnBounce(Mail *m, bool MarkOriginal, int WithAttachments = -1);
void OnReceipt(Mail *m);
int OnNotify(LViewI *Ctrl, LNotification n) override;
// Printing
void OnPrintHeaders(ScribePrintContext &Context) override;
void OnPrintText(ScribePrintContext &Context, LPrintPageRanges &Pages) override;
int OnPrintHtml(ScribePrintContext &Context, LPrintPageRanges &Pages, LSurface *RenderedHtml) override;
// Misc
uint32_t GetFlags() override;
void SetFlags(ulong i, bool IgnoreReceipt = false, bool Update = true);
void DeleteAsSpam(LView *View);
const char *GetFieldText(int Field) override;
LAutoString GetCharSet();
char *GetNewText(int Max = 64 << 10, const char *AsCp = "utf-8");
int *GetDefaultFields() override;
Store3ItemTypes Type() override { return MAGIC_MAIL; }
void DoContextMenu(LMouse &m, LView *Parent = 0) override;
int Compare(LListItem *Arg, ssize_t Field) override;
char *GetDropFileName() override;
bool GetDropFiles(LString::Array &Files) override;
LAutoString GetSig(bool HtmlVersion, ScribeAccount *Account = 0);
bool LoadFromFile(char *File);
void PrepSend();
void NewRecipient(char *Email, char *Name = 0);
void ClearCachedItems();
bool Send(bool Now);
void CreateMailHeaders();
bool AddCalendarEvent(LViewI *Parent, bool AddPopupReminder, LString *Msg);
LArray GetCalendarAttachments();
void Reparse() override;
// UI
LDocView *CreateView(MailViewOwner *Owner, LString MimeType, bool Sunken, size_t MaxBytes, bool NoEdit = false);
ThingUi *DoUI(MailContainer *c = 0) override;
// Alt HTML
bool HasAlternateHtml(Attachment **Attach = 0);
char *GetAlternateHtml(List *Refs = 0); // dynamically allocated ptr
bool WriteAlternateHtml(char *File = NULL, int FileLen = 0); // defaults to TEMP dir
// Account stuff
void ProcessTextForResponse(Mail *From, LOptionsFile *Options, ScribeAccount *Account);
void WrapAndQuote(LStringPipe &Pipe, const char *QuoteStr, int WrapAt = -1);
ScribeAccount *GetAccountSentTo();
// Access
int64 TotalSizeof();
bool Save(ScribeFolder *Into = 0) override;
// Attachments
Attachment *AttachFile(LView *Parent, const char *FileName);
bool AttachFile(Attachment *File);
bool DeleteAttachment(Attachment *File);
LArray GetAttachments();
bool GetAttachments(List *Attachments);
bool HasAttachments() { return Attachments.Length() > 0; }
bool UnloadAttachments();
// Import / Export
bool GetFormats(bool Export, LString::Array &MimeTypes) override;
IoProgress Import(IoProgressFnArgs) override;
IoProgress Export(IoProgressFnArgs) override;
// ListItem
void Update() override;
const char *GetText(int i) override;
int GetImage(int SelFlags = 0) override;
void OnMeasure(LPoint *Info) override;
void OnPaintColumn(LItem::ItemPaintCtx &Ctx, int i, LItemColumn *c) override;
void OnPaint(LItem::ItemPaintCtx &Ctx) override;
};
inline const char *toString(Mail::NewEmailState s)
{
#define _(s) case Mail::s: return #s;
switch (s)
{
_(NewEmailNone)
_(NewEmailLoading)
_(NewEmailFilter)
_(NewEmailBayes)
_(NewEmailGrowl)
_(NewEmailTray)
}
#undef _
LAssert(0);
return "#invalidNewEmailState";
}
// this is where the items reside
// and it forms a leaf on the mail box tree
// to the user it looks like a folder
class ScribeClass ScribeFolder :
public ThingType,
public LTreeItem,
public LDragDropSource,
public MailContainer
{
friend class MailTree;
friend class FolderPropertiesDlg;
friend class ThingList;
friend class ScribeWnd;
friend class MoveToState;
protected:
class ScribeFolderPriv *d;
ThingList *View();
LString DropFileName;
LString GetDropFileName();
// UI cache
LAutoString NameCache;
int ChildUnRead = 0;
LTreeItem *LoadOnDemand = NULL;
LAutoPtr Loading;
LArray FieldArray;
void SerializeFieldWidths(bool Write = false);
void EmptyFieldList();
void SetLoadFolder(Thing *t) { if (t) t->SetParentFolder(this); }
bool HasFieldId(int Id);
void ContinueLoading(int OldUnread, std::function Callback);
// Tree item stuff
void _PourText(LPoint &Size) override;
void _PaintText(LItem::ItemPaintCtx &Ctx) override;
int _UnreadChildren();
void UpdateOsUnread();
// Debugging state
enum FolderState
{
FldState_Idle,
FldState_Loading,
FldState_Populating,
} CurState = FldState_Idle;
public:
List Items;
ScribeFolder();
~ScribeFolder();
// Object
LArray &GetFieldArray() { return FieldArray; }
LDataFolderI *GetFldObj() { return dynamic_cast(GetObject()); }
bool SetObject(LDataI *o, bool InDataDestuctor, const char *File, int Line) override;
// ThingType
Store3ItemTypes Type() override { return GetObject() ? (Store3ItemTypes)GetObject()->Type() : MAGIC_NONE; }
ScribeFolder *GetFolder() override { return dynamic_cast(LTreeItem::GetParent()); }
ScribeFolder *GetChildFolder() { return dynamic_cast(LTreeItem::GetChild()); }
ScribeFolder *GetNextFolder() { return dynamic_cast(LTreeItem::GetNext()); }
Store3Status SetFolder(ScribeFolder *f, int Param = -1) override;
ScribeFolder *IsFolder() { return this; }
Store3Status CopyTo(ScribeFolder *NewParent, int NewIndex = -1);
// MailContainer
size_t Length() override;
ssize_t IndexOf(Mail *m) override;
Mail *operator [](size_t i) override;
/// Update the unread count
void OnUpdateUnRead
(
/// Increments the count, or zero if a child folder is changing.
int Offset,
/// Re-scan the folder
bool ScanItems
);
// Methods
LDATA_INT32_PROP(UnRead, FIELD_UNREAD);
LDATA_INT_TYPE_PROP(Store3ItemTypes, ItemType, FIELD_FOLDER_TYPE, MAGIC_MAIL);
LDATA_INT32_PROP(Open, FIELD_FOLDER_OPEN);
LDATA_INT32_PROP(SortIndex, FIELD_FOLDER_INDEX);
LDATA_INT64_PROP(Items, FIELD_FOLDER_ITEMS); // Cached item count
LDATA_INT_TYPE_PROP(ScribePerm, ReadAccess, FIELD_FOLDER_PERM_READ, PermRequireNone);
LDATA_INT_TYPE_PROP(ScribePerm, WriteAccess, FIELD_FOLDER_PERM_WRITE, PermRequireNone);
LDATA_ENUM_PROP(SystemFolderType, FIELD_SYSTEM_FOLDER, Store3SystemFolder);
void SetSort(int Col, bool Ascend, bool CanDirty = true);
int GetSortAscend() { return GetObject()->GetInt(FIELD_SORT) > 0; }
int GetSortCol() { return abs((int)GetObject()->GetInt(FIELD_SORT)) - 1; }
int GetSortField();
void ReSort();
bool Save(ScribeFolder *Into = 0) override;
bool ReindexField(int OldIndex, int NewIndex);
void CollectSubFolderMail(ScribeFolder *To = 0);
bool InsertThing(Thing *Item);
void MoveTo(LArray &Items, bool CopyOnly, std::function&)> Callback = NULL);
bool Delete(LArray &Items, bool ToTrash);
void SetDefaultFields(bool Force = false);
bool Thread();
ScribePerm GetFolderPerms(ScribeAccessType Access);
void SetFolderPerms(LView *Parent, ScribeAccessType Access, ScribePerm Perm, std::function Callback);
bool GetThreaded();
void SetThreaded(bool t);
// void Update();
void GetMessageById(const char *Id, std::function Callback);
void SetLoadOnDemand();
void SortSubfolders();
void DoContextMenu(LMouse &m);
void OnItemType();
bool IsInTrash();
bool SortItems();
///
/// These methods can be used in a synchronous or asynchronous manner:
/// sync: Call with 'Callback=NULL' and use the return value.
/// If the function needs to show a dialog (like to get permissions from
/// the user) then it'll return Store3Delayed immediately.
/// async: Call with a valid callback, and the method will possibly wait
/// for the user and then either return Store3Error or Store3Success.
Store3Status LoadThings(LViewI *Parent = NULL, std::function Callback = NULL);
Store3Status WriteThing(Thing *t, std::function Callback = NULL);
Store3Status DeleteThing(Thing *t, std::function Callback = NULL);
Store3Status DeleteAllThings( std::function Callback = NULL);
bool LoadFolders();
bool UnloadThings();
bool IsWriteable() { return true; }
bool IsPublicFolders() { return false; }
void OnProperties(int Tab = -1) override;
ScribeFolder *CreateSubDirectory(const char *Name, int Type);
void OnRename(char *NewName);
void OnDelete();
LString GetPath();
ScribeFolder *GetSubFolder(const char *Path);
void Populate(ThingList *List);
bool CanHaveSubFolders(Store3ItemTypes Type = MAGIC_MAIL) { return GetItemType() != MAGIC_ANY; }
void OnRethread();
// Name
void SetName(const char *Name, bool Encode);
LString GetName(bool Decode);
// Tree Item
const char *GetText(int i=0) override;
int GetImage(int Flags = 0) override;
void OnExpand(bool b) override;
bool OnKey(LKey &k) override;
void Update() override;
// Drag'n'drop
bool GetFormats(LDragFormats &Formats) override;
bool OnBeginDrag(LMouse &m) override;
void OnEndData() override;
bool GetData(LArray &Data) override;
void OnReceiveFiles(LArray &Files);
// Import/Export
bool GetFormats(bool Export, LString::Array &MimeTypes);
IoProgress Import(IoProgressFnArgs);
IoProgress Export(IoProgressFnArgs);
void ExportAsync(LAutoPtr f, const char *MimeType, std::function Callback = NULL);
const char *GetStorageMimeType();
// Dom
bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL) override;
bool SetVariant(const char *Name, LVariant &Value, const char *Array = NULL) override;
bool CallMethod(const char *MethodName, LVariant *ReturnValue, LArray &Args) override;
};
//////////////////////////////////////////////////////////////
class Filter;
class FilterCondition
{
protected:
bool TestData(Filter *F, LVariant &v, LStream *Log);
public:
// Data
LAutoString Source; // Data Source (used to be "int Field")
LAutoString Value; // Constant
char Op;
uint8_t Not;
// Methods
FilterCondition();
bool Set(class LXmlTag *t);
// Test condition against email
bool Test(Filter *F, Mail *m, LStream *Log);
FilterCondition &operator =(FilterCondition &c);
// Object
ThingUi *DoUI(MailContainer *c = 0);
};
class FilterAction : public LListItem, public LDataPropI
{
LCombo *TypeCbo;
LEdit *ArgEdit;
LButton *Btn;
public:
// Data
FilterActionTypes Type;
LString Arg1;
// Methods
FilterAction(LDataStoreI *Store);
~FilterAction();
bool Set(LXmlTag *t);
bool Get(LXmlTag *t);
bool Do(Filter *F, ScribeWnd *App, Mail *&m, LStream *log);
void Browse(ScribeWnd *App, LView *Parent);
void DescribeHtml(Filter *Flt, LStream &s);
LDataPropI &operator =(LDataPropI &p);
// List item
const char *GetText(int Col = 0);
void OnMeasure(LPoint *Info);
bool Select();
void Select(bool b);
void OnPaintColumn(LItem::ItemPaintCtx &Ctx, int i, LItemColumn *c);
int OnNotify(LViewI *c, LNotification n);
// Object
ThingUi *DoUI(MailContainer *c = 0);
// bool Serialize(ObjProperties &f, bool Write);
};
class ScribeClass Filter : public Thing
{
friend class FilterUi;
protected:
class FilterUi *Ui;
class FilterPrivate *d;
static int MaxIndex;
// Ui
LListItemCheckBox *ChkIncoming;
LListItemCheckBox *ChkOutgoing;
LListItemCheckBox *ChkInternal;
bool IgnoreCheckEvents;
// Current
Mail **Current;
// XML I/O
LAutoPtr ConditionsCache;
LAutoPtr Parse(bool Actions);
// Methods
bool EvaluateTree(LXmlTag *n, Mail *m, bool &Stop, LStream *Log);
bool EvaluateXml(Mail *m, bool &Stop, LStream *Log);
public:
Filter(ScribeWnd *app, LDataI *object = 0);
~Filter();
LDATA_STR_PROP(Name, FIELD_FILTER_NAME);
LDATA_STR_PROP(ConditionsXml, FIELD_FILTER_CONDITIONS_XML);
LDATA_STR_PROP(ActionsXml, FIELD_FILTER_ACTIONS_XML);
LDATA_STR_PROP(Script, FIELD_FILTER_SCRIPT);
LDATA_INT32_PROP(Index, FIELD_FILTER_INDEX);
LDATA_INT32_PROP(StopFiltering, FIELD_STOP_FILTERING);
LDATA_INT32_PROP(Incoming, FIELD_FILTER_INCOMING);
LDATA_INT32_PROP(Outgoing, FIELD_FILTER_OUTGOING);
LDATA_INT32_PROP(Internal, FIELD_FILTER_INTERNAL);
int Compare(LListItem *Arg, ssize_t Field) override;
Thing &operator =(Thing &c) override;
Filter *IsFilter() override { return this; }
static void Reindex(ScribeFolder *Folder);
int *GetDefaultFields() override;
const char *GetFieldText(int Field) override;
// Methods
void Empty();
LAutoString DescribeHtml();
// Import / Export
char *GetDropFileName() override;
bool GetDropFiles(LString::Array &Files) override;
bool GetFormats(bool Export, LString::Array &MimeTypes) override;
IoProgress Import(IoProgressFnArgs) override;
IoProgress Export(IoProgressFnArgs) override;
// Dom
bool Evaluate(char *s, LVariant &v);
bool SetVariant(const char *Name, LVariant &Value, const char *Array = NULL) override;
bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL) override;
bool CallMethod(const char *MethodName, LVariant *ReturnValue, LArray &Args) override;
// Filter
bool Test(Mail *m, bool &Stop, LStream *Log = 0);
bool DoActions(Mail *&m, bool &Stop, LStream *Log = 0);
Mail *GetCurrent() { return Current?*Current:0; }
/// This filters all the mail in 'Email'. Anything that is handled by a filter
/// is removed from the list, leaving just the unfiltered mail.
static int ApplyFilters
(
/// [In] The window of the filtering caller
LView *Parent,
/// [In] List of all the filter to test and/or apply
List &Filters,
/// [In/Out] The email to filter. After the call anything that has been
/// acted on by a filter will be removed from the list.
List &Email
);
// Object
Store3ItemTypes Type() override { return MAGIC_FILTER; }
ThingUi *DoUI(MailContainer *c = 0) override;
bool Serialize(LFile &f, bool Write);
bool Save(ScribeFolder *Into = 0) override;
void OnMouseClick(LMouse &m) override;
void AddAction(FilterAction *a);
// List item
const char *GetText(int i) override;
int GetImage(int Flags) override;
void OnPaint(ItemPaintCtx &Ctx) override;
void OnColumnNotify(int Col, int64 Data) override;
// Index
Filter *GetFilterAt(int Index);
};
//////////////////////////////////////////////////////////////////////
class Accountlet;
enum AccountThreadState
{
ThreadIdle,
ThreadSetup,
ThreadConnecting,
ThreadTransfer,
ThreadWaiting,
ThreadDeleting,
ThreadDone,
ThreadCancel,
ThreadError
};
enum ReceiveAction
{
MailNoop,
MailDelete,
MailDownloadAndDelete,
MailDownload,
MailUpload,
MailHeaders,
};
enum ReceiveStatus
{
MailReceivedNone,
MailReceivedWaiting, // This has been given to the main thread
MailReceivedOk, // and one of "Ok" or "Error" has to be
MailReceivedError, // set to continue.
MailReceivedMax,
};
ScribeFunc const char *AccountThreadStateName(AccountThreadState i);
ScribeFunc const char *ReceiveActionName(ReceiveAction i);
ScribeFunc const char *ReceiveStatusName(ReceiveStatus i);
class LScribeMime : public LMime
{
public:
LScribeMime() : LMime(ScribeTempPath())
{
}
};
class LMimeStream : public LTempStream, public LScribeMime
{
public:
LMimeStream();
bool Parse();
};
class MailTransferEvent
{
public:
// Message
LAutoPtr Rfc822Msg;
ReceiveAction Action = MailNoop;
ReceiveStatus Status = MailReceivedNone;
int Index = 0;
bool Explicit = false;
LString Uid;
int64 Size = 0;
int64 StartWait = 0;
// Header Listing
class AccountMessage *Msg = NULL;
LList *GetList();
// Sending
ScribeEnvelope *Send = NULL;
LString OutgoingHeaders;
// Other
class Accountlet *Account = NULL;
MailTransferEvent()
{
}
~MailTransferEvent()
{
#ifndef LGI_STATIC
// LStackTrace("%p::~MailTransferEvent\n", this);
#endif
}
};
#define RoProp(Type, Name) \
protected: \
Type Name; \
public: \
Type Get##Name() { return Name; }
class AccountThread : public LThread, public LCancel
{
protected:
Accountlet *Acc;
void OnAfterMain();
public:
// Object
AccountThread(Accountlet *acc);
~AccountThread();
// Api
Accountlet *GetAccountlet() { return Acc; }
// 1st phase
virtual void Disconnect();
// 2nd phase
virtual bool Kill();
};
#define AccStrOption(func, opt) \
LVariant func(const char *Set = 0) { LVariant v; StrOption(opt, v, Set); return v; }
#define AccIntOption(name, opt) \
int name(int Set = -1) { LVariant v; IntOption(opt, v, Set); return v.CastInt32(); }
class ScribeClass Accountlet : public LStream
{
friend class ScribeAccount;
friend class AccountletThread;
friend class AccountThread;
public:
struct AccountletPriv
{
LArray Log;
};
class AccountletLock
{
LMutex *l;
public:
bool Locked;
AccountletPriv *d;
AccountletLock(AccountletPriv *data, LMutex *lck, const char *file, int line)
{
d = data;
l = lck;
Locked = lck->Lock(file, line);
}
~AccountletLock()
{
if (Locked)
l->Unlock();
}
};
typedef LAutoPtr I;
private:
AccountletPriv d;
LMutex PrivLock;
protected:
// Data
ScribeAccount *Account;
LAutoPtr Thread;
bool ConnectionStatus;
MailProtocol *Client;
uint64 LastOnline;
LString TempPsw;
bool Quiet;
LView *Parent;
// Pointers
ScribeFolder *Root;
LDataStoreI *DataStore;
LMailStore *MailStore; // this memory is owned by ScribeWnd
// Options
const char *OptPassword;
// Members
LSocketI *CreateSocket(bool Sending, LCapabilityClient *Caps, bool RawLFCheck);
bool WaitForTransfers(List &Files);
void StrOption(const char *Opt, LVariant &v, const char *Set);
void IntOption(const char *Opt, LVariant &v, int Set);
// LStringPipe Line;
ssize_t Write(const void *buf, ssize_t size, int flags);
public:
MailProtocolProgress Group;
MailProtocolProgress Item;
Accountlet(ScribeAccount *a);
~Accountlet();
Accountlet &operator =(const Accountlet &a)
{
LAssert(0);
return *this;
}
I Lock(const char *File, int Line)
{
I a(new AccountletLock(&d, &PrivLock, File, Line));
if (!a->Locked)
a.Reset();
return a;
}
// Methods
bool Connect(LView *Parent, bool Quiet);
bool Lock();
void Unlock();
virtual bool IsConfigured() { return false; }
bool GetStatus() { return ConnectionStatus; }
uint64 GetLastOnline() { return LastOnline; }
ScribeAccount* GetAccount() { return Account; }
bool IsCancelled();
void IsCancelled(bool b);
ScribeWnd* GetApp();
const char* GetStateName();
ScribeFolder* GetRootFolder() { return Root; }
RoProp(AccountThreadState, State);
bool IsOnline()
{
if (DataStore)
return DataStore->GetInt(FIELD_IS_ONLINE) != 0;
return Thread != 0;
}
void OnEndSession()
{
if (DataStore)
DataStore->SetInt(FIELD_IS_ONLINE, false);
}
// Commands
void Disconnect();
void Kill();
// Data
char *OptionName(const char *Opt, char *Dest, int DestLen);
void Delete();
LThread *GetThread() { return Thread; }
LMailStore *GetMailStore() { return MailStore; }
LDataStoreI *GetDataStore() { return DataStore; }
// General options
virtual int UseSSL(int Set = -1) = 0;
AccStrOption(Name, OPT_AccountName);
AccIntOption(Disabled, OPT_AccountDisabled);
AccIntOption(Id, OPT_AccountUID);
AccIntOption(Expanded, OPT_AccountExpanded);
bool GetPassword(LPassword *p);
void SetPassword(LPassword *p);
bool IsCheckDialup();
// Events
void OnThreadDone();
void OnOnlineChange(bool Online);
virtual void OnBeforeDelete();
// LDom impl
bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL);
bool SetVariant(const char *Name, LVariant &Value, const char *Array = NULL);
// Virtuals
virtual void Main(AccountletThread *Thread) = 0;
virtual LVariant Server(const char *Set = 0) = 0;
virtual LVariant UserName(const char *Set = 0) = 0;
virtual void Enabled(bool b) = 0;
virtual void OnPulse(char *s, int s_len) {}
virtual bool IsReceive() { return false; }
virtual bool InitMenus() { return false; }
virtual void CreateMaps() = 0;
virtual ScribeAccountletStatusIcon GetStatusIcon() { return STATUS_ERROR; }
};
#undef RoProp
class AccountletThread;
class AccountIdentity : public Accountlet
{
public:
AccountIdentity(ScribeAccount *a);
AccStrOption(Name, OPT_AccIdentName);
AccStrOption(Email, OPT_AccIdentEmail);
AccStrOption(ReplyTo, OPT_AccIdentReply);
AccStrOption(TextSig, OPT_AccIdentTextSig);
AccStrOption(HtmlSig, OPT_AccIdentHtmlSig);
AccIntOption(Sort, OPT_AccountSort);
int UseSSL(int Set = -1) { return 0; }
void Main(AccountletThread *Thread) {}
LVariant Server(const char *Set = 0) { return LVariant(); }
LVariant UserName(const char *Set = 0) { return LVariant(); }
void Enabled(bool b) {}
void CreateMaps();
bool IsValid();
bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL);
bool SetVariant(const char *Name, LVariant &Value, const char *Array = NULL);
};
struct ScribeEnvelope
{
LString MsgId;
LString SourceFolder;
LString From;
LArray To;
LString References;
LString FwdMsgId;
LString BounceMsgId;
LString Rfc822;
};
class SendAccountlet : public Accountlet
{
friend class ScribeAccount;
friend class ScribeWnd;
LMenuItem *SendItem;
public:
LArray Outbox;
SendAccountlet(ScribeAccount *a);
~SendAccountlet();
// Methods
void Main(AccountletThread *Thread);
void Enabled(bool b);
bool InitMenus();
void CreateMaps();
ScribeAccountletStatusIcon GetStatusIcon();
// Sending options
AccStrOption(Server, OPT_SmtpServer);
AccIntOption(Port, OPT_SmtpPort);
AccStrOption(Domain, OPT_SmtpDomain);
AccStrOption(UserName, OPT_SmtpName);
AccIntOption(RequireAuthentication, OPT_SmtpAuth);
AccIntOption(AuthType, OPT_SmtpAuthType);
AccStrOption(PrefCharset1, OPT_SendCharset1);
AccStrOption(PrefCharset2, OPT_SendCharset2);
AccStrOption(HotFolder, OPT_SendHotFolder);
AccIntOption(OnlySendThroughThisAccount, OPT_OnlySendThroughThis);
/// Get/Set the SSL mode
/// \sa #SSL_NONE, #SSL_STARTTLS or #SSL_DIRECT
AccIntOption(UseSSL, OPT_SmtpSSL);
bool IsConfigured()
{
LVariant hot = HotFolder();
if (hot.Str())
{
bool Exists = LDirExists(hot.Str());
printf("%s:%i - '%s' exists = %i\n", _FL, hot.Str(), Exists);
return Exists;
}
return ValidStr(Server().Str());
}
bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL);
bool SetVariant(const char *Name, LVariant &Value, const char *Array = NULL);
};
typedef LHashTbl, LXmlTag*> MsgListHash;
class MsgList : protected MsgListHash
{
LOptionsFile *Opts;
LString Tag;
bool Loaded;
bool Load();
LXmlTag *LockId(const char *id, const char *file, int line);
void Unlock();
public:
typedef MsgListHash Parent;
bool Dirty;
MsgList(LOptionsFile *Opts, char *Tag);
~MsgList();
// Access methods
bool Add(const char *id);
bool Delete(const char *id);
int Length();
bool Find(const char *id);
void Empty();
LString::Array CopyKeys();
// Dates
bool SetDate(char *id, LDateTime *dt);
bool GetDate(char *id, LDateTime *dt);
};
class ReceiveAccountlet : public Accountlet
{
friend class ScribeAccount;
friend class ScribeWnd;
friend class ImapThread;
friend class ScpThread;
LArray Actions;
LList *Items;
int SecondsTillOnline;
LMenuItem *ReceiveItem;
LMenuItem *PreviewItem;
List *IdTemp;
LAutoPtr SettingStore;
LAutoPtr Msgs;
LAutoPtr Spam;
public:
ReceiveAccountlet(ScribeAccount *a);
~ReceiveAccountlet();
// Props
LList *GetItems() { return Items; }
bool SetItems(LList *l);
bool SetActions(LArray *a = NULL);
bool IsReceive() { return true; }
bool IsPersistant();
// Methods
void Main(AccountletThread *Thread);
void OnPulse(char *s, int s_len);
bool OnIdle();
void Enabled(bool b);
bool InitMenus();
int GetCheckTimeout();
void CreateMaps();
ScribeAccountletStatusIcon GetStatusIcon();
// Message list
bool HasMsg(const char *Id);
void AddMsg(const char *Id);
void RemoveMsg(const char *Id);
void RemoveAllMsgs();
int GetMsgs();
// Spam list
void DeleteAsSpam(const char *Id);
bool RemoveFromSpamIds(const char *Id);
bool IsSpamId(const char *Id, bool Delete = false);
// Receive options
AccStrOption(Protocol, OPT_Pop3Protocol);
ScribeProtocol ProtocolType() { return ProtocolStrToEnum(Protocol().Str()); }
AccStrOption(Server, OPT_Pop3Server);
AccIntOption(Port, OPT_Pop3Port);
AccStrOption(UserName, OPT_Pop3Name);
AccStrOption(DestinationFolder, OPT_Pop3Folder);
AccIntOption(AutoReceive, OPT_Pop3AutoReceive);
AccStrOption(CheckTimeout, OPT_Pop3CheckEvery);
AccIntOption(LeaveOnServer, OPT_Pop3LeaveOnServer);
AccIntOption(DeleteAfter, OPT_DeleteAfter);
AccIntOption(DeleteDays, OPT_DeleteDays);
AccIntOption(DeleteLarger, OPT_DeleteIfLarger);
AccIntOption(DeleteSize, OPT_DeleteIfLargerSize);
AccIntOption(DownloadLimit, OPT_MaxEmailSize);
AccStrOption(Assume8BitCharset, OPT_Receive8BitCs);
AccStrOption(AssumeAsciiCharset, OPT_ReceiveAsciiCs);
AccIntOption(AuthType, OPT_ReceiveAuthType);
AccStrOption(HotFolder, OPT_ReceiveHotFolder);
AccIntOption(SecureAuth, OPT_ReceiveSecAuth);
/// Get/Set the SSL mode
/// \sa #SSL_NONE, #SSL_STARTTLS or #SSL_DIRECT
AccIntOption(UseSSL, OPT_Pop3SSL);
bool IsConfigured()
{
LVariant hot = HotFolder();
if (hot.Str() && LDirExists(hot.Str()))
{
return true;
}
LVariant v = Server();
bool s = ValidStr(v.Str());
v = Protocol();
if (!v.Str() ||
_stricmp(v.Str(), PROTOCOL_POP_OVER_HTTP) != 0)
{
v = UserName();
s &= ValidStr(v.Str());
}
return s;
}
bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL);
bool SetVariant(const char *Name, LVariant &Value, const char *Array = NULL);
};
class ScribeAccount :
public LDom,
public LXmlTreeUi,
public LCapabilityClient
{
friend class ScribeWnd;
friend class ScribePopViewer;
friend class AccountStatusPanel;
friend class Accountlet;
friend class SendAccountlet;
friend class ReceiveAccountlet;
protected:
class ScribeAccountPrivate *d;
ScribeWnd *Parent;
ScribeFolder *&GetRoot();
void SetIndex(int i);
public:
// Data
AccountIdentity Identity;
SendAccountlet Send;
ReceiveAccountlet Receive;
LArray Views;
// Object
ScribeAccount(ScribeWnd *parent, int index);
~ScribeAccount();
// Lifespan
bool IsValid();
bool Create();
bool Delete();
// Properties
ScribeWnd *GetApp() { return Parent; }
int GetIndex();
bool IsOnline();
void SetCheck(bool c);
LMenuItem *GetMenuItem();
void SetMenuItem(LMenuItem *i);
void OnEndSession()
{
Send.OnEndSession();
Receive.OnEndSession();
}
// Commands
void Stop();
bool Disconnect();
void Kill();
void SetDefaults();
// User interface
void InitUI(LView *Parent, int Tab, std::function callback);
bool InitMenus();
void SerializeUi(LView *Wnd, bool Load);
int OnNotify(LViewI *Ctrl, LNotification &n);
// Worker
void OnPulse(char *s = NULL, int s_len = 0);
void ReIndex(int i);
void CreateMaps();
// LDom interface
bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL) override;
bool SetVariant(const char *Name, LVariant &Value, const char *Array = NULL) override;
bool CallMethod(const char *MethodName, LVariant *ReturnValue, LArray &Args) override;
};
//////////////////////////////////////////////////////////////////////
class ScribeClass ScribeDom : public LDom
{
ScribeWnd *App;
public:
Mail *Email;
Contact *Con;
ContactGroup *Grp;
Calendar *Cal;
Filter *Fil;
ScribeDom(ScribeWnd *a);
bool GetVariant(const char *Name, LVariant &Value, const char *Array = 0);
};
//////////////////////////////////////////////////////////////////////
#include "BayesianFilter.h"
#include "Components.h"
class LMailStore
{
public:
bool Default, Expanded;
LString Name;
LString Path;
LDataStoreI *Store;
ScribeFolder *Root;
LMailStore()
{
Expanded = true;
Default = false;
Store = NULL;
Root = NULL;
}
bool IsOk()
{
return Store != NULL &&
Root != NULL;
}
int Priority()
{
int Ver = Store ? (int)Store->GetInt(FIELD_VERSION) : 0;
return Ver;
}
void Empty()
{
DeleteObj(Store);
Name.Empty();
Path.Empty();
Root = NULL;
}
LMailStore &operator =(LMailStore &a)
{
LAssert(0);
return *this;
}
};
struct OptionsInfo
{
LString File;
char *Leaf;
int Score;
uint64 Mod;
bool Usual;
OptionsInfo();
OptionsInfo &operator =(char *p);
LAutoPtr Load();
};
class ScribeClass ScribeWnd :
public LWindow,
public LDom,
public LDataEventsI,
public BayesianFilter,
public CapabilityInstaller,
public LCapabilityTarget
{
friend class ScribeAccount;
friend class Accountlet;
friend class SendAccountlet;
friend class ReceiveAccountlet;
friend class AccountStatusPanel;
friend class ScribeFolder;
friend class OptionsDlg;
friend class LoadWordStoreThread;
friend struct ScribeReplicator;
friend struct LoadMailStoreState;
friend struct UnitTestState;
public:
enum LayoutMode
{
OptionsLayout = 0,
///--------------------
/// | |
/// Folders | List |
/// | |
/// |---------|
/// | Preview |
///--------------------
FoldersListAndPreview = 1,
///-----------------
/// | |
/// Folders | List |
/// | |
///-----------------
/// Preview |
///-----------------
PreviewOnBottom,
///-----------------
/// | |
/// Folders | List |
/// | |
///-----------------
FoldersAndList,
///---------------------------
/// | | |
/// Folders | List | Preview |
/// | | |
///---------------------------
ThreeColumn,
};
enum AppState
{
ScribeConstructing, // In Construct1 + Construct2
ScribeConstructed, // Finished Construct2 and ready for Construct3
ScribeInitializing, // In Construct3
ScribeRunning,
ScribeExiting,
ScribeLoadingFolders,
ScribeUnloadingFolders,
};
AppState GetScribeState()
{
return ScribeState;
}
protected:
class ScribeWndPrivate *d = NULL;
LTrayIcon TrayIcon;
// Ipc
LSharedMemory *ScribeIpc = NULL;
class ScribeIpcInstance *ThisInst = NULL;
bool ShutdownIpc();
// Accounts
List Accounts;
// New Mail stuff
class LNewMailDlg *NewMailDlg = NULL;
static AppState ScribeState;
DoEvery Ticker;
int64 LastDrop = 0;
// Static
LSubMenu *File = NULL;
LSubMenu *ContactsMenu = NULL;
LSubMenu *Edit = NULL;
LSubMenu *Help = NULL;
LToolBar *Commands = NULL;
// Dynamic
LSubMenu *IdentityMenu = NULL;
LMenuItem *DefaultIdentityItem = NULL;
LSubMenu *MailMenu = NULL;
LSubMenu *SendMenu = NULL, *ReceiveMenu = NULL, *PreviewMenu = NULL;
LMenuItem *SendItem = NULL, *ReceiveItem = NULL, *PreviewItem = NULL;
LSubMenu *NewTemplateMenu = NULL;
LMenuItem *WorkOffline = NULL;
// Commands
LCommand CmdSend;
LCommand CmdReceive;
LCommand CmdPreview;
// Storage
LArray Folders;
LArray FolderTasks;
List PostValidateFree;
// Main view
LAutoPtr ImageList;
LAutoPtr ToolbarImgs;
class LBox *Splitter = NULL;
ThingList *MailList = NULL;
class DynamicHtml *TitlePage = NULL;
class LSearchView *SearchView = NULL;
MailTree *Tree = NULL;
class LPreviewPanel *PreviewPanel = NULL;
class AccountStatusPanel *StatusPanel = NULL;
// Security
ScribePerm CurrentAuthLevel = PermRequireNone;
// Methods
void SetupUi();
void SetupAccounts();
int AdjustAllObjectSizes(LDataI *Item);
bool CleanFolders(ScribeFolder *f);
void LoadFolders(std::function Callback);
void LoadMailStores(std::function Callback);
bool ProcessFolder(LDataStoreI *&Store, int StoreIdx, char *StoreName);
bool UnLoadFolders();
void AddFolderToMru(char *FileName);
void AddContactsToMenu(LSubMenu *Menu);
bool FindWordDb(char *Out, int OutSize, char *Name);
void OnFolderChanged(LDataFolderI *folder);
bool ValidateFolder(LMailStore *s, int Id);
void GrowlOnMail(Mail *m);
void GrowlInfo(LString title, LString text);
bool OnTransfer();
ScribeDomType StrToDom(const char *Var) { return ::StrToDom(Var); }
const char* DomToStr(ScribeDomType p) { return ::DomToStr(p); }
void LoadImageResources();
void DoOnTimer(LScriptCallback *c);
public:
ScribeWnd();
void Construct1();
void Construct2();
void Construct3();
void SetLanguage();
~ScribeWnd();
const char *GetClass() override { return "ScribeWnd"; }
void DoDebug(char *s);
void Validate(LMailStore *s);
// Unit testing.
static bool IsUnitTest;
#ifdef _DEBUG
void UnitTests(std::function Callback);
#endif
// Dom
bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL) override;
bool CallMethod(const char *MethodName, LVariant *ReturnValue, LArray &Args) override;
// ---------------------------------------------------------------------
// Methods
LAutoString GetDataFolder();
LDataStoreI *CreateDataStore(const char *Full, bool CreateIfMissing);
Thing *CreateThingOfType(Store3ItemTypes Type, LDataI *obj = 0);
Thing *CreateItem(int Type, ScribeFolder *Folder = 0, bool Ui = true);
Mail *CreateMail(Contact *c = 0, const char *Email = 0, const char *Name = 0);
Mail *LookupMailRef(const char *MsgRef, bool TraceAllUids = false);
bool CreateFolders(LAutoString &FileName);
bool CompactFolders(LMailStore &Store, bool Interactive = true);
void Send(int Which = -1, bool Quiet = false);
void Receive(int Which);
void Preview(int Which);
void OnBeforeConnect(ScribeAccount *Account, bool Receive);
void OnAfterConnect(ScribeAccount *Account, bool Receive);
bool NeedsCapability(const char *Name, const char *Param = NULL) override;
void OnInstall(CapsHash *Caps, bool Status) override;
void OnCloseInstaller() override;
bool HasFolderTasks() { return FolderTasks.Length() > 0; }
int GetEventHandle();
void Update(int What = 0);
void UpdateUnRead(ScribeFolder *Folder, int Delta);
void ThingPrint(std::function Callback, ThingType *m, LPrinter *Info = NULL, LView *Parent = NULL, int MaxPage = -1);
bool OpenAMail(ScribeFolder *Folder);
void BuildDynMenus();
LDocView *CreateTextControl(int Id, const char *MimeType, bool Editor, Mail *m = 0);
void SetLastDrop() { LastDrop = LCurrentTime(); }
void SetListPane(LView *v);
void SetLayout(LayoutMode Mode = OptionsLayout);
bool IsMyEmail(const char *Email);
bool SetItemPreview(LView *v);
LOptionsFile::PortableType GetPortableType();
ScribeRemoteContent RemoteContent_GetSenderStatus(const char *Addr);
void RemoteContent_ClearCache();
void RemoteContent_AddSender(const char *Addr, bool WhiteList);
void SetDefaultHandler();
void OnSetDefaultHandler(bool Error, bool OldAssert);
void SetCurrentIdentity(int i=-1);
int GetCurrentIdentity();
bool MailReplyTo(Mail *m, bool All = false);
bool MailForward(Mail *m);
bool MailBounce(Mail *m);
void MailMerge(LArray &Recip, const char *FileName, Mail *Source);
void OnNewMail(List *NewMailObjs, bool Add = true);
void OnNewMailSound();
void OnCommandLine();
void OnTrayClick(LMouse &m) override;
void OnTrayMenu(LSubMenu &m) override;
void OnTrayMenuResult(int MenuId) override;
void OnFolderSelect(ScribeFolder *f);
void AddThingSrc(ScribeFolder *src);
void RemoveThingSrc(ScribeFolder *src);
LArray GetThingSources(Store3ItemTypes Type);
bool GetContacts(List &Contacts, ScribeFolder *f = 0, bool Deep = true);
List *GetEveryone();
void HashContacts(LHashTbl,Contact*> &Contacts, ScribeFolder *Folder = 0, bool Deep = true);
// CapabilityInstaller impl
LAutoString GetHttpProxy() override;
InstallProgress *StartAction(MissingCapsBar *Bar, LCapabilityTarget::CapsHash *Components, const char *Action) override;
class HttpImageThread *GetImageLoader();
int GetMaxPages();
ScribeWnd::LayoutMode GetEffectiveLayoutMode();
ThingList *GetMailList() { return MailList; }
// Gets the matching mail store for a given identity
LMailStore *GetMailStoreForIdentity
(
/// If this is NULL, assume the current identity
const char *IdEmail = NULL
);
ScribeFolder *GetFolder(int Id, LMailStore *s = NULL, bool Quiet = false);
ScribeFolder *GetFolder(int Id, LDataI *s);
ScribeFolder *GetFolder(const char *Name, LMailStore *s = NULL);
ScribeFolder *GetCurrentFolder();
int GetFolderType(ScribeFolder *f);
LImageList *GetIconImgList() { return ImageList; }
LAutoPtr &GetToolbarImgList() { return ToolbarImgs; }
LString GetResourceFile(SribeResourceType Type);
DoEvery *GetTicker() { return &Ticker; }
ScribeAccount *GetSendAccount();
ScribeAccount *GetCurrentAccount();
ThingFilter *GetThingFilter();
List *GetAccounts() { return &Accounts; }
ScribeAccount *GetAccountById(int Id);
ScribeAccount *GetAccountByEmail(const char *Email);
LPrinter *GetPrinter();
int GetActiveThreads();
int GetToolbarHeight();
void GetFilters(List &Filters, bool JustIn, bool JustOut, bool JustInternal);
bool OnFolderTask(LEventTargetI *Ptr, bool Add);
LArray &GetStorageFolders() { return Folders; }
LMailStore *GetDefaultMailStore();
LMailStore *GetMailStoreForPath(const char *Path);
bool OnMailStore(LMailStore **MailStore, bool Add);
ThingList *GetItemList() { return MailList; }
LColour GetColour(int i);
LMutex *GetLock();
LFont *GetPreviewFont();
LFont *GetBoldFont() { return LSysBold; }
LToolBar *LoadToolbar(LViewI *Parent, const char *File, LAutoPtr &Img);
class LVmCallback *GetDebuggerCallback();
class GpgConnector *GetGpgConnector();
void GetUserInput(LView *Parent, LString Msg, bool Password, std::function Callback);
int GetCalendarSources(LArray &Sources);
Store3Status GetAccessLevel(LViewI *Parent, ScribePerm Required, const char *ResourceName, std::function Callback);
void GetAccountSettingsAccess(LViewI *Parent, ScribeAccessType AccessType, std::function Callback);
const char* EditCtrlMimeType();
LAutoString GetReplyXml(const char *MimeType);
LAutoString GetForwardXml(const char *MimeType);
bool GetHelpFilesPath(char *Path, int PathSize);
bool LaunchHelp(const char *File);
LAutoString ProcessSig(Mail *m, char *Xml, const char *MimeType);
LString ProcessReplyForwardTemplate(Mail *m, Mail *r, char *Xml, int &Cursor, const char *MimeType);
bool LogFilterActivity();
ScribeFolder *FindContainer(LDataFolderI *f);
bool SaveDirtyObjects(int TimeLimitMs = 100);
class LSpellCheck *CreateSpellObject();
class LSpellCheck *GetSpellThread(bool OverrideOpt = false);
bool SetSpellThreadParams(LSpellCheck *Thread);
void OnSpellerSettingChange();
bool OnMailTransferEvent(MailTransferEvent *e);
LViewI *GetView() { return this; }
char *GetUiTags();
struct MailStoreUpgradeParams
{
LAutoString OldFolders;
LAutoString NewFolders;
bool Quiet;
LStream *Log;
MailStoreUpgradeParams()
{
Quiet = false;
Log = 0;
}
};
// Scripting support
bool GetScriptCallbacks(LScriptCallbackType Type, LArray &Callbacks);
LScriptCallback GetCallback(const char *CallbackMethodName);
bool RegisterCallback(LScriptCallbackType Type, LScriptArguments &Args);
LStream *ShowScriptingConsole();
bool ExecuteScriptCallback(LScriptCallback &c, LScriptArguments &Args, bool ReturnArgs = false);
LScriptEngine *GetScriptEngine();
// Options
LOptionsFile *GetOptions(bool Create = false) override;
bool ScanForOptionsFiles(LArray &Inf, LSystemPath PathType);
bool LoadOptions();
bool SaveOptions();
bool IsSending() { return false; }
bool ShowToolbarText();
bool IsValid();
// Data events from storage back ends
bool GetSystemPath(int Folder, LVariant &Path) override;
void OnNew(LDataFolderI *parent, LArray &new_items, int pos, bool is_new) override;
bool OnDelete(LDataFolderI *parent, LArray &items) override;
bool OnMove(LDataFolderI *new_parent, LDataFolderI *old_parent, LArray &Items) override;
void SetContext(const char *file, int line) override;
bool OnChange(LArray &items, int FieldHint) override;
void Post(LDataStoreI *store, void *Param) override { PostEvent(M_STORAGE_EVENT, store->Id, (LMessage::Param)Param); }
void OnPropChange(LDataStoreI *store, int Prop, LVariantType Type) override;
bool Match(LDataStoreI *store, LDataPropI *Addr, int Type, LArray &Matches) override;
ContactGroup *FindGroup(char *Name);
bool AddStore3EventHandler(LDataEventsI *callback);
bool RemoveStore3EventHandler(LDataEventsI *callback);
// ---------------------------------------------------------------------
// Events
void OnDelete();
int OnNotify(LViewI *Ctrl, LNotification n) override;
void OnPaint(LSurface *pDC) override;
LMessage::Result OnEvent(LMessage *Msg) override;
int OnCommand(int Cmd, int Event, OsView Handle) override;
void OnPulse() override;
void OnPulseSecond();
bool OnRequestClose(bool OsShuttingDown) override;
void OnSelect(List *l = 0, bool ChangeEvent = false);
void OnReceiveFiles(LArray &Files) override;
void OnUrl(const char *Url) override;
void OnZoom(LWindowZoom Action) override;
void OnCreate() override;
void OnMinute();
void OnHour();
bool OnIdle();
void OnBayesAnalyse(const char *Msg, const char *WhiteListEmail) override;
/// \returns true if spam
bool OnBayesResult(const char *MailRef, double Rating) override;
/// \returns true if spam
bool OnBayesResult(Mail *m, double Rating);
void OnScriptCompileError(const char *Source, Filter *f);
};
////////////////////////////////////////////////////////////////////////////////////
#ifdef SCRIBE_APP
#include "ScribePrivate.h"
#endif
diff --git a/src/ScribeFolder.cpp b/src/ScribeFolder.cpp
--- a/src/ScribeFolder.cpp
+++ b/src/ScribeFolder.cpp
@@ -1,4548 +1,4550 @@
/*
** FILE: ScribeFolder.cpp
** AUTHOR: Matthew Allen
** DATE: 17/1/2000
** DESCRIPTION: Scribe folder's
**
** Copyright (C) 2000-2002, Matthew Allen
** fret@memecode.com
*/
// Includes
#include "Scribe.h"
#include "lgi/common/DropFiles.h"
#include "lgi/common/ProgressDlg.h"
#include "lgi/common/QuickSort.h"
#include "lgi/common/DisplayString.h"
#include "lgi/common/TextFile.h"
#include "lgi/common/LgiRes.h"
#include "lgi/common/FileSelect.h"
#include "lgi/common/ClipBoard.h"
#include "Calendar.h"
#include "resdefs.h"
#include "ReplicateDlg.h"
#include "FolderTask.h"
//////////////////////////////////////////////////////////////////////////////
#if WINNATIVE
#include "lgi/common/Com.h"
#endif
class LDndFilePromise
#if defined(WINDOWS)
#elif defined(__GTK_H__)
#elif defined(MAC)
#endif
{
LStream *Src;
#if WINNATIVE
FILEGROUPDESCRIPTOR Gd;
#elif defined(__GTK_H__)
#elif defined(MAC)
#endif
public:
LDndFilePromise(LDragData &dd, LStream *src, LString FileName)
{
Src = src;
#if WINNATIVE
ZeroObj(Gd);
Gd.cItems = 1;
// CLSID Fd.clsid;
// SIZEL Fd.sizel;
// POINTL Fd.pointl;
// FILETIME Fd.ftCreationTime;
// FILETIME Fd.ftLastAccessTime;
// FILETIME Fd.ftLastWriteTime;
FILEDESCRIPTOR &Fd = Gd.fgd[0];
Fd.dwFlags = FD_ATTRIBUTES | FD_PROGRESSUI;
Fd.dwFileAttributes = FILE_ATTRIBUTE_COMPRESSED | FILE_ATTRIBUTE_NORMAL;
int64 Sz = Src->GetSize();
if (Sz >= 0)
{
Fd.dwFlags |= FD_FILESIZE;
Fd.nFileSizeHigh = Sz >> 32;
Fd.nFileSizeLow = Sz & 0xffffffff;
}
auto Leaf = FileName.RFind(DIR_STR);
LAutoWString FnW(Utf8ToWide(Leaf >= 0 ? FileName(Leaf+1,-1) : FileName));
Strcpy(Fd.cFileName, CountOf(Fd.cFileName), FnW.Get());
LVariant &v = dd.Data[0];
v.SetBinary(sizeof(Gd), &Gd);
#elif defined(__GTK_H__)
LAssert(!"Not impl.");
#elif LGI_COCOA
LAssert(!"Not impl.");
#elif LGI_CARBON
// See Apple: Technical Note TN1085
// https://web.archive.org/web/20080725134839/http://developer.apple.com/technotes/tn/tn1085.html
// flavorTypePromiseHFS = 'phfs'
PromiseHFSFlavor Promise;
ZeroObj(Promise);
Promise.fileType = 'mbox';
Promise.fileCreator = '****';
Promise.promisedFlavor = kDragPromisedFlavor; // 'fssP'
int Sz = sizeof(Promise);
LVariant &v1 = dd.Data[0];
v1.SetBinary(sizeof(Promise), &Promise, false);
// The Dnd code will add the 2nd part of the promise
// There isn't the visibility into that API at this level
#else
#error "Impl me."
#endif
}
~LDndFilePromise()
{
#if defined(WINDOWS)
#elif defined(__GTK_H__)
#elif defined(MAC)
#endif
}
#if defined(WINDOWS)
#elif defined(__GTK_H__)
#elif defined(MAC)
#endif
int AddContents(LDragData &dd)
{
dd.Data[0] = Src;
return 1;
}
};
//////////////////////////////////////////////////////////////////////////////
#define PROFILE_POPULATE 0
#define PROFILE_LOAD_THINGS 0
int FoldersCurrentlyLoading = 0;
char ScribeFolderObject[] = "com.memecode.Folder";
class ScribeFolderPriv
{
public:
int8 IsInbox = -1;
bool InUpdateUnread = false;
LAutoPtr DsBase;
LAutoPtr DsUnread;
LAutoPtr FilePromise;
};
//////////////////////////////////////////////////////////////////////////////
ScribeFolder::ScribeFolder()
{
d = new ScribeFolderPriv;
}
ScribeFolder::~ScribeFolder()
{
bool IsRoot = !GetParent();
if (CurState != FldState_Idle)
{
// int Cur = FoldersCurrentlyLoading;
#ifdef _DEBUG
LAssert(!"Can't delete folder while it's busy.");
#else
LgiMsg( App,
"Can't unload folder while busy. Please report what\n"
"you were attempting to do to fret@memecode.com",
"Scribe Error",
MB_OK);
#endif
return;
}
switch (GetItemType())
{
case MAGIC_CALENDAR:
CalendarSource::FolderDelete(this);
// fall through
case MAGIC_CONTACT:
case MAGIC_FILTER:
case MAGIC_GROUP:
App->RemoveThingSrc(this);
break;
default:
break;
}
if (View())
{
bool IsMe = View()->GetContainer() == this;
// bool IsSelect = Select();
View()->DeletePlaceHolders();
if (IsMe)
{
View()->SetContainer(0);
View()->RemoveAll();
}
}
Thing *t;
int i = 0;
while ((t = Items[i]))
{
if (!t->DecRef())
i++;
}
Update();
EmptyFieldList();
DeleteObj(d);
ScribeFolder *f = GetChildFolder();
DeleteObj(f);
if (!IsRoot)
{
f = GetNextFolder();
DeleteObj(f);
}
// Don't delete 'Object' here, it's owned by the backend storage object
}
bool ScribeFolder::SetObject(LDataI *o, bool InDestructor, const char *File, int Line)
{
if (CurState != FldState_Idle)
{
LAssert(!"Can't set object while folder is not idle.");
return false;
}
return LDataUserI::SetObject(o, InDestructor, File, Line);
}
void ScribeFolder::UpdateOsUnread()
{
#ifdef WIN32
if (App->GetFolder(FOLDER_INBOX) == this)
{
LHashTbl, int> Un(0, -1);
// Pre-populate 'Un' with our email accounts
List *Acc = App->GetAccounts();
if (Acc)
{
for (auto a: *Acc)
{
LVariant e = a->Identity.Email();
if (e.Str())
Un.Add(e.Str(), 0);
}
}
// Scan folder for unread email...
for (auto t : Items)
{
Mail *m = t->IsMail();
if (m)
{
if (!TestFlag(m->GetFlags(), MAIL_READ))
{
ScribeAccount *a = m->GetAccountSentTo();
if (a)
{
LVariant Email;
Email = a->Identity.Email();
if (Email.Str())
{
int Cur = Un.Find(Email.Str());
Un.Add(Email.Str(), Cur + 1);
}
}
}
}
}
#if 0
// Set system email status
LArray Ver;
int Os = LGetOs(&Ver);
if ((Os == LGI_OS_WIN32 || Os == LGI_OS_WIN64) && (Ver[0] > 5 || (Ver[0] == 5 && Ver[1] > 0)))
{
char e[256];
LgiGetExeFile(e, sizeof(e));
typedef HRESULT (__stdcall *pSHSetUnreadMailCount)(LPCWSTR pszMailAddress, DWORD dwCount,LPCWSTR pszShellExecuteCommand);
LLibrary Shell32("shell32");
pSHSetUnreadMailCount SHSetUnreadMailCount = (pSHSetUnreadMailCount) Shell32.GetAddress("SHSetUnreadMailCountW");
if (SHSetUnreadMailCount)
{
LVariant Exe;
Exe = e;
char *k;
for (int u=Un.First(&k); u>=0; u=Un.Next(&k))
{
LVariant Email = k;
SHSetUnreadMailCount(Email.WStr(), u, Exe.WStr());
}
}
}
#endif
}
#endif
}
void ScribeFolder::SetLoadOnDemand()
{
if ((LoadOnDemand = new LTreeItem))
{
Expanded(false);
LoadOnDemand->SetText((char*)LLoadString(IDS_LOADING));
Insert(LoadOnDemand);
}
}
void ScribeFolder::GetMessageById(const char *Id, std::function Callback)
{
if (!Id)
{
if (Callback) Callback(NULL);
return;
}
LoadThings(NULL, [this, Id=LString(Id), Callback](auto s)
{
if (s < Store3Delayed)
{
if (Callback) Callback(NULL);
return;
}
for (auto t : Items)
{
Mail *r = t->IsMail();
if (!r)
continue;
auto rid = r->GetMessageId();
if (!Stricmp(rid, Id.Get()))
{
if (Callback)
Callback(r);
return;
}
}
});
}
bool ScribeFolder::InsertThing(Thing *t)
{
bool Status = false;
if (t && Select() && View())
{
// Filter
ThingFilter *Filter = App->GetThingFilter();
t->SetFieldArray(FieldArray);
if (!Filter || Filter->TestThing(t))
{
if (t->GetList() != View())
{
View()->Insert(t);
#if WINNATIVE
UpdateWindow(View()->Handle());
#endif
}
}
Status = true;
}
return Status;
}
bool ScribeFolder::GetThreaded()
{
return GetObject() ? GetObject()->GetInt(FIELD_FOLDER_THREAD) != 0 : false;
}
void ScribeFolder::SetThreaded(bool t)
{
if (GetObject() && GetThreaded() ^ t)
{
GetObject()->SetInt(FIELD_FOLDER_THREAD, t);
SetDirty();
}
}
ThingList *ScribeFolder::View()
{
return App ? App->GetMailList() : 0;
}
bool ScribeFolder::HasFieldId(int Id)
{
auto o = GetFldObj();
if (o)
{
for (LDataPropI *f = o->Fields().First(); f; f = o->Fields().Next())
{
if (Id == f->GetInt(FIELD_ID))
{
return true;
}
}
}
return false;
}
void ScribeFolder::SetFolderPerms(LView *Parent, ScribeAccessType Access, ScribePerm Perm, std::function Callback)
{
ScribePerm Current = GetFolderPerms(Access);
int Field = (Access == ScribeReadAccess) ? FIELD_FOLDER_PERM_READ : FIELD_FOLDER_PERM_WRITE;
if (GetObject() && Current != Perm)
{
App->GetAccessLevel(Parent, Current, GetPath(), [this, Field, Perm, Callback](auto Allow)
{
if (Allow)
{
GetObject()->SetInt(Field, Perm);
SetDirty();
}
if (Callback)
Callback(Allow);
});
}
else
{
if (Callback)
Callback(true); // i.e. not changing
}
}
ScribePerm ScribeFolder::GetFolderPerms(ScribeAccessType Access)
{
int Field = (Access == ScribeReadAccess) ? FIELD_FOLDER_PERM_READ : FIELD_FOLDER_PERM_WRITE;
return GetObject() ? (ScribePerm)GetObject()->GetInt(Field) : PermRequireNone;
}
Store3Status ScribeFolder::CopyTo(ScribeFolder *NewParent, int NewIndex)
{
Store3Status Copied = Store3Error;
if (NewParent == GetParent())
{
NewParent->GetObject()->GetStore();
}
else if (GetObject() &&
GetObject()->GetStore() &&
NewParent->GetObject() &&
NewParent->GetObject()->GetStore())
{
// Find or create the destination folder
LDataFolderI *Dst = 0;
auto Name = GetObject()->GetStr(FIELD_FOLDER_NAME);
for (ScribeFolder *f = NewParent->GetChildFolder(); f; f = f->GetNextFolder())
{
auto n = f->GetObject()->GetStr(FIELD_FOLDER_NAME);
if (n && !_stricmp(n, Name))
{
Dst = f->GetFldObj();
}
}
if (!Dst)
{
// Create sub-folder
if ((Dst = dynamic_cast(NewParent->GetObject()->GetStore()->Create(GetObject()->Type()))))
{
Dst->CopyProps(*GetObject());
if (Dst->Save(NewParent->GetObject()) == Store3Error)
return Store3Error;
}
else return Store3Error;
}
if (Dst)
{
Copied = Store3ReplicateFolders(App, Dst, GetFldObj(), true, false, 0);
}
}
else LAssert(!"Pointer error");
return Copied;
}
Store3Status ScribeFolder::SetFolder(ScribeFolder *f, int Param)
{
Store3Status Moved = Store3Error;
if (f == this)
{
LAssert(0);
}
else if (f &&
GetObject() &&
GetObject()->GetStore() &&
f->GetObject() &&
f->GetObject()->GetStore())
{
if (GetObject()->GetStore() == f->GetObject()->GetStore())
{
// Simple in storage movement
LArray Mv;
Mv.Add(GetObject());
Moved = GetObject()->GetStore()->Move(f->GetFldObj(), Mv);
if (Moved && Param >= 0)
{
GetObject()->SetInt(FIELD_FOLDER_INDEX, Param);
}
}
else
{
// Cross storage movement...
// Find or create the destinate folder
LDataFolderI *Dst = 0;
int Idx = 0;
auto Name = GetObject()->GetStr(FIELD_FOLDER_NAME);
for (ScribeFolder *c = f->GetChildFolder(); c; c = c->GetNextFolder(), Idx++)
{
auto n = c->GetObject()->GetStr(FIELD_FOLDER_NAME);
if (n && !_stricmp(n, Name))
{
Dst = c->GetFldObj();
break;
}
}
if (!Dst)
{
Dst = dynamic_cast(f->GetObject()->GetStore()->Create(f->Type()));
}
else
{
ScribeFolder *c = CastFolder(dynamic_cast(Dst));
if (c)
{
c->Remove();
Param = Idx;
DeleteObj(c);
}
}
if (Dst)
{
if (View())
View()->RemoveAll();
// Clean up all the items and sub-folders, they will be created by the
// replication code calling "OnNew".
Thing *t;
while ((t = Items[0]))
{
t->DecRef();
Items.Delete(t);
}
// Delete the child folders...
ScribeFolder *Cf;
while ((Cf = GetChildFolder()))
{
DeleteObj(Cf);
}
// Copy ourself over...
Dst->CopyProps(*GetObject());
LDataFolderI *Old = GetFldObj();
SetObject(Dst, false, _FL);
// Save the object to the new store...
Store3Status s = GetObject()->Save(f->GetObject());
if (s != Store3Error)
{
// And replicate all the children objects...
Moved = Store3ReplicateFolders(App, GetFldObj(), Old, true, true, 0);
}
}
else LAssert(!"Not a valid folder");
}
if (Moved == Store3Success)
{
// Move LTreeItem node...
f->Insert(this, Param);
Select(true);
LoadFolders();
// Adjust all the other indexes so that it's remembered
ScribeFolder *p = GetFolder();
if (p)
{
int Idx = 0;
for (p = p->GetChildFolder(); p; p = p->GetNextFolder())
{
p->SetSortIndex(Idx++);
p->SetDirty();
}
}
}
}
else LAssert(!"Pointer error");
return Moved;
}
Store3Status ScribeFolder::DeleteAllThings(std::function Callback)
{
if (!GetFldObj())
return Store3Error;
Store3Status r = GetFldObj()->DeleteAllChildren();
if (r == Store3Error)
{
LAssert(!"DeleteAllChildren failed.");
}
else if (r == Store3Success)
{
LAssert(Items.Length() == 0);
OnUpdateUnRead(0, true);
Update();
}
return r;
}
Store3Status ScribeFolder::DeleteThing(Thing *t, std::function Callback)
{
Store3Status Status = Store3Error;
if (t && t->GetObject())
{
if (t->IsAttachment())
{
#ifdef _DEBUG
Mail *m =
#endif
t->IsAttachment()->GetOwner();
LAssert(m && Items.HasItem(m));
}
if (t->GetObject()->Delete())
{
t->SetParentFolder(NULL);
if (View())
View()->Remove(t);
}
}
return Status;
}
Store3Status ScribeFolder::WriteThing(Thing *t, std::function Callback)
{
if (!t)
{
if (Callback) Callback(Store3Error);
return Store3Error;
}
auto ParentFolder = GetFolder();
auto Path = ParentFolder ? ParentFolder->GetPath() : NULL;
auto OnAllow = [this, t, Callback]()
{
// Generic thing storage..
bool Create = !t->GetObject();
if (Create)
t->SetObject(GetObject()->GetStore()->Create(t->Type()), false, _FL);
if (!t->GetObject())
{
LAssert(!"No object?");
LgiTrace("%s:%i - No object to save.\n", _FL);
if (Callback) Callback(Store3Error);
return;
}
// saving a thing that already has an item on disk
auto Obj = GetObject();
auto Status = t->GetObject()->Save(Obj);
if (Status != Store3Error)
{
// The ScribeWnd::OnNew will take care of inserting the item into the
// right folder, updating the unread count, any filtering etc.
t->OnSerialize(true);
if (Callback) Callback(Status);
}
else
{
if (Create)
t->SetObject(NULL, false, _FL);
LgiTrace("%s:%i - Object->Save returned %i.\n", _FL, Status);
if (Callback) Callback(Store3Error);
}
};
if (App)
App->GetAccessLevel(App,
GetWriteAccess(),
Path,
[OnAllow](auto Allow)
{
if (Allow)
OnAllow();
});
else
OnAllow();
return Store3Success;
}
int ThingContainerNameCmp(LTreeItem *a, LTreeItem *b, NativeInt d)
{
ScribeFolder *A = dynamic_cast(a);
ScribeFolder *B = dynamic_cast(b);
if (A && B)
{
const char *s1 = A->GetText();
const char *s2 = B->GetText();
const char *Empty = "";
return _stricmp(s1?s1:Empty, s2?s2:Empty);
}
else LAssert(!"Invalid objects.");
return 0;
}
int ThingContainerIdxCmp(LTreeItem *a, LTreeItem *b, NativeInt d)
{
ScribeFolder *A = dynamic_cast(a);
ScribeFolder *B = dynamic_cast(b);
if (A && B)
{
int Aidx = A->GetSortIndex();
int Bidx = B->GetSortIndex();
if (Aidx >= 0 || Bidx >= 0)
{
return Aidx - Bidx;
}
}
else LAssert(!"Invalid objects.");
return 0;
}
void ScribeFolder::SortSubfolders()
{
int i = 0;
ScribeFolder *c;
LTreeItem::Items.Sort(ThingContainerNameCmp);
for (c = GetChildFolder(); c; c = c->GetNextFolder())
{
c->SetSortIndex(i++);
c->SetDirty();
}
GetTree()->UpdateAllItems();
GetTree()->Invalidate();
}
void ScribeFolder::DoContextMenu(LMouse &m)
{
MailTree *mt = dynamic_cast(GetTree());
LScriptUi s(new LSubMenu);
List Templates;
bool ForceDelete = m.Shift();
bool IsTrash = false;
bool IsSystem = false;
if (!s.Sub)
return;
IsTrash = GetItemType() == MAGIC_ANY;
IsSystem = App->GetFolderType(this) >= 0;
if (App->GetFolderType(this) == FOLDER_OUTBOX)
{
s.Sub->AppendItem(LLoadString(IDS_MARK_ALL_SEND), IDM_MARK_SEND, true);
}
if (IsTrash)
{
s.Sub->AppendItem(LLoadString(IDS_EMPTY), IDM_EMPTY, true);
s.Sub->AppendItem(LLoadString(IDS_MARK_ALL_READ), IDM_MARK_ALL_READ, true);
}
else if (!IsRoot())
{
auto *LiteralNew = LLoadString(IDS_NEW);
const char *Type = 0;
switch (GetItemType())
{
case MAGIC_MAIL:
Type = LLoadString(IDS_MAIL);
break;
case MAGIC_CONTACT:
Type = LLoadString(IDS_CONTACT);
break;
case MAGIC_CALENDAR:
Type = LLoadString(IDS_CAL_EVENT);
break;
case MAGIC_FILTER:
Type = LLoadString(IDS_FILTER);
break;
default:
break;
}
if (Type)
{
char Str[256];
sprintf_s(Str, sizeof(Str), "%s %s", LiteralNew, Type);
s.Sub->AppendItem(Str, IDM_NEW_EMAIL, true);
}
}
switch (GetItemType())
{
case MAGIC_CONTACT:
{
char Str[256];
const char *LiteralEmail = LLoadString(IDS_EMAIL);
sprintf_s(Str, sizeof(Str), "%s '%s'", LiteralEmail, GetText());
s.Sub->AppendItem(Str, IDM_EMAIL_GROUP, true);
ScribeFolder *f = App->GetFolder(FOLDER_TEMPLATES);
if (f)
{
// FIXME
f->LoadThings();
auto Merge = s.Sub->AppendSub(LLoadString(IDS_MERGE_TEMPLATE));
if (Merge)
{
int n = 0;
for (auto t: f->Items)
{
Mail *m = t->IsMail();
if (m)
{
Templates.Insert(m);
Merge->AppendItem(m->GetSubject()?m->GetSubject():(char*)"(no subject)", IDM_MERGE_TEMPLATE_BASE+n++, true);
}
}
}
}
else
{
s.Sub->AppendItem(LLoadString(IDS_MERGE_TEMPLATE), 0, false);
}
s.Sub->AppendItem(LLoadString(IDS_MERGE_FILE), IDM_MERGE_FILE, true);
break;
}
case MAGIC_MAIL:
{
if (!IsRoot())
{
s.Sub->AppendItem(LLoadString(IDC_COLLECT_SUB_MAIL), IDM_COLLECT_MAIL, true);
s.Sub->AppendItem(LLoadString(IDS_MARK_ALL_READ), IDM_MARK_ALL_READ, true);
if (GetObject() &&
GetObject()->GetInt(FIELD_STORE_TYPE) == Store3Imap)
{
s.Sub->AppendItem(LLoadString(IDC_UNDELETE), IDM_UNDELETE, true);
s.Sub->AppendItem("Expunge", IDM_EXPUNGE, true);
s.Sub->AppendItem(LLoadString(IDC_REFRESH), IDM_REFRESH, true);
}
}
break;
}
default:
break;
}
if (s.Sub->ItemAt(0))
{
s.Sub->AppendSeparator();
}
s.Sub->AppendItem(LLoadString(IDS_FIND), IDM_FIND, true);
s.Sub->AppendSeparator();
bool HasFolderTask = App->HasFolderTasks();
bool FolderOp = !HasFolderTask && !IsRoot();
s.Sub->AppendItem(LLoadString(IDS_DELETEFOLDER), IDM_DELETE, FolderOp && ((!IsTrash && !IsSystem) || ForceDelete));
s.Sub->AppendItem(LLoadString(IDS_CREATESUBFOLDER), IDM_CREATE_SUB, !HasFolderTask && CanHaveSubFolders(GetItemType()));
s.Sub->AppendItem(LLoadString(IDS_RENAMEFOLDER), IDM_RENAME, FolderOp);
auto ExportMimeType = GetStorageMimeType();
s.Sub->AppendItem(LLoadString(IDS_EXPORT), IDM_EXPORT, FolderOp && ExportMimeType != NULL);
s.Sub->AppendSeparator();
s.Sub->AppendItem(LLoadString(IDS_SORT_SUBFOLDERS), IDM_SORT_SUBFOLDERS, true);
s.Sub->AppendItem(LLoadString(IDS_COPY_PATH), IDM_COPY_PATH, true);
s.Sub->AppendItem(LLoadString(IDS_PROPERTIES), IDM_PROPERTIES, true);
LArray Callbacks;
if (App->GetScriptCallbacks(LFolderContextMenu, Callbacks))
{
LScriptArguments Args(NULL);
Args[0] = new LVariant(App);
Args[1] = new LVariant(this);
Args[2] = new LVariant(&s);
for (auto c: Callbacks)
App->ExecuteScriptCallback(*c, Args);
Args.DeleteObjects();
}
m.ToScreen();
LPoint Scr = _ScrollPos();
m.x -= Scr.x;
m.y -= Scr.y;
int Msg = s.Sub->Float(GetTree(), m.x, m.y);
switch (Msg)
{
case IDM_NEW_EMAIL:
{
switch (GetItemType())
{
case MAGIC_MAIL:
case MAGIC_CONTACT:
case MAGIC_CALENDAR:
case MAGIC_FILTER:
{
if (App)
App->CreateItem(GetItemType(), this);
break;
}
default:
break;
}
break;
}
case IDM_MARK_SEND:
{
for (Thing *i: Items)
{
Mail *m = i->IsMail();
if (m)
{
m->SetFlags(MAIL_READY_TO_SEND | MAIL_READ | MAIL_CREATED);
m->SetDirty();
m->Update();
}
}
break;
}
case IDM_EMAIL_GROUP:
{
if (App)
{
Mail *m = dynamic_cast(App->CreateItem(MAGIC_MAIL, NULL, false));
if (m)
{
MailUi *UI = dynamic_cast(m->DoUI());
if (UI)
{
List Cts;
App->GetContacts(Cts, this);
for (auto c: Cts)
{
UI->AddRecipient(c);
}
}
}
}
break;
}
case IDM_FIND:
{
ScribeFolder *Folder = (ScribeFolder*) GetTree()->Selection();
OpenFinder(App, Folder);
break;
}
case IDM_CREATE_SUB:
{
if (mt)
mt->OnCreateSubDirectory(this);
break;
}
case IDM_DELETE:
{
if (mt)
mt->OnDelete(this, ForceDelete);
break;
}
case IDM_RENAME:
{
auto Dlg = new FolderNameDlg(mt, GetName(true));
Dlg->DoModal([this, Dlg, mt](auto dlg, auto id)
{
if (id && ValidStr(Dlg->Name))
{
// check for folder name conflicts...
ScribeFolder *ParentFolder = GetFolder();
LString Path;
if (ParentFolder)
Path = ParentFolder->GetPath();
if (Path)
{
char s[256];
sprintf_s(s, sizeof(s), "%s/%s", Path.Get(), Dlg->Name);
if (App->GetFolder(s))
{
LgiMsg(mt, LLoadString(IDS_SUBFLD_NAME_CLASH), AppName, MB_OK);
return;
}
}
// change the folders name...
OnRename(Dlg->Name);
}
delete dlg;
});
break;
}
case IDM_EXPORT:
{
LString DropName = LGetLeaf(GetDropFileName());
if (!DropName)
{
LgiTrace("%s:%i - Failed to create folder name.\n", _FL);
break;
}
auto s = new LFileSelect(mt);
s->Name(DropName);
s->Save([this, ExportMimeType](auto s, auto ok)
{
LAutoPtr mem(s);
if (ok)
{
if (LFileExists(s->Name()))
{
LString a, b;
a.Printf(LLoadString(IDS_ERROR_FILE_EXISTS), s->Name());
b.Printf("\n%s\n", LLoadString(IDS_ERROR_FILE_OVERWRITE));
if (LgiMsg(GetTree(), a + b, AppName, MB_YESNO) == IDNO)
return;
}
LAutoPtr f(new LFile);
if (!f || !f->Open(s->Name(), O_WRITE))
{
LgiTrace("%s:%i - Failed to open '%s' for writing.\n", _FL, s->Name());
return;
}
f->SetSize(0);
LAutoPtr str(f.Release());
ExportAsync(str, ExportMimeType);
}
});
break;
}
case IDM_EMPTY:
{
if (App->GetMailList())
{
App->GetMailList()->RemoveAll();
LArray Del;
for (ScribeFolder *c = GetChildFolder(); c; c = c->GetNextFolder())
Del.Add(c->GetObject());
if (Del.Length())
GetObject()->GetStore()->Delete(Del, false);
DeleteAllThings([mt](auto status)
{
mt->Invalidate();
});
}
break;
}
case IDM_SORT_SUBFOLDERS:
{
SortSubfolders();
break;
}
case IDM_PROPERTIES:
{
mt->OnProperties(this);
break;
}
case IDM_COPY_PATH:
{
LClipBoard c(GetTree());
#ifdef WINDOWS
LAutoWString w(Utf8ToWide(GetPath()));
c.TextW(w);
#else
c.Text(GetPath());
#endif
break;
}
case IDM_COLLECT_MAIL:
{
CollectSubFolderMail();
break;
}
case IDM_MARK_ALL_READ:
{
LArray Change;
for (auto c : Items)
{
Mail *m = c->IsMail();
if (m)
{
if (!TestFlag(m->GetFlags(), MAIL_READ))
Change.Add(m->GetObject());
}
}
LVariant v = MAIL_READ;
if (GetObject()->GetStore()->Change(Change, FIELD_FLAGS, v, OpPlusEquals) == Store3Error)
{
LProgressDlg Prog(GetTree(), 500);
Prog.SetRange(Change.Length());
// FIXME!!
// Prog.SetYieldTime(200);
Prog.SetDescription("Marking email...");
for (auto c : Change)
{
auto t = CastThing(c);
Mail *m = t ? t->IsMail() : NULL;
if (m)
m->SetFlags(m->GetFlags() | MAIL_READ, false, false);
Prog++;
if (Prog.IsCancelled())
break;
}
OnUpdateUnRead(0, true);
}
break;
}
case IDM_MERGE_FILE:
{
auto s = new LFileSelect(mt);
s->Type("Email Template", "*.txt;*.eml");
s->Open([this](auto dlg, auto id)
{
if (id)
{
LArray Recip;
for (auto i: Items)
{
Recip.Add(new ListAddr(i->IsContact()));
}
App->MailMerge(Recip, dlg->Name(), 0);
Recip.DeleteObjects();
}
delete dlg;
});
break;
}
case IDM_UNDELETE:
{
const char *s = DomToStr(SdUndelete);
GetFldObj()->OnCommand(s);
break;
}
case IDM_EXPUNGE:
{
const char *s = DomToStr(SdExpunge);
GetFldObj()->OnCommand(s);
break;
}
case IDM_REFRESH:
{
const char *s = DomToStr(SdRefresh);
GetFldObj()->OnCommand(s);
break;
}
default:
{
Mail *Template = Templates[Msg - IDM_MERGE_TEMPLATE_BASE];
if (Template)
{
LArray Recip;
for (auto i: Items)
{
Recip.Add(new ListAddr(i->IsContact()));
}
App->MailMerge(Recip, 0, Template);
Recip.DeleteObjects();
}
else
{
// Handle any installed callbacks for menu items
for (unsigned i=0; iExecuteScriptCallback(Cb, Args);
}
}
}
break;
}
}
}
bool ScribeFolder::IsInTrash()
{
ScribeFolder *p = this;
while ((p = p->GetFolder()))
{
if (p->GetItemType() == MAGIC_ANY)
return true;
}
return false;
}
/// This just adds certain folder types as a group ware source at the app level
void ScribeFolder::OnItemType()
{
switch (GetItemType())
{
case MAGIC_CONTACT:
case MAGIC_FILTER:
case MAGIC_CALENDAR:
case MAGIC_GROUP:
App->AddThingSrc(this);
break;
default:
break;
}
}
bool ScribeFolder::LoadFolders()
{
// static int64 Last = 0;
auto FldObj = GetFldObj();
if (!FldObj)
return true;
CurState = FldState_Loading;
FoldersCurrentlyLoading++;
LHashTbl, ScribeFolder*> Loaded;
for (ScribeFolder *c = GetChildFolder(); c; c = c->GetNextFolder())
{
Loaded.Add(c->GetObject(), c);
}
LDataFolderI *s;
auto &Sub = FldObj->SubFolders();
for (unsigned sIdx = 0;
Sub.GetState() == Store3Loaded &&
sIdx < Sub.Length();
sIdx++)
{
if (!(s = Sub[sIdx]))
break;
if (!Loaded.Find(s))
{
ScribeFolder *n = new ScribeFolder;
if (n)
{
n->App = App;
n->SetObject(s, false, _FL);
Insert(n);
SetWillDirty(false);
Expanded(GetOpen() != 0);
SetWillDirty(true);
n->LoadFolders();
n->OnItemType();
}
#if 0
int64 Now = LCurrentTime();
if (Now - Last > 500)
{
LYield();
Last = Now;
}
#endif
}
}
LTreeItem::Items.Sort(ThingContainerIdxCmp);
CurState = FldState_Idle;
FoldersCurrentlyLoading--;
return true;
}
class LoadProgressItem : public LListItem
{
int n;
int Total;
int64 Last;
public:
LoadProgressItem(int t)
{
n = 0;
Total = t;
Last = LCurrentTime();
}
void SetPos(int i)
{
n = i;
if (n % 32 == 0)
{
int64 Now = LCurrentTime();
if (Now > Last + 500)
{
if (Parent)
{
Parent->Invalidate(&Pos, true);
#ifdef MAC
LYield();
#endif
}
Last = Now;
}
LSleep(0);
}
}
void OnPaint(ItemPaintCtx &Ctx)
{
int x = n * 150 / Total;
LRect &r = Ctx;
Ctx.pDC->Colour(L_FOCUS_SEL_BACK);
Ctx.pDC->Rectangle(r.x1, r.y1, r.x1 + x, r.y2);
Ctx.pDC->Colour(L_MED);
Ctx.pDC->Rectangle(r.x1 + x + 1, r.y1, r.x1 + 150, r.y2);
Ctx.pDC->Colour(L_WORKSPACE);
Ctx.pDC->Rectangle(r.x1 + 151, r.y1, r.x2, r.y2);
}
};
class LoadingItem : public LListItem
{
LDataIterator *Iter;
LString s;
public:
LoadingItem(LDataIterator *it)
{
_UserPtr = NULL;
if ((Iter = it))
{
Iter->SetProgressFn([this](ssize_t pos, ssize_t sz)
{
this->s.Printf("%.1f%%", (double)pos * 100 / sz);
this->Update();
});
}
}
~LoadingItem()
{
if (Iter)
Iter->SetProgressFn(NULL);
}
bool SetText(const char *str, int i)
{
s = str;
Update();
return true;
}
void OnPaint(LItem::ItemPaintCtx &Ctx)
{
LString msg;
auto loading = LLoadString(IDS_LOADING);
if (s)
msg.Printf("%s %s", loading, s.Get());
else
msg = loading;
LDisplayString d(LSysFont, msg);
LSysFont->Colour(L_TEXT, L_WORKSPACE);
LSysFont->Transparent(false);
d.Draw(Ctx.pDC, (Ctx.X()-d.X())/2, Ctx.y1, &Ctx);
}
void Select(bool b)
{
}
};
bool ScribeFolder::UnloadThings()
{
bool Status = true;
if (IsLoaded())
{
// LgiTrace("Unloading %s, Items=%i\n", GetPath(), Items.Length());
// Emptying the item list, leave the store nodes around though
Thing *t;
while ((t = Items[0]))
{
if (t->GetDirty())
{
t->Save(0);
}
t->SetObject(NULL, false, _FL);
t->DecRef();
}
Items.Empty();
IsLoaded(false);
GetObject()->SetInt(FIELD_LOADED, false);
}
return Status;
}
#define DEBUG_PROF_LOAD_THINGS 0
#if DEBUG_PROF_LOAD_THINGS
#define PROFILE(str) prof.Add("Add");
.Add(str)
#else
#define PROFILE(str)
#endif
void ScribeFolder::ContinueLoading(int OldUnread, std::function Callback)
{
auto FldObj = GetFldObj();
WhenLoaded(_FL,
[this, OldUnread, Callback, FldObj]()
{
// This is called when all the Store3 objects are loaded
int Unread = OldUnread;
if (Unread < 0)
Unread = GetUnRead();
Loading.Reset();
auto &Children = GetFldObj()->Children();
if (Children.GetState() != Store3Loaded)
{
LAssert(!"Really should be loaded by now.");
return;
}
for (auto c = Children.First(); c; c = Children.Next())
{
auto t = CastThing(c);
if (t)
{
// LAssert(Items.HasItem(t));
}
else if ((t = App->CreateThingOfType((Store3ItemTypes) c->Type(), c)))
{
t->SetObject(c, false, _FL);
t->SetParentFolder(this);
t->OnSerialize(false);
}
}
int NewUnRead = 0;
for (auto t: Items)
{
if (t->GetFolder() != this)
{
#ifdef _DEBUG
char s[256];
sprintf_s(s, sizeof(s),
"%s:%i - Error, thing not parented correctly: this='%s', child='%x'\n",
_FL,
GetText(0),
t->GetObject() ? t->GetObject()->Type() : 0);
printf("%s", s);
LgiMsg(App, s, AppName);
#endif
t->SetFolder(this);
}
Mail *m = t->IsMail();
if (m)
NewUnRead += (m->GetFlags() & MAIL_READ) ? 0 : 1;
t->SetFieldArray(FieldArray);
}
if (Unread != NewUnRead)
OnUpdateUnRead(NewUnRead - Unread, false);
Update();
if (d->IsInbox < 0 && App)
{
d->IsInbox = App->GetFolder(FOLDER_INBOX) == this;
if (d->IsInbox > 0)
UpdateOsUnread();
}
if (Callback)
Callback(Store3Success);
},
0);
if (!IsLoaded())
{
bool Ui = Tree ? Tree->InThread() : false;
if (Ui)
Tree->Capture(false);
auto &Children = FldObj->Children();
auto Status = Children.GetState();
if (Status != Store3Loaded)
{
if (View() && Loading.Reset(Ui ? new LoadingItem(&Children) : NULL))
View()->Insert(Loading);
return; // Ie deferred or error...
}
IsLoaded(true);
}
}
Store3Status ScribeFolder::LoadThings(LViewI *Parent, std::function Callback)
{
int OldUnRead = GetUnRead();
auto FldObj = GetFldObj();
if (!FldObj)
{
LgiTrace("%s:%i - No folder object.\n", _FL);
return Store3Error;
}
if (!Parent)
Parent = App;
auto Path = GetPath();
if (!App || !Path)
{
LAssert(!"We should probably always have an 'App' and 'Path' ptrs...");
return Store3Error;
}
auto Access = App->GetAccessLevel(
Parent,
GetReadAccess(),
Path,
[this, OldUnRead, Callback](auto access)
{
if (access)
ContinueLoading(OldUnRead, Callback);
else if (Callback)
Callback(Store3NoPermissions);
});
if (Access == Store3Error)
{
// No read access:
LgiTrace("%s:%i - Folder read access denied.\n", _FL);
// Emptying the item list, leave the store nodes around though
for (auto t: Items)
t->SetObject(NULL, false, _FL);
Items.Empty();
IsLoaded(false);
Update();
}
return Access;
}
void ScribeFolder::OnRename(char *NewName)
{
if (!NewName)
return;
int FolderType = App->GetFolderType(this);
SetName(NewName, true); // Calls update too..
SetDirty();
if (FolderType >= 0)
{
// it's a system folder so reflect the changes in
// the system folder tracking options.
char KeyName[32];
sprintf_s(KeyName, sizeof(KeyName), "Folder-%i", FolderType);
auto Path = GetPath();
if (Path)
{
LVariant v = Path.Get();
App->GetOptions()->SetValue(KeyName, v);
}
}
}
void ScribeFolder::OnDelete()
{
if (GetObject())
{
char Msg[256];
sprintf_s(Msg, sizeof(Msg), LLoadString(IDS_DELETE_FOLDER_DLG), GetText());
int Result = LgiMsg(App, Msg, AppName, MB_YESNO);
if (Result == IDYES)
{
if (App->GetMailList())
App->GetMailList()->RemoveAll();
ScribeFolder *Parent = GetFolder();
LArray Del;
Del.Add(GetObject());
if (GetObject()->GetStore()->Delete(Del, true))
{
if (Parent)
Parent->Select(true);
}
}
}
}
ScribeFolder *ScribeFolder::GetSubFolder(const char *Path)
{
ScribeFolder *Folder = 0;
if (Path)
{
if (*Path == '/') Path++;
char Name[256];
char *Sep = strchr((char*)Path, '/');
if (Sep)
{
ZeroObj(Name);
memcpy(Name, Path, Sep-Path);
}
else
{
strcpy_s(Name, sizeof(Name), Path);
}
LTreeItem *Starts[2] =
{
GetChild(),
IsRoot() ? GetNext() : 0
};
for (int s=0; !Folder && sGetNext())
{
ScribeFolder *f = dynamic_cast(i);
if (f)
{
auto n = f->GetName(true);
if (n.Equals(Name))
{
if (Sep)
Folder = f->GetSubFolder(Sep+1);
else
Folder = f;
break;
}
}
}
}
}
return Folder;
}
bool ScribeFolder::ReindexField(int OldIndex, int NewIndex)
{
if (!GetFldObj())
return false;
auto &Flds = GetFldObj()->Fields();
if (GetFldObj()->Fields().Length() <= 0)
return false;
LDataPropI *f = Flds[OldIndex];
if (!f)
return false;
Flds.Delete(f);
Flds.Insert(f, OldIndex < NewIndex ? NewIndex - 1 : NewIndex);
int i=0;
FieldArray.Length(0);
for (f = Flds.First(); f; f = Flds.Next())
{
auto Id = f->GetInt(FIELD_ID);
FieldArray[i++] = (int)Id;
}
for (auto t: Items)
t->SetFieldArray(FieldArray);
SetDirty();
return true;
}
int ScribeFolder::_UnreadChildren()
{
int Status = 0;
for (ScribeFolder *f=GetChildFolder(); f; f=f->GetNextFolder())
{
int u = f->GetUnRead();
if (u > 0)
Status += u;
Status += f->_UnreadChildren();
}
return Status;
}
void ScribeFolder::_PourText(LPoint &Size)
{
if (!d)
return;
const char *Text = GetText();
Size.x = Size.y = 0;
if (Text && !d->DsBase)
{
if (GetUnRead() > 0 || ChildUnRead)
{
const char *StartCount = strrchr(Text, '(');
ssize_t NameLen = StartCount ? StartCount-Text : strlen(Text);
LFont *b = (App->GetBoldFont()) ? App->GetBoldFont() : GetTree()->GetFont();
d->DsBase.Reset(new LDisplayString(b, Text, NameLen));
d->DsUnread.Reset(new LDisplayString(GetTree()->GetFont(), StartCount));
}
else
{
d->DsBase.Reset(new LDisplayString(GetTree()->GetFont(), Text));
}
}
if (d->DsBase)
Size.x += d->DsBase->X();
if (d->DsUnread)
Size.x += d->DsUnread->X();
Size.x += 4;
Size.y = MAX(16, LSysFont->GetHeight());
}
void ScribeFolder::_PaintText(LItem::ItemPaintCtx &Ctx)
{
if (!d)
return;
LRect *_Text = _GetRect(TreeItemText);
if (d->DsBase)
{
LFont *f = d->DsBase->GetFont();
int Tab = f->TabSize();
f->TabSize(0);
f->Transparent(false);
f->Colour(Ctx.Fore, Ctx.TxtBack);
d->DsBase->Draw(Ctx.pDC, _Text->x1 + 2, _Text->y1 + 1, _Text);
if (d->DsUnread)
{
f = d->DsUnread->GetFont();
f->Transparent(true);
LColour UnreadCol(App->GetColour(L_UNREAD_COUNT));
if
(
abs
(
(UnreadCol.GetGray() - Ctx.Back.GetGray())
)
< 80
)
{
// too close.. use fore
f->Colour(Ctx.Fore, Ctx.TxtBack);
}
else
{
// contrast ok.. use unread
f->Colour(UnreadCol, Ctx.TxtBack);
}
d->DsUnread->Draw(Ctx.pDC, _Text->x1 + 2 + d->DsBase->X(), _Text->y1 + 1);
}
f->TabSize(Tab);
if (Ctx.x2 > _Text->x2)
{
Ctx.pDC->Colour(Ctx.Back);
Ctx.pDC->Rectangle(_Text->x2 + 1, Ctx.y1, Ctx.x2, Ctx.y2);
}
}
else
{
Ctx.pDC->Colour(Ctx.Back);
Ctx.pDC->Rectangle(&Ctx);
}
}
bool ScribeFolder::Save(ScribeFolder *Into)
{
bool Status = false;
if (GetObject())
{
// saving a disk object
Store3Status s = GetObject()->Save();
if (s != Store3Error)
{
Status = true;
SetDirty(false);
}
else
{
LAssert(0);
}
}
return Status;
}
void ScribeFolder::OnExpand(bool b)
{
if (b && LoadOnDemand)
{
DeleteObj(LoadOnDemand);
if (App->ScribeState != ScribeWnd::ScribeExiting)
{
ScribeWnd::AppState Prev = App->ScribeState;
App->ScribeState = ScribeWnd::ScribeLoadingFolders;
LoadFolders();
if (App->ScribeState == ScribeWnd::ScribeExiting)
{
LCloseApp();
return;
}
App->ScribeState = Prev;
}
}
OnUpdateUnRead(0, false);
SetDirty(((uchar)b) != GetOpen());
SetOpen(b);
}
bool ScribeFolder::OnKey(LKey &k)
{
#ifndef WINDOWS
// This is being done by the VK_APPS key on windows...
if (k.IsContextMenu())
{
if (k.Down())
{
LMouse m;
m.x = 5;
m.y = 5;
m.ViewCoords = true;
m.Target = GetTree();
DoContextMenu(m);
}
return true;
}
#endif
return false;
}
#define DefField(id, wid) \
{ \
LDataPropI *Fld = Fields.Create(GetFldObj()->GetStore()); \
if (Fld) \
{ \
Fld->SetInt(FIELD_ID, id); \
Fld->SetInt(FIELD_WIDTH, wid); \
Fields.Insert(Fld); \
} \
}
void ScribeFolder::SetDefaultFields(bool Force)
{
if (!GetFldObj())
return;
if (Force || GetFldObj()->Fields().Length() <= 0)
{
LDataIterator &Fields = GetFldObj()->Fields();
Fields.DeleteObjects();
int FolderType = App ? App->GetFolderType(this) : -1;
if (FolderType == FOLDER_OUTBOX ||
FolderType == FOLDER_SENT)
{
DefField(FIELD_PRIORITY, 10);
DefField(FIELD_FLAGS, 15);
DefField(FIELD_TO, 150);
DefField(FIELD_SUBJECT, 250);
DefField(FIELD_SIZE, 80);
DefField(FIELD_DATE_SENT, 100);
}
else
{
switch (GetItemType())
{
case MAGIC_MAIL:
{
DefField(FIELD_PRIORITY, 10);
DefField(FIELD_FLAGS, 10);
DefField(FIELD_FROM, 150);
DefField(FIELD_SUBJECT, 250);
DefField(FIELD_SIZE, 80);
DefField(FIELD_DATE_SENT, 100);
break;
}
case MAGIC_CONTACT:
{
DefField(FIELD_FIRST_NAME, 200);
DefField(FIELD_LAST_NAME, 200);
DefField(FIELD_EMAIL, 300);
break;
}
case MAGIC_CALENDAR:
{
DefField(FIELD_CAL_SUBJECT, 200);
DefField(FIELD_CAL_START_UTC, 150);
DefField(FIELD_CAL_END_UTC, 150);
break;
}
case MAGIC_FILTER:
{
for (int i=0; DefaultFilterFields[i]; i++)
{
DefField(DefaultFilterFields[i], i == 0 ? 200 : 100);
}
break;
}
case MAGIC_GROUP:
{
DefField(FIELD_GROUP_NAME, 300);
break;
}
default:
break;
}
}
// set for save
SetDirty(Fields.Length() > 0);
}
else
{
- // already has fields, so don't interfer with them
+ // already has fields, so don't interfere with them
}
}
LString ScribeFolder::GetPath()
{
LString::Array p;
ScribeFolder *f = this;
while (f)
{
- p.Add(f->GetName(true));
+ auto name = f->GetName(true);
+ if (name)
+ p.Add(name);
f = dynamic_cast(f->GetParent());
}
LString dir = "/";
return dir + dir.Join(p.Reverse());
}
void ScribeFolder::SetName(const char *Name, bool Encode)
{
if (Name)
{
d->DsBase.Reset();
d->DsUnread.Reset();
NameCache.Reset();
if (GetObject())
GetObject()->SetStr(FIELD_FOLDER_NAME, Name);
SetText(Name); // Calls update
}
}
LString ScribeFolder::GetName(bool Decode)
{
if (!GetObject())
return NULL;
auto In = GetObject()->GetStr(FIELD_FOLDER_NAME);
if (!In)
return NULL;
if (Decode)
return LUrlDecode(In);
else
return In;
}
void ScribeFolder::Update()
{
if (Tree && !Tree->InThread())
{
// LgiTrace("%s:%i - Update not in thread?\n", _FL);
return;
}
d->DsBase.Reset();
d->DsUnread.Reset();
NameCache.Reset();
LTreeItem::Update();
}
const char *ScribeFolder::GetText(int i)
{
if (!NameCache)
{
LString Name = GetName(true);
if (!Name)
return "...loading...";
size_t NameLen = strlen(Name) + 40;
NameCache.Reset(new char[NameLen]);
if (NameCache)
{
bool ShowTotals = false;
LVariant v;
if (App->GetOptions()->GetValue(OPT_ShowFolderTotals, v))
ShowTotals = v.CastInt32() != 0;
int c = sprintf_s(NameCache, NameLen, "%s", Name.Get());
auto ItemType = GetItemType();
if (ItemType &&
(ShowTotals || GetUnRead() || ChildUnRead))
{
c += sprintf_s(NameCache+c, NameLen-c, " (");
if (ItemType == MAGIC_MAIL ||
ItemType == MAGIC_ANY)
{
const char *Ch = ChildUnRead ? "..." : "";
if (ShowTotals)
c += sprintf_s(NameCache+c, NameLen-c, "%i%s/", GetUnRead(), Ch);
else if (GetUnRead())
c += sprintf_s(NameCache+c, NameLen-c, "%i%s", GetUnRead(), Ch);
else if (ChildUnRead)
c += sprintf_s(NameCache+c, NameLen-c, "...");
}
if (ShowTotals)
{
if (IsLoaded())
c += sprintf_s(NameCache+c, NameLen-c, "%zi", Items.Length());
else
c += sprintf_s(NameCache+c, NameLen-c, "?");
}
c += sprintf_s(NameCache+c, NameLen-c, ")");
}
}
}
return NameCache;
}
int ScribeFolder::GetImage(int Flags)
{
if (GetItemType() == MAGIC_ANY)
{
return ICON_TRASH;
}
if (Flags)
{
return ICON_OPEN_FOLDER;
}
switch (GetItemType())
{
case MAGIC_MAIL:
return ICON_FOLDER_MAIL;
case MAGIC_CONTACT:
return ICON_FOLDER_CONTACTS;
case MAGIC_FILTER:
return ICON_FOLDER_FILTERS;
case MAGIC_CALENDAR:
return ICON_FOLDER_CALENDAR;
case MAGIC_NONE:
return ICON_MAILBOX;
default:
return ICON_CLOSED_FOLDER;
}
}
class NullMail : public Mail
{
Thing &operator =(Thing &c) override
{
return *this;
}
public:
NullMail(ScribeWnd *App, MContainer *c, ScribeFolder *f) : Mail(App)
{
SetFlags(MAIL_READ | MAIL_CREATED);
Container = c;
SetParentFolder(f);
SetSubject((char*)LLoadString(IDS_MISSING_PARENT));
}
~NullMail()
{
SetParentFolder(NULL);
}
bool IsPlaceHolder() override
{
return true;
}
const char *GetText(int i) override
{
// Clear all the fields
return 0;
}
int Compare(LListItem *Arg, ssize_t Field) override
{
// Use the first mail to sort into position
if (Container)
{
MContainer *c = Container->Children.Length() ? Container->Children[0] : 0;
if (c && c->Message)
{
return c->Message->Compare(Arg, Field);
}
}
return -1;
}
void OnMouseClick(LMouse &m) override
{
// Disable the mouse click
}
bool SetDirty(bool b = true) override
{
// Don't set it to dirty, otherwise the dirty object
// clean up code will save it into the outbox.
return true;
}
};
template
int TrashCompare(T *pa, T *pb, NativeInt Data)
{
ScribeFolder *f = (ScribeFolder*)Data;
Thing *a = dynamic_cast(pa);
Thing *b = dynamic_cast(pb);
if (!a || !b)
return 0;
int type = a->Type() - b->Type();
if (type)
return type;
int col = f->GetSortCol();
int *defs = a->GetDefaultFields();
if (!defs || !defs[col])
return 0;
return (f->GetSortAscend() ? 1 : -1) * a->Compare(b, defs[col]);
}
template int TrashCompare(LListItem *pa, LListItem *pb, NativeInt Data);
int ListItemCompare(LListItem *a, LListItem *b, NativeInt Data)
{
ScribeFolder *f = (ScribeFolder*)Data;
return (f->GetSortAscend() ? 1 : -1) * a->Compare(b, f->GetSortField());
}
int ThingCompare(Thing *a, Thing *b, NativeInt Data)
{
ScribeFolder *f = (ScribeFolder*)Data;
return (f->GetSortAscend() ? 1 : -1) * a->Compare(b, f->GetSortField());
}
int ThingSorter(Thing *a, Thing *b, ThingSortParams *Params)
{
return (Params->SortAscend ? 1 : -1) * a->Compare(b, Params->SortField);
}
bool ScribeFolder::Thread()
{
bool Status = false;
if (GetItemType() == MAGIC_MAIL)
{
int Thread = GetThreaded();
ThingList *Lst = View();
List m;
for (auto t: Items)
{
Mail *e = t->IsMail();
if (e)
{
if (Thread)
{
m.Insert(e);
}
else if (e->Container)
{
DeleteObj(e->Container);
}
}
}
if (Lst && m[0])
{
// Thread
LArray Containers;
MContainer::Thread(m, Containers);
LAssert(m.Length() == Items.Length());
// Insert blank items for missing thread parents
for (unsigned i=0; iMessage)
{
LAssert(c->Children.Length() > 1);
c->Message = new NullMail(App, c, this);
if (c->Message)
{
Lst->PlaceHolders.Insert(c->Message);
if (View() && c->Message)
{
c->Message->SetFieldArray(FieldArray);
Items.Insert(c->Message);
}
}
}
}
// Sort root list
LArray Containers2 = Containers;
ThingSortParams Params;
Params.SortAscend = GetSortAscend();
Params.SortField = GetSortField();
#if 0
for (int i=0; iMessage ? Containers[i]->Message->GetFieldText(Params.SortField) : NULL;
LgiTrace("%p(%s)\n", Containers[i], Str1);
}
Containers.Sort(ContainerCompare);
LQuickSort(Base, Containers2.Length(), ContainerSorter, &Params);
for (int i=0; iMessage ? Containers[i]->Message->GetFieldText(Params.SortField) : NULL;
char *Str2 = Containers2[i]->Message ? Containers2[i]->Message->GetFieldText(Params.SortField) : NULL;
LgiTrace("%p(%s), %p(%s) - %s\n", Containers[i], Str1, Containers2[i], Str2, Containers[i]!=Containers2[i]?"DIFFERENT":"");
}
#else
MContainer **Base = &Containers[0];
if (Containers.Length() > 1)
LQuickSort(Base, Containers.Length(), ContainerSorter, &Params);
/*
for (int i=0; iMessage ? Containers[i]->Message->GetFieldText(Params.SortField) : NULL;
LgiTrace("%p(%s)\n", Containers[i]->Message, Str1);
}
LgiTrace("\n");
*/
#endif
// Position and index the thread tree
int Index = 0;
for (unsigned i=0; iPour(Index, 0, 0, i < Containers.Length()-1, &Params);
}
// Sort all the items by index
Items.Sort(ContainerIndexer);
Status = true;
/*
int Idx = 0;
for (Thing *t = Items.First(); t; t = Items.Next(), Idx++)
{
Mail *m = t->IsMail();
if (m)
{
char *Str = m->GetFieldText(Params.SortField);
LgiTrace("%i,%i %p %s\n", Idx, m->Container ? m->Container->Index : -1, m, Str);
}
}
*/
}
}
else
{
for (auto t: Items)
{
Mail *e = t->IsMail();
if (e)
DeleteObj(e->Container);
}
}
return Status;
}
struct SortPairInt
{
Thing *t;
uint64 ts;
void SetDate(Thing *th, int sf)
{
t = th;
auto obj = th->GetObject();
// Store3State loaded = (Store3State)obj->GetInt(FIELD_LOADED);
auto dt = obj->GetDate(sf);
ts = dt && dt->Year() ? dt->Ts() : 0;
}
void SetInt(Thing *th, int sf)
{
t = th;
auto obj = th->GetObject();
// Store3State loaded = (Store3State)obj->GetInt(FIELD_LOADED);
ts = obj->GetInt(sf);
}
static int Compare(SortPairInt *a, SortPairInt *b)
{
int64_t diff = (int64_t)a->ts - (int64_t)b->ts;
if (diff < 0) return -1;
return diff > 0 ? 1 : 0;
}
};
struct SortPairStr
{
Thing *t;
const char *ts;
void SetStr(Thing *th, int sf)
{
t = th;
ts = th->GetObject()->GetStr(sf);
}
static int Compare(SortPairStr *a, SortPairStr *b)
{
return stricmp(a->ts ? a->ts : "", b->ts ? b->ts : "");
}
};
bool ScribeFolder::SortItems()
{
int sf = GetSortField();
LVariantType type = GV_NULL;
auto StartTs = LCurrentTime();
const static int TimeOut = 2/*sec*/ * 1000;
switch (sf)
{
case FIELD_DATE_SENT:
case FIELD_DATE_RECEIVED:
type = GV_DATETIME;
break;
case FIELD_SUBJECT:
type = GV_STRING;
break;
default:
return false;
}
LArray intPairs;
LArray strPairs;
bool intType = type == GV_DATETIME ||
type == GV_INT32 ||
type == GV_INT64;
if (intType)
{
intPairs.Length(Items.Length());
int n = 0;
auto s = intPairs.AddressOf();
if (type == GV_DATETIME)
{
for (auto i: Items)
{
s[n++].SetDate(i, sf);
if (n % 50 == 0 && LCurrentTime() - StartTs >= TimeOut)
return false;
}
}
else
{
for (auto i: Items)
{
s[n++].SetInt(i, sf);
if (n % 50 == 0 && LCurrentTime() - StartTs >= TimeOut)
return false;
}
}
intPairs.Sort(SortPairInt::Compare);
}
else if (type == GV_STRING)
{
strPairs.Length(Items.Length());
int n = 0;
auto s = strPairs.AddressOf();
for (auto i: Items)
s[n++].SetStr(i, sf);
strPairs.Sort(SortPairStr::Compare);
}
Items.Empty();
if (intType)
{
if (GetSortAscend())
for (auto i: intPairs) Items.Add(i.t);
else
for (auto it = intPairs.rbegin(); it != intPairs.end(); it--)
Items.Add((*it).t);
}
else
{
if (GetSortAscend())
for (auto i: strPairs) Items.Add(i.t);
else
for (auto it = strPairs.rbegin(); it != strPairs.end(); it--)
Items.Add((*it).t);
}
return true;
}
void ScribeFolder::Populate(ThingList *list)
{
LProfile Prof("ScribeFolder::Populate", 1000);
App->OnSelect();
if (!GetFldObj() || !list)
return;
CurState = FldState_Populating;
ScribeFolder *Prev = list->GetContainer();
bool Refresh = Prev == this;
// Remove old items from list
Prof.Add("Delete Placeholders");
list->DeletePlaceHolders();
list->RemoveAll();
if (!Refresh || list->GetColumns() == 0 || GetItemType() == MAGIC_FILTER)
{
if (Prev)
{
// save previous folders settings
Prev->SerializeFieldWidths();
}
Prof.Add("Empty cols");
LVariant GridLines;
if (App->GetOptions()->GetValue(OPT_GridLines, GridLines))
{
list->DrawGridLines(GridLines.CastInt32() != 0);
}
list->EmptyColumns();
Prof.Add("Set def fields");
bool ForceDefaultFields = GetItemType() == MAGIC_FILTER;
if (GetFldObj()->Fields().Length() <= 0 || ForceDefaultFields)
{
SetDefaultFields(ForceDefaultFields);
}
// Add fields to list view
int n = 0;
LArray Empty;
for (auto t: Items)
{
t->SetFieldArray(Empty);
}
LRect *Bounds = 0;
if (App->GetIconImgList())
{
Bounds = App->GetIconImgList()->GetBounds();
}
switch (GetItemType())
{
case MAGIC_ANY:
{
list->AddColumn("", 170);
list->AddColumn("", 170);
list->AddColumn("", 170);
list->AddColumn("", 170);
break;
}
default:
{
n = 0;
FieldArray.Length(0);
for (LDataPropI *i = GetFldObj()->Fields().First(); i; i = GetFldObj()->Fields().Next())
{
int FieldId = (int)i->GetInt(FIELD_ID);
const char *FName = LLoadString(FieldId);
int Width = (int)i->GetInt(FIELD_WIDTH);
const char *FieldText = FName ? FName : i->GetStr(FIELD_NAME);
LAssert(FieldText != NULL);
LItemColumn *c = list->AddColumn(FieldText, Width);
if (c)
{
switch (i->GetInt(FIELD_ID))
{
case FIELD_PRIORITY:
{
int x = 12;
if (Bounds)
{
x = Bounds[ICON_PRIORITY_BLACK].X() + 7;
}
c->Width(x);
c->Image(ICON_PRIORITY_BLACK);
c->Resizable(false);
break;
}
case FIELD_FLAGS:
{
int x = 14;
if (Bounds)
{
x = Bounds[ICON_FLAGS_BLACK].X() + 7;
}
c->Width(x);
c->Image(ICON_FLAGS_BLACK);
c->Resizable(false);
break;
}
case FIELD_SIZE:
{
c->TextAlign(LCss::Len(LCss::AlignRight));
break;
}
}
}
FieldArray[n++] = (int)i->GetInt(FIELD_ID);
}
break;
}
}
// Add all items to list
if (View())
{
View()->SetContainer(this);
// tell the list who we are
if (GetSortCol() >= 0)
{
// set current sort settings
View()->SetSort(GetSortCol(), GetSortAscend());
}
}
Prof.Add("Load things");
// FIXME:
LoadThings();
}
// Filter
List Is;
// Do any threading/sorting
static LString SortMsg;
if (!Thread() &&
GetSortField())
{
SortMsg.Printf("Sorting " LPrintfInt64 " items", Items.Length());
Prof.Add(SortMsg);
if (GetItemType() == MAGIC_ANY)
{
Items.Sort(TrashCompare, (NativeInt)this);
}
else
{
// Sort..
// if (!SortItems())
Items.Sort(ThingCompare, (NativeInt)this);
}
}
// Do any filtering...
Prof.Add("Filtering");
ThingFilter *Filter = App->GetThingFilter();
auto FilterStart = LCurrentTime();
size_t Pos = 0;
for (auto t: Items)
{
t->SetFieldArray(FieldArray);
if (!Filter || Filter->TestThing(t))
{
// Add anyway... because all items are not part of list
Is.Insert(t);
}
if ((LCurrentTime()-FilterStart) > 300)
{
if (!Loading && Loading.Reset(new LoadingItem(NULL)))
list->Insert(Loading, 0);
if (Loading)
{
LString s;
s.Printf(LPrintfInt64 " of " LPrintfInt64 ", %.1f%%",
Pos, Items.Length(), (double)Pos * 100 / Items.Length());
Loading->SetText(s);
}
FilterStart = LCurrentTime();
}
Pos++;
}
Prof.Add("Inserting");
if (View() && Is[0])
{
View()->Insert(Is, -1, true);
}
Prof.Add("Deleting");
list->DeletePlaceHolders();
Loading.Reset();
Prof.Add("OnSelect");
GetFldObj()->OnSelect(true);
CurState = FldState_Idle;
}
void ScribeFolder::OnUpdateUnRead(int Offset, bool ScanItems)
{
if (!d->InUpdateUnread)
{
d->InUpdateUnread = true;
int OldUnRead = GetUnRead();
d->DsBase.Reset();
d->DsUnread.Reset();
NameCache.Reset();
if (ScanItems)
{
if (GetItemType() == MAGIC_MAIL ||
GetItemType() == MAGIC_ANY)
{
size_t Count = 0;
for (auto t: Items)
{
Mail *m = t->IsMail();
if (m && !TestFlag(m->GetFlags(), MAIL_READ))
Count++;
}
SetUnRead((int32_t)Count);
}
}
else if (Offset != 0)
{
SetUnRead(GetUnRead() + Offset);
}
if (GetUnRead() < 0)
SetUnRead(0);
if (OldUnRead != GetUnRead())
{
for (LTreeItem *i = GetParent(); i; i = i->GetParent())
{
ScribeFolder *tc = dynamic_cast(i);
if (tc && tc->GetParent())
tc->OnUpdateUnRead(0, false);
}
SetDirty();
if (d->IsInbox > 0)
UpdateOsUnread();
}
ChildUnRead = Expanded() ? 0 : _UnreadChildren();
Update();
d->InUpdateUnread = false;
}
}
void ScribeFolder::EmptyFieldList()
{
if (GetFldObj())
GetFldObj()->Fields().DeleteObjects();
}
void ScribeFolder::SerializeFieldWidths(bool Write)
{
if (GetFldObj() && View())
{
// LAssert(View()->GetColumns() == Object->Fields().Length());
int Cols = MIN(View()->GetColumns(), (int)GetFldObj()->Fields().Length());
for (int i=0; iColumnAt(i);
LDataPropI *f = GetFldObj()->Fields()[i];
if (c && f)
{
if (f->GetInt(FIELD_WIDTH) != c->Width())
{
if (Write)
{
c->Width((int)f->GetInt(FIELD_WIDTH));
}
else
{
f->SetInt(FIELD_WIDTH, c->Width());
SetDirty();
}
}
}
else LAssert(0);
}
}
}
void ScribeFolder::OnProperties(int Tab)
{
if (!GetObject())
return;
SerializeFieldWidths();
if (View())
{
SetSort(View()->GetSortCol(), View()->GetSortAscending());
}
OpenFolderProperties(this, Tab, [this](auto repop)
{
if (repop)
{
SetDirty();
SerializeFieldWidths(true);
Populate(View());
}
});
}
ScribeFolder *ScribeFolder::CreateSubDirectory(const char *Name, int Type)
{
ScribeFolder *NewFolder = 0;
auto ThisObj = dynamic_cast(GetObject());
if (Name && ThisObj && ThisObj->GetStore())
{
LDataI *Fld = GetObject()->GetStore()->Create(MAGIC_FOLDER);
if (Fld)
{
LDataFolderI *Obj = dynamic_cast(Fld);
if (Obj)
{
NewFolder = new ScribeFolder;
if (NewFolder)
{
NewFolder->App = App;
NewFolder->SetObject(Obj, false, _FL);
// Set name and type
NewFolder->SetName(Name, true);
NewFolder->GetObject()->SetInt(FIELD_FOLDER_TYPE, Type);
ThisObj->SubFolders();
if (NewFolder->GetObject()->Save(ThisObj))
{
Insert(NewFolder);
NewFolder->SetDefaultFields();
NewFolder->OnItemType();
}
else
{
DeleteObj(NewFolder);
}
}
}
}
}
return NewFolder;
}
bool ScribeFolder::Delete(LArray &Items, bool ToTrash)
{
if (!App)
return false;
List NotNew;
LArray Del;
LDataStoreI *Store = NULL;
for (auto i: Items)
{
if (i->IsPlaceHolder())
{
i->DecRef();
}
else
{
Mail *m = i->IsMail();
if (m)
NotNew.Insert(m);
auto ObjStore = i->GetObject() ? i->GetObject()->GetStore() : NULL;
if (!Store)
Store = ObjStore;
if (Store == ObjStore)
Del.Add(i->GetObject());
else
LAssert(!"All objects must have the same store.");
}
}
if (NotNew.Length())
App->OnNewMail(&NotNew, false);
if (Del.Length() == 0)
return true;
if (!Store)
{
LgiTrace("%s:%i - No Store?\n", _FL);
return false;
}
if (!Store->Delete(Del, ToTrash))
{
LgiTrace("%s:%i - Store.Delete failed.\n", _FL);
return false;
}
return true;
}
class MoveToState
{
ScribeWnd *App = NULL;
// Input
ScribeFolder *Folder = NULL;
LArray Items;
bool CopyOnly;
std::function&)> Callback;
// Output
bool Result = false; // Overall success/failure
LArray Status; // Per item status
// State
LArray InStoreMove; // Object in the same store...
LDataI *FolderObj = NULL;
LDataStoreI *FolderStore = NULL;
ScribeMailType NewBayesType = BayesMailUnknown;
LHashTbl, int> Map;
int NewFolderType = -1;
bool BuildDynMenus = false;
bool BayesInc = false;
size_t Moves = 0;
// Returns true if the object is deleted.
bool SetStatus(int i, Store3Status s)
{
LAssert(Status[i] == Store3NotImpl);
Status[i] = s;
LAssert(Moves > 0);
Moves--;
if (Moves > 0)
return false;
OnMovesDone();
return true;
}
public:
MoveToState(ScribeFolder *folder,
LArray &items,
bool copyOnly,
std::function&)> callback) :
Folder(folder),
Items(items),
CopyOnly(copyOnly),
Callback(callback)
{
// Validate parameters
if (Folder &&
(App = Folder->App))
{
LVariant v;
if (App->GetOptions()->GetValue(OPT_BayesIncremental, v))
BayesInc = v.CastInt32() != 0;
}
else
{
delete this;
return;
}
if ((FolderObj = Folder->GetObject()))
{
FolderStore = Folder->GetObject()->GetStore();
}
Status.Length(Moves = Items.Length());
for (auto &s: Status)
s = Store3NotImpl;
auto FolderItemType = Folder->GetItemType();
auto ThisFolderPath = Folder->GetPath();
NewBayesType = App->BayesTypeFromPath(ThisFolderPath);
NewFolderType = App->GetFolderType(Folder);
ScribeFolder *TemplatesFolder = App->GetFolder(FOLDER_TEMPLATES);
BuildDynMenus = Folder == TemplatesFolder;
for (unsigned i=0; iGetObject())
{
if (SetStatus(i, Store3Error))
return;
continue;
}
auto ThingItemType = t->Type();
if (FolderItemType != ThingItemType && FolderItemType != MAGIC_ANY)
{
if (SetStatus(i, Store3Error))
return;
continue;
}
ScribeFolder *Old = t->GetFolder();
LString Path;
if (Old)
{
Path = Old->GetPath();
if (Old == TemplatesFolder)
// Moving to or from the templates folder... update the menu
BuildDynMenus = true;
}
if (Old && Path)
{
bool IsDeleted = false;
App->GetAccessLevel(App,
Old->GetFolderPerms(ScribeWriteAccess),
Path,
[this, i, t, &IsDeleted](bool Allow)
{
if (Allow)
IsDeleted = Move(i, t);
else
IsDeleted = SetStatus(i, Store3NoPermissions);
});
// If the callback has already been executed and the object is deleted, exit immediately.
if (IsDeleted)
return;
}
else
{
if (Move(i, t))
return;
}
}
}
// This must call SetStatus once and only once for each item it's called with.
// Returns true if the SetStatus call indicates deletion.
// 'this' will be invalid after SetStatus returns true.
bool Move(int i, Thing *t)
{
ScribeMailType OldBayesType = BayesMailUnknown;
if (BayesInc &&
t->IsMail() &&
TestFlag(t->IsMail()->GetFlags(), MAIL_READ))
{
OldBayesType = App->BayesTypeFromPath(t->IsMail());
}
ScribeFolder *Old = t->GetFolder();
Store3Status r = Store3NotImpl;
int OldFolderType = Old ? App->GetFolderType(Old) : -1;
if ( (OldFolderType == FOLDER_TRASH || OldFolderType == FOLDER_SENT) &&
NewFolderType == FOLDER_TRASH)
{
// Delete for good
r = Old ? Old->DeleteThing(t, NULL) : Store3Error;
}
else
{
// If this folder is currently selected...
if (Folder->Select())
{
// Insert item into list
t->SetFieldArray(Folder->FieldArray);
}
if (CopyOnly)
{
LDataI *NewT = FolderStore->Create(t->Type());
if (NewT)
{
NewT->CopyProps(*t->GetObject());
r = NewT->Save(Folder->GetObject());
}
else
{
r = Store3Error;
}
}
else
{
if (NewFolderType != FOLDER_TRASH &&
OldBayesType != NewBayesType)
{
App->OnBayesianMailEvent(t->IsMail(), OldBayesType, NewBayesType);
}
// Move to this folder
auto o = t->GetObject();
if (o && o->GetStore() == FolderStore)
{
InStoreMove.Add(o);
Map.Add(t, i);
r = Store3Delayed;
}
else
{
// Out of store more... use the old single object method... for the moment..
r = t->SetFolder(Folder);
if (r == Store3Success)
{
// Remove from the list..
if (Old && Old->Select() && App->GetMailList())
App->GetMailList()->Remove(t);
}
}
}
}
if (r == Store3Success)
t->OnMove();
return SetStatus(i, r);
}
void OnMovesDone()
{
if (InStoreMove.Length())
{
Store3Status s = Store3NotImpl;
auto Fld = dynamic_cast(Folder->GetObject());
if (!Fld)
s = Store3Error;
else
s = FolderStore->Move(Fld, InStoreMove);
Result = s >= Store3Delayed;
for (auto p: Map)
{
Status[p.value] = s;
if (s == Store3Success)
{
LAssert(p.key->GetFolder() == Folder);
LAssert(Items.HasItem(p.key));
p.key->OnMove();
}
}
}
if (BuildDynMenus)
// Moving to or from the templates folder... update the menu
App->BuildDynMenus();
if (Callback)
Callback(Result, Status);
delete this;
}
};
void ScribeFolder::MoveTo(LArray &Items, bool CopyOnly, std::function&)> Callback)
{
if (Items.Length() == 0)
return;
if (!GetObject() || !App)
return;
new MoveToState(this, Items, CopyOnly, Callback);
}
int ThingFilterCompare(Thing *a, Thing *b, NativeInt Data)
{
Filter *A = a->IsFilter();
Filter *B = b->IsFilter();
return (A && B) ? A->GetIndex() - B->GetIndex() : 0;
}
void ScribeFolder::ReSort()
{
if (View() && Select())
{
View()->SetSort(GetSortCol(), GetSortAscend());
}
}
void ScribeFolder::SetSort(int Col, bool Ascend, bool CanDirty)
{
if (GetItemType() == MAGIC_FILTER)
{
// Remove any holes in the indexing
int i = 1;
Items.Sort(ThingFilterCompare);
for (auto t : Items)
{
Filter *f = t->IsFilter();
if (f)
{
if (f->GetIndex() != i)
{
f->SetIndex(i);
}
i++;
}
}
}
if (GetSortCol() != Col ||
GetSortAscend() != (uchar)Ascend)
{
GetObject()->SetInt(FIELD_SORT, (Col + 1) * (Ascend ? 1 : -1));
if (CanDirty)
SetDirty();
}
}
int ScribeFolder::GetSortField()
{
int Status = 0;
int Col = GetSortCol();
if (Col >= 0 && Col < (int)FieldArray.Length())
Status = FieldArray[Col];
return Status;
}
class FolderStream : public LStream
{
ScribeFolder *f;
// Current index into the thing array
int Idx;
// Total known size of stream...
int64 Sz;
// A memory buffer containing just the encoded 'Thing'
LMemStream Mem;
LString FolderMime;
public:
FolderStream(ScribeFolder *folder) : Mem(512 << 10)
{
f = folder;
Idx = 0;
Sz = 0;
switch (f->GetItemType())
{
case MAGIC_MAIL:
FolderMime = sMimeMbox;
break;
case MAGIC_CONTACT:
FolderMime = sMimeVCard;
break;
case MAGIC_CALENDAR:
FolderMime = sMimeVCalendar;
break;
default:
LAssert(!"Need a mime type?");
break;
}
}
int GetIndex() { return Idx; }
int Open(const char *Str = 0,int Int = 0) { return true; }
bool IsOpen() { return true; }
int Close() { return 0; }
int64 GetSize()
{
return -1;
}
int64 SetSize(int64 Size)
{
return -1;
}
int64 GetPos()
{
return -1;
}
int64 SetPos(int64 pos)
{
// This means that IStream::Seek return E_NOTIMPL, which is important
// for windows to support copies of unknown size.
return -1;
}
ssize_t Read(void *Buffer, ssize_t Size, int Flags = 0)
{
// Read from stream..
int64 Remaining = Mem.GetSize() - Mem.GetPos();
if (Remaining <= 0)
{
Thing *t = Idx < (ssize_t)f->Items.Length() ? f->Items[Idx++] : NULL;
if (t)
{
Mem.SetSize(0);
// We can't allow Export to delete the Mem object, we own it.
// So create a proxy object for it.
LAutoPtr cp(new LProxyStream(&Mem));
if (t->Export(cp, FolderMime))
Sz += Mem.GetSize();
else
LAssert(0);
Mem.SetPos(0);
}
else return 0;
}
Remaining = Mem.GetSize() - Mem.GetPos();
if (Remaining > 0)
{
int Common = (int)MIN(Size, Remaining);
ssize_t Rd = Mem.Read(Buffer, Common);
if (Rd > 0)
return Rd;
else
LAssert(0);
}
return 0;
}
ssize_t Write(const void *Buffer, ssize_t Size, int Flags = 0)
{
LAssert(0);
return 0;
}
LStreamI *Clone()
{
LAssert(0);
return NULL;
}
};
// ScribeFolder drag'n'drop
bool ScribeFolder::GetFormats(LDragFormats &Formats)
{
if (GetItemType() == MAGIC_MAIL ||
GetItemType() == MAGIC_CONTACT ||
GetItemType() == MAGIC_CALENDAR)
{
Formats.SupportsFileStreams();
}
Formats.Supports(ScribeFolderObject);
return Formats.Length() > 0;
}
bool ScribeFolder::OnBeginDrag(LMouse &m)
{
if (GetParent())
Drag(Tree, m.Event, DROPEFFECT_MOVE | DROPEFFECT_COPY);
return true;
}
void ScribeFolder::OnEndData()
{
DropFileName.Empty();
}
LString ScribeFolder::GetDropFileName()
{
LAutoString Fn(MakeFileName(GetObject()->GetStr(FIELD_FOLDER_NAME), 0));
DropFileName = Fn;
if (GetItemType() == MAGIC_MAIL)
DropFileName += ".mbx";
else if (GetItemType() == MAGIC_CONTACT)
DropFileName += ".vcf";
else if (GetItemType() == MAGIC_CALENDAR)
DropFileName += ".ics";
else if (GetItemType() == MAGIC_FILTER)
DropFileName += ".xml";
return DropFileName;
}
bool ScribeFolder::GetData(LArray &Data)
{
ssize_t DataSet = 0;
for (unsigned idx=0; idxSetPos(0);
auto Fn = GetDropFileName();
auto MimeType = sMimeMbox;
auto r = dd.AddFileStream(LGetLeaf(Fn), MimeType, s);
LAssert(r);
}
DataSet = dd.Data.Length();
}
else if (dd.IsFormat(LGI_FileDropFormat))
{
LMouse m;
if (App->GetMouse(m, true))
{
LString::Array Files;
if (GetDropFileName())
{
LAutoPtr f(new LFile);
if (f->Open(DropFileName, O_WRITE))
{
if (GetItemType() == MAGIC_MAIL)
Export(AutoCast(f), sMimeMbox);
else if (GetItemType() == MAGIC_CONTACT)
Export(AutoCast(f), sMimeVCard);
else if (GetItemType() == MAGIC_CALENDAR)
Export(AutoCast(f), sMimeVCalendar);
}
}
if (DropFileName)
{
Files.Add(DropFileName.Get());
if (CreateFileDrop(&dd, m, Files))
{
DataSet++;
}
else LgiTrace("%s:%i - CreateFileDrop failed.\n", _FL);
}
else LgiTrace("%s:%i - No drop file name.\n", _FL);
}
else LgiTrace("%s:%i - GetMouse failed.\n", _FL);
}
else if (dd.IsFormat(ScribeFolderObject))
{
ScribeClipboardFmt *Fmt = ScribeClipboardFmt::Alloc(true, 1);
if (Fmt)
{
Fmt->FolderAt(0, this);
dd.Data[0].SetBinary(Fmt->Sizeof(), Fmt);
DataSet++;
free(Fmt);
}
}
}
return DataSet > 0;
}
void ScribeFolder::CollectSubFolderMail(ScribeFolder *To)
{
if (!To) To = this;
LoadThings(NULL, [this, To](auto Status)
{
LArray