1 /* 2 Open Tracker License 3 4 Terms and Conditions 5 6 Copyright (c) 1991-2001, Be Incorporated. All rights reserved. 7 8 Permission is hereby granted, free of charge, to any person obtaining a copy of 9 this software and associated documentation files (the "Software"), to deal in 10 the Software without restriction, including without limitation the rights to 11 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 12 of the Software, and to permit persons to whom the Software is furnished to do 13 so, subject to the following conditions: 14 15 The above copyright notice and this permission notice applies to all licensees 16 and shall be included in all copies or substantial portions of the Software. 17 18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, 20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 21 BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN 22 AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN 23 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 25 Except as contained in this notice, the name of Be Incorporated shall not be 26 used in advertising or otherwise to promote the sale, use or other dealings in 27 this Software without prior written authorization from Be Incorporated. 28 29 BeMail(TM), Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or 30 registered trademarks of Be Incorporated in the United States and other 31 countries. Other brand product names are registered trademarks or trademarks 32 of their respective holders. All rights reserved. 33 */ 34 35 36 #include "MailWindow.h" 37 38 #include <stdio.h> 39 #include <stdlib.h> 40 #include <string.h> 41 #include <fcntl.h> 42 #include <unistd.h> 43 #include <sys/stat.h> 44 #include <sys/utsname.h> 45 46 #include <Autolock.h> 47 #include <Clipboard.h> 48 #include <Debug.h> 49 #include <E-mail.h> 50 #include <InterfaceKit.h> 51 #include <Locale.h> 52 #include <PathMonitor.h> 53 #include <Roster.h> 54 #include <Screen.h> 55 #include <StorageKit.h> 56 #include <String.h> 57 #include <TextView.h> 58 #include <UTF8.h> 59 60 #include <fs_index.h> 61 #include <fs_info.h> 62 63 #include <MailMessage.h> 64 #include <MailSettings.h> 65 #include <MailDaemon.h> 66 #include <mail_util.h> 67 68 #include <CharacterSetRoster.h> 69 70 #include "ButtonBar.h" 71 #include "Content.h" 72 #include "Enclosures.h" 73 #include "FieldMsg.h" 74 #include "FindWindow.h" 75 #include "Header.h" 76 #include "Messages.h" 77 #include "MailApp.h" 78 #include "MailPopUpMenu.h" 79 #include "MailSupport.h" 80 #include "Prefs.h" 81 #include "QueryMenu.h" 82 #include "Signature.h" 83 #include "Status.h" 84 #include "String.h" 85 #include "Utilities.h" 86 87 88 #define B_TRANSLATE_CONTEXT "Mail" 89 90 91 using namespace BPrivate; 92 93 94 const char *kUndoStrings[] = { 95 "Undo", 96 "Undo typing", 97 "Undo cut", 98 "Undo paste", 99 "Undo clear", 100 "Undo drop" 101 }; 102 103 const char *kRedoStrings[] = { 104 "Redo", 105 "Redo typing", 106 "Redo cut", 107 "Redo paste", 108 "Redo clear", 109 "Redo drop" 110 }; 111 112 113 // Text for both the main menu and the pop-up menu. 114 static const char *kSpamMenuItemTextArray[] = { 115 "Mark as spam and move to trash", // M_TRAIN_SPAM_AND_DELETE 116 "Mark as spam", // M_TRAIN_SPAM 117 "Unmark this message", // M_UNTRAIN 118 "Mark as genuine" // M_TRAIN_GENUINE 119 }; 120 121 static const uint32 kMsgQuitAndKeepAllStatus = 'Casm'; 122 123 static const char *kQueriesDirectory = "mail/queries"; 124 static const char *kAttrQueryInitialMode = "_trk/qryinitmode"; 125 // taken from src/kits/tracker/Attributes.h 126 static const char *kAttrQueryInitialString = "_trk/qryinitstr"; 127 static const char *kAttrQueryInitialNumAttrs = "_trk/qryinitnumattrs"; 128 static const char *kAttrQueryInitialAttrs = "_trk/qryinitattrs"; 129 static const uint32 kAttributeItemMain = 'Fatr'; 130 // taken from src/kits/tracker/FindPanel.h 131 static const uint32 kByNameItem = 'Fbyn'; 132 // taken from src/kits/tracker/FindPanel.h 133 static const uint32 kByAttributeItem = 'Fbya'; 134 // taken from src/kits/tracker/FindPanel.h 135 static const uint32 kByForumlaItem = 'Fbyq'; 136 // taken from src/kits/tracker/FindPanel.h 137 138 139 // static list for tracking of Windows 140 BList TMailWindow::sWindowList; 141 BLocker TMailWindow::sWindowListLock; 142 143 static bool sKeepStatusOnQuit; 144 145 146 // #pragma mark - 147 148 149 TMailWindow::TMailWindow(BRect rect, const char* title, TMailApp* app, 150 const entry_ref* ref, const char* to, const BFont* font, bool resending, 151 BMessenger* messenger) 152 : BWindow(rect, title, B_DOCUMENT_WINDOW, 0), 153 fApp(app), 154 fFieldState(0), 155 fPanel(NULL), 156 fSendButton(NULL), 157 fSaveButton(NULL), 158 fPrintButton(NULL), 159 fSigButton(NULL), 160 fZoom(rect), 161 fEnclosuresView(NULL), 162 fPrevTrackerPositionSaved(false), 163 fNextTrackerPositionSaved(false), 164 fSigAdded(false), 165 fReplying(false), 166 fResending(resending), 167 fSent(false), 168 fDraft(false), 169 fChanged(false), 170 fStartingText(NULL), 171 fOriginatingWindow(NULL), 172 fReadButton(NULL), 173 fNextButton(NULL) 174 { 175 if (messenger != NULL) 176 fTrackerMessenger = *messenger; 177 178 char str[256]; 179 char status[272]; 180 uint32 message; 181 float height; 182 BMenu* menu; 183 BMenu* subMenu; 184 BMenuItem* item; 185 BMessage* msg; 186 attr_info info; 187 BFile file(ref, B_READ_ONLY); 188 189 if (ref) { 190 fRef = new entry_ref(*ref); 191 fMail = new BEmailMessage(fRef); 192 fIncoming = true; 193 } else { 194 fRef = NULL; 195 fMail = NULL; 196 fIncoming = false; 197 } 198 199 fAutoMarkRead = fApp->AutoMarkRead(); 200 BRect r(0, 0, RIGHT_BOUNDARY, 15); 201 fMenuBar = new BMenuBar(r, ""); 202 203 // File Menu 204 205 menu = new BMenu(B_TRANSLATE("File")); 206 207 msg = new BMessage(M_NEW); 208 msg->AddInt32("type", M_NEW); 209 menu->AddItem(item = new BMenuItem(B_TRANSLATE("New mail message"), 210 msg, 'N')); 211 item->SetTarget(be_app); 212 213 // Cheap hack - only show the drafts menu when composing messages. Insert 214 // a "true || " in the following IF statement if you want the old BeMail 215 // behaviour. The difference is that without live draft menu updating you 216 // can open around 100 e-mails (the BeOS maximum number of open files) 217 // rather than merely around 20, since each open draft-monitoring query 218 // sucks up one file handle per mounted BFS disk volume. Plus mail file 219 // opening speed is noticably improved! ToDo: change this to populate the 220 // Draft menu with the file names on demand - when the user clicks on it; 221 // don't need a live query since the menu isn't staying up for more than a 222 // few seconds. 223 224 if (!fIncoming) { 225 QueryMenu *queryMenu; 226 queryMenu = new QueryMenu(B_TRANSLATE("Open draft"), false); 227 queryMenu->SetTargetForItems(be_app); 228 229 queryMenu->SetPredicate("MAIL:draft==1"); 230 menu->AddItem(queryMenu); 231 } 232 233 if (!fIncoming || resending) { 234 menu->AddItem(fSendLater = new BMenuItem(B_TRANSLATE("Save as draft"), 235 new BMessage(M_SAVE_AS_DRAFT), 'S')); 236 } 237 238 if (!resending && fIncoming) { 239 menu->AddSeparatorItem(); 240 241 subMenu = new BMenu(B_TRANSLATE("Close and ")); 242 if (file.GetAttrInfo(B_MAIL_ATTR_STATUS, &info) == B_NO_ERROR) 243 file.ReadAttr(B_MAIL_ATTR_STATUS, B_STRING_TYPE, 0, str, info.size); 244 else 245 str[0] = 0; 246 247 if (!strcmp(str, "New")) { 248 subMenu->AddItem(item = new BMenuItem(B_TRANSLATE("Leave as New"), 249 new BMessage(M_CLOSE_SAME), 'W', B_SHIFT_KEY)); 250 #if 0 251 subMenu->AddItem(item = new BMenuItem(B_TRANSLATE("Set to Read"), 252 new BMessage(M_CLOSE_READ), 'W')); 253 #endif 254 message = M_CLOSE_READ; 255 } else { 256 if (strlen(str)) 257 sprintf(status, B_TRANSLATE("Leave as '%s'"), str); 258 else 259 sprintf(status, B_TRANSLATE("Leave same")); 260 subMenu->AddItem(item = new BMenuItem(status, 261 new BMessage(M_CLOSE_SAME), 'W')); 262 message = M_CLOSE_SAME; 263 AddShortcut('W', B_COMMAND_KEY | B_SHIFT_KEY, 264 new BMessage(M_CLOSE_SAME)); 265 } 266 267 subMenu->AddItem(new BMenuItem(B_TRANSLATE("Move to trash"), 268 new BMessage(M_DELETE), 'T', B_CONTROL_KEY)); 269 AddShortcut('T', B_SHIFT_KEY | B_COMMAND_KEY, 270 new BMessage(M_DELETE_NEXT)); 271 272 subMenu->AddSeparatorItem(); 273 274 subMenu->AddItem(new BMenuItem(B_TRANSLATE("Set to Saved"), 275 new BMessage(M_CLOSE_SAVED), 'W', B_CONTROL_KEY)); 276 277 if (add_query_menu_items(subMenu, INDEX_STATUS, M_STATUS, 278 B_TRANSLATE("Set to %s")) > 0) 279 subMenu->AddSeparatorItem(); 280 281 subMenu->AddItem(new BMenuItem(B_TRANSLATE("Set to" B_UTF8_ELLIPSIS), 282 new BMessage(M_CLOSE_CUSTOM))); 283 284 #if 0 285 subMenu->AddItem(new BMenuItem(new TMenu( 286 B_TRANSLATE("Set to" B_UTF8_ELLIPSIS), INDEX_STATUS, M_STATUS, 287 false, false), 288 new BMessage(M_CLOSE_CUSTOM))); 289 #endif 290 menu->AddItem(subMenu); 291 } else { 292 menu->AddSeparatorItem(); 293 menu->AddItem(new BMenuItem(B_TRANSLATE("Close"), 294 new BMessage(B_CLOSE_REQUESTED), 'W')); 295 } 296 297 menu->AddSeparatorItem(); 298 menu->AddItem(fPrint = new BMenuItem( 299 B_TRANSLATE("Page setup" B_UTF8_ELLIPSIS), 300 new BMessage(M_PRINT_SETUP))); 301 menu->AddItem(fPrint = new BMenuItem( 302 B_TRANSLATE("Print" B_UTF8_ELLIPSIS), 303 new BMessage(M_PRINT), 'P')); 304 fMenuBar->AddItem(menu); 305 306 menu->AddSeparatorItem(); 307 menu->AddItem(item = new BMenuItem( 308 B_TRANSLATE("About Mail" B_UTF8_ELLIPSIS), 309 new BMessage(B_ABOUT_REQUESTED))); 310 item->SetTarget(be_app); 311 312 menu->AddSeparatorItem(); 313 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Quit"), 314 new BMessage(B_QUIT_REQUESTED), 'Q')); 315 item->SetTarget(be_app); 316 317 // Edit Menu 318 319 menu = new BMenu(B_TRANSLATE("Edit")); 320 menu->AddItem(fUndo = new BMenuItem(B_TRANSLATE("Undo"), 321 new BMessage(B_UNDO), 'Z', 0)); 322 fUndo->SetTarget(NULL, this); 323 menu->AddItem(fRedo = new BMenuItem(B_TRANSLATE("Redo"), 324 new BMessage(M_REDO), 'Z', B_SHIFT_KEY)); 325 fRedo->SetTarget(NULL, this); 326 menu->AddSeparatorItem(); 327 menu->AddItem(fCut = new BMenuItem(B_TRANSLATE("Cut"), 328 new BMessage(B_CUT), 'X')); 329 fCut->SetTarget(NULL, this); 330 menu->AddItem(fCopy = new BMenuItem(B_TRANSLATE("Copy"), 331 new BMessage(B_COPY), 'C')); 332 fCopy->SetTarget(NULL, this); 333 menu->AddItem(fPaste = new BMenuItem(B_TRANSLATE("Paste"), 334 new BMessage(B_PASTE), 335 'V')); 336 fPaste->SetTarget(NULL, this); 337 menu->AddSeparatorItem(); 338 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Select all"), 339 new BMessage(M_SELECT), 'A')); 340 menu->AddSeparatorItem(); 341 item->SetTarget(NULL, this); 342 menu->AddItem(new BMenuItem(B_TRANSLATE("Find" B_UTF8_ELLIPSIS), 343 new BMessage(M_FIND), 'F')); 344 menu->AddItem(new BMenuItem(B_TRANSLATE("Find again"), 345 new BMessage(M_FIND_AGAIN), 'G')); 346 if (!fIncoming) { 347 menu->AddSeparatorItem(); 348 menu->AddItem(fQuote =new BMenuItem(B_TRANSLATE("Quote"), 349 new BMessage(M_QUOTE), B_RIGHT_ARROW)); 350 menu->AddItem(fRemoveQuote = new BMenuItem(B_TRANSLATE("Remove quote"), 351 new BMessage(M_REMOVE_QUOTE), B_LEFT_ARROW)); 352 menu->AddSeparatorItem(); 353 fSpelling = new BMenuItem(B_TRANSLATE("Check spelling"), 354 new BMessage(M_CHECK_SPELLING), ';'); 355 menu->AddItem(fSpelling); 356 if (fApp->StartWithSpellCheckOn()) 357 PostMessage (M_CHECK_SPELLING); 358 } 359 menu->AddSeparatorItem(); 360 menu->AddItem(item = new BMenuItem( 361 B_TRANSLATE("Preferences" B_UTF8_ELLIPSIS), 362 new BMessage(M_PREFS),',')); 363 item->SetTarget(be_app); 364 fMenuBar->AddItem(menu); 365 menu->AddItem(item = new BMenuItem( 366 B_TRANSLATE("Accounts" B_UTF8_ELLIPSIS), 367 new BMessage(M_ACCOUNTS),'-')); 368 item->SetTarget(be_app); 369 370 // View Menu 371 372 if (!resending && fIncoming) { 373 menu = new BMenu(B_TRANSLATE("View")); 374 menu->AddItem(fHeader = new BMenuItem(B_TRANSLATE("Show header"), 375 new BMessage(M_HEADER), 'H')); 376 menu->AddItem(fRaw = new BMenuItem(B_TRANSLATE("Show raw message"), 377 new BMessage(M_RAW))); 378 fMenuBar->AddItem(menu); 379 } 380 381 // Message Menu 382 383 menu = new BMenu(B_TRANSLATE("Message")); 384 385 if (!resending && fIncoming) { 386 BMenuItem *menuItem; 387 menu->AddItem(new BMenuItem(B_TRANSLATE("Reply"), 388 new BMessage(M_REPLY),'R')); 389 menu->AddItem(new BMenuItem(B_TRANSLATE("Reply to sender"), 390 new BMessage(M_REPLY_TO_SENDER),'R',B_OPTION_KEY)); 391 menu->AddItem(new BMenuItem(B_TRANSLATE("Reply to all"), 392 new BMessage(M_REPLY_ALL), 'R', B_SHIFT_KEY)); 393 394 menu->AddSeparatorItem(); 395 396 menu->AddItem(new BMenuItem(B_TRANSLATE("Forward"), 397 new BMessage(M_FORWARD), 'J')); 398 menu->AddItem(new BMenuItem(B_TRANSLATE("Forward without attachments"), 399 new BMessage(M_FORWARD_WITHOUT_ATTACHMENTS))); 400 menu->AddItem(menuItem = new BMenuItem(B_TRANSLATE("Resend"), 401 new BMessage(M_RESEND))); 402 menu->AddItem(menuItem = new BMenuItem(B_TRANSLATE("Copy to new"), 403 new BMessage(M_COPY_TO_NEW), 'D')); 404 405 menu->AddSeparatorItem(); 406 fDeleteNext = new BMenuItem(B_TRANSLATE("Move to trash"), 407 new BMessage(M_DELETE_NEXT), 'T'); 408 menu->AddItem(fDeleteNext); 409 menu->AddSeparatorItem(); 410 411 fPrevMsg = new BMenuItem(B_TRANSLATE("Previous message"), 412 new BMessage(M_PREVMSG), B_UP_ARROW); 413 menu->AddItem(fPrevMsg); 414 fNextMsg = new BMenuItem(B_TRANSLATE("Next message"), 415 new BMessage(M_NEXTMSG), B_DOWN_ARROW); 416 menu->AddItem(fNextMsg); 417 menu->AddSeparatorItem(); 418 fSaveAddrMenu = subMenu = new BMenu(B_TRANSLATE("Save address")); 419 420 // create the list of addresses 421 422 BList addressList; 423 get_address_list(addressList, fMail->To(), extract_address); 424 get_address_list(addressList, fMail->CC(), extract_address); 425 get_address_list(addressList, fMail->From(), extract_address); 426 get_address_list(addressList, fMail->ReplyTo(), extract_address); 427 428 for (int32 i = addressList.CountItems(); i-- > 0;) { 429 char *address = (char *)addressList.RemoveItem(0L); 430 431 // insert the new address in alphabetical order 432 int32 index = 0; 433 while ((item = subMenu->ItemAt(index)) != NULL) { 434 if (!strcmp(address, item->Label())) { 435 // item already in list 436 goto skip; 437 } 438 439 if (strcmp(address, item->Label()) < 0) 440 break; 441 442 index++; 443 } 444 445 msg = new BMessage(M_SAVE); 446 msg->AddString("address", address); 447 subMenu->AddItem(new BMenuItem(address, msg), index); 448 449 skip: 450 free(address); 451 } 452 453 menu->AddItem(subMenu); 454 fMenuBar->AddItem(menu); 455 456 // Spam Menu 457 458 if (fApp->ShowSpamGUI()) { 459 menu = new BMenu("Spam filtering"); 460 menu->AddItem(new BMenuItem("Mark as spam and move to trash", 461 new BMessage(M_TRAIN_SPAM_AND_DELETE), 'K')); 462 menu->AddItem(new BMenuItem("Mark as spam", 463 new BMessage(M_TRAIN_SPAM), 'K', B_OPTION_KEY)); 464 menu->AddSeparatorItem(); 465 menu->AddItem(new BMenuItem("Unmark this message", 466 new BMessage(M_UNTRAIN))); 467 menu->AddSeparatorItem(); 468 menu->AddItem(new BMenuItem("Mark as genuine", 469 new BMessage(M_TRAIN_GENUINE), 'K', B_SHIFT_KEY)); 470 fMenuBar->AddItem(menu); 471 } 472 } else { 473 menu->AddItem(fSendNow = new BMenuItem(B_TRANSLATE("Send message"), 474 new BMessage(M_SEND_NOW), 'M')); 475 476 if (!fIncoming) { 477 menu->AddSeparatorItem(); 478 fSignature = new TMenu(B_TRANSLATE("Add signature"), 479 INDEX_SIGNATURE, M_SIGNATURE); 480 menu->AddItem(new BMenuItem(fSignature)); 481 menu->AddItem(item = new BMenuItem( 482 B_TRANSLATE("Edit signatures" B_UTF8_ELLIPSIS), 483 new BMessage(M_EDIT_SIGNATURE))); 484 item->SetTarget(be_app); 485 menu->AddSeparatorItem(); 486 menu->AddItem(fAdd = new BMenuItem( 487 B_TRANSLATE("Add enclosure" B_UTF8_ELLIPSIS), 488 new BMessage(M_ADD), 'E')); 489 menu->AddItem(fRemove = new BMenuItem( 490 B_TRANSLATE("Remove enclosure"), 491 new BMessage(M_REMOVE), 'T')); 492 } 493 fMenuBar->AddItem(menu); 494 } 495 496 // Queries Menu 497 498 fQueryMenu = new BMenu(B_TRANSLATE("Queries")); 499 fMenuBar->AddItem(fQueryMenu); 500 501 _RebuildQueryMenu(true); 502 503 // Menu Bar 504 505 AddChild(fMenuBar); 506 height = fMenuBar->Bounds().bottom + 1; 507 508 // Button Bar 509 510 float bbwidth = 0, bbheight = 0; 511 512 bool showButtonBar = fApp->ShowButtonBar(); 513 514 if (showButtonBar) { 515 BuildButtonBar(); 516 fButtonBar->ShowLabels(showButtonBar); 517 fButtonBar->Arrange(true); 518 fButtonBar->GetPreferredSize(&bbwidth, &bbheight); 519 fButtonBar->ResizeTo(Bounds().right, bbheight); 520 fButtonBar->MoveTo(0, height); 521 fButtonBar->Show(); 522 } else 523 fButtonBar = NULL; 524 525 r.top = r.bottom = height + bbheight + 1; 526 fHeaderView = new THeaderView (r, rect, fIncoming, fMail, resending, 527 (resending || !fIncoming) 528 ? fApp->MailCharacterSet() 529 // Use preferences setting for composing mail. 530 : B_MAIL_NULL_CONVERSION, 531 // Default is automatic selection for reading mail. 532 fApp->DefaultChain()); 533 534 r = Frame(); 535 r.OffsetTo(0, 0); 536 r.top = fHeaderView->Frame().bottom - 1; 537 fContentView = new TContentView(r, fIncoming, fMail, 538 const_cast<BFont *>(font), false, fApp->ColoredQuotes()); 539 // TContentView needs to be properly const, for now cast away constness 540 541 AddChild(fHeaderView); 542 if (fEnclosuresView) 543 AddChild(fEnclosuresView); 544 AddChild(fContentView); 545 546 if (to) 547 fHeaderView->fTo->SetText(to); 548 549 AddShortcut('n', B_COMMAND_KEY, new BMessage(M_NEW)); 550 551 // If auto-signature, add signature to the text here. 552 553 BString signature = fApp->Signature(); 554 555 if (!fIncoming && strcmp(signature.String(), B_TRANSLATE("None")) != 0) { 556 if (strcmp(signature.String(), B_TRANSLATE("Random")) == 0) 557 PostMessage(M_RANDOM_SIG); 558 else { 559 // Create a query to find this signature 560 BVolume volume; 561 BVolumeRoster().GetBootVolume(&volume); 562 563 BQuery query; 564 query.SetVolume(&volume); 565 query.PushAttr(INDEX_SIGNATURE); 566 query.PushString(signature.String()); 567 query.PushOp(B_EQ); 568 query.Fetch(); 569 570 // If we find the named query, add it to the text. 571 BEntry entry; 572 if (query.GetNextEntry(&entry) == B_NO_ERROR) { 573 off_t size; 574 BFile file; 575 file.SetTo(&entry, O_RDWR); 576 if (file.InitCheck() == B_NO_ERROR) { 577 file.GetSize(&size); 578 char *str = (char *)malloc(size); 579 size = file.Read(str, size); 580 581 fContentView->fTextView->Insert(str, size); 582 fContentView->fTextView->GoToLine(0); 583 fContentView->fTextView->ScrollToSelection(); 584 585 fStartingText = (char *)malloc(size 586 = strlen(fContentView->fTextView->Text()) + 1); 587 if (fStartingText != NULL) 588 strcpy(fStartingText, fContentView->fTextView->Text()); 589 } 590 } else { 591 char tempString [2048]; 592 query.GetPredicate (tempString, sizeof (tempString)); 593 printf ("Query failed, was looking for: %s\n", tempString); 594 } 595 } 596 } 597 598 if (fRef != NULL) 599 SetTitleForMessage(); 600 601 _UpdateSizeLimits(); 602 603 AddShortcut('q', B_SHIFT_KEY, new BMessage(kMsgQuitAndKeepAllStatus)); 604 } 605 606 607 void 608 TMailWindow::BuildButtonBar() 609 { 610 ButtonBar *bbar; 611 612 bbar = new ButtonBar(BRect(0, 0, 100, 100), "ButtonBar", 2, 3, 0, 1, 10, 613 2); 614 bbar->AddButton(B_TRANSLATE("New"), 28, new BMessage(M_NEW)); 615 bbar->AddDivider(5); 616 fButtonBar = bbar; 617 618 if (fResending) { 619 fSendButton = bbar->AddButton(B_TRANSLATE("Send"), 8, 620 new BMessage(M_SEND_NOW)); 621 bbar->AddDivider(5); 622 } else if (!fIncoming) { 623 fSendButton = bbar->AddButton(B_TRANSLATE("Send"), 8, 624 new BMessage(M_SEND_NOW)); 625 fSendButton->SetEnabled(false); 626 fSigButton = bbar->AddButton(B_TRANSLATE("Signature"), 4, 627 new BMessage(M_SIG_MENU)); 628 fSigButton->InvokeOnButton(B_SECONDARY_MOUSE_BUTTON); 629 fSaveButton = bbar->AddButton(B_TRANSLATE("Save"), 44, 630 new BMessage(M_SAVE_AS_DRAFT)); 631 fSaveButton->SetEnabled(false); 632 fPrintButton = bbar->AddButton(B_TRANSLATE("Print"), 16, 633 new BMessage(M_PRINT)); 634 fPrintButton->SetEnabled(false); 635 bbar->AddButton(B_TRANSLATE("Trash"), 0, new BMessage(M_DELETE)); 636 bbar->AddDivider(5); 637 } else { 638 BmapButton *button = bbar->AddButton(B_TRANSLATE("Reply"), 12, 639 new BMessage(M_REPLY)); 640 button->InvokeOnButton(B_SECONDARY_MOUSE_BUTTON); 641 button = bbar->AddButton(B_TRANSLATE("Forward"), 40, 642 new BMessage(M_FORWARD)); 643 button->InvokeOnButton(B_SECONDARY_MOUSE_BUTTON); 644 fPrintButton = bbar->AddButton(B_TRANSLATE("Print"), 16, 645 new BMessage(M_PRINT)); 646 bbar->AddButton(B_TRANSLATE("Trash"), 0, new BMessage(M_DELETE_NEXT)); 647 if (fApp->ShowSpamGUI()) { 648 button = bbar->AddButton("Spam", 48, new BMessage(M_SPAM_BUTTON)); 649 button->InvokeOnButton(B_SECONDARY_MOUSE_BUTTON); 650 } 651 bbar->AddDivider(5); 652 fNextButton = bbar->AddButton(B_TRANSLATE("Next"), 24, 653 new BMessage(M_NEXTMSG)); 654 bbar->AddButton(B_TRANSLATE("Previous"), 20, new BMessage(M_PREVMSG)); 655 if (!fAutoMarkRead) { 656 _AddReadButton(); 657 } 658 } 659 660 bbar->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 661 bbar->Hide(); 662 AddChild(bbar); 663 } 664 665 666 void 667 TMailWindow::UpdateViews() 668 { 669 float bbwidth = 0, bbheight = 0; 670 float nextY = fMenuBar->Frame().bottom + 1; 671 672 uint8 showButtonBar = fApp->ShowButtonBar(); 673 674 // Show/Hide Button Bar 675 if (showButtonBar) { 676 // Create the Button Bar if needed 677 if (!fButtonBar) 678 BuildButtonBar(); 679 680 fButtonBar->ShowLabels(showButtonBar == 1); 681 fButtonBar->Arrange(true); 682 // True for all buttons same size, false to just fit 683 fButtonBar->GetPreferredSize(&bbwidth, &bbheight); 684 fButtonBar->ResizeTo(Bounds().right, bbheight); 685 fButtonBar->MoveTo(0, nextY); 686 nextY += bbheight + 1; 687 if (fButtonBar->IsHidden()) 688 fButtonBar->Show(); 689 else 690 fButtonBar->Invalidate(); 691 } else if (fButtonBar && !fButtonBar->IsHidden()) 692 fButtonBar->Hide(); 693 694 // Arange other views to match 695 fHeaderView->MoveTo(0, nextY); 696 nextY = fHeaderView->Frame().bottom; 697 if (fEnclosuresView) { 698 fEnclosuresView->MoveTo(0, nextY); 699 nextY = fEnclosuresView->Frame().bottom + 1; 700 } 701 BRect bounds(Bounds()); 702 fContentView->MoveTo(0, nextY - 1); 703 fContentView->ResizeTo(bounds.right - bounds.left, 704 bounds.bottom - nextY + 1); 705 706 _UpdateSizeLimits(); 707 } 708 709 710 void 711 TMailWindow::UpdatePreferences() 712 { 713 fAutoMarkRead = fApp->AutoMarkRead(); 714 715 _UpdateReadButton(); 716 } 717 718 719 TMailWindow::~TMailWindow() 720 { 721 fApp->SetLastWindowFrame(Frame()); 722 723 delete fMail; 724 delete fPanel; 725 delete fOriginatingWindow; 726 727 BAutolock locker(sWindowListLock); 728 sWindowList.RemoveItem(this); 729 } 730 731 732 status_t 733 TMailWindow::GetMailNodeRef(node_ref &nodeRef) const 734 { 735 if (fRef == NULL) 736 return B_ERROR; 737 738 BNode node(fRef); 739 return node.GetNodeRef(&nodeRef); 740 } 741 742 743 bool 744 TMailWindow::GetTrackerWindowFile(entry_ref *ref, bool next) const 745 { 746 // Position was already saved 747 if (next && fNextTrackerPositionSaved) { 748 *ref = fNextRef; 749 return true; 750 } 751 if (!next && fPrevTrackerPositionSaved) { 752 *ref = fPrevRef; 753 return true; 754 } 755 756 if (!fTrackerMessenger.IsValid()) 757 return false; 758 759 // Ask the tracker what the next/prev file in the window is. 760 // Continue asking for the next reference until a valid 761 // email file is found (ignoring other types). 762 entry_ref nextRef = *ref; 763 bool foundRef = false; 764 while (!foundRef) { 765 BMessage request(B_GET_PROPERTY); 766 BMessage spc; 767 if (next) 768 spc.what = 'snxt'; 769 else 770 spc.what = 'sprv'; 771 772 spc.AddString("property", "Entry"); 773 spc.AddRef("data", &nextRef); 774 775 request.AddSpecifier(&spc); 776 BMessage reply; 777 if (fTrackerMessenger.SendMessage(&request, &reply) != B_OK) 778 return false; 779 780 if (reply.FindRef("result", &nextRef) != B_OK) 781 return false; 782 783 char fileType[256]; 784 BNode node(&nextRef); 785 if (node.InitCheck() != B_OK) 786 return false; 787 788 if (BNodeInfo(&node).GetType(fileType) != B_OK) 789 return false; 790 791 if (strcasecmp(fileType,"text/x-email") == 0) 792 foundRef = true; 793 } 794 795 *ref = nextRef; 796 return foundRef; 797 } 798 799 800 void 801 TMailWindow::SaveTrackerPosition(entry_ref *ref) 802 { 803 // if only one of them is saved, we're not going to do it again 804 if (fNextTrackerPositionSaved || fPrevTrackerPositionSaved) 805 return; 806 807 fNextRef = fPrevRef = *ref; 808 809 fNextTrackerPositionSaved = GetTrackerWindowFile(&fNextRef, true); 810 fPrevTrackerPositionSaved = GetTrackerWindowFile(&fPrevRef, false); 811 } 812 813 814 void 815 TMailWindow::SetOriginatingWindow(BWindow *window) 816 { 817 delete fOriginatingWindow; 818 fOriginatingWindow = new BMessenger(window); 819 } 820 821 822 void 823 TMailWindow::SetTrackerSelectionToCurrent() 824 { 825 BMessage setSelection(B_SET_PROPERTY); 826 setSelection.AddSpecifier("Selection"); 827 setSelection.AddRef("data", fRef); 828 829 fTrackerMessenger.SendMessage(&setSelection); 830 } 831 832 833 void 834 TMailWindow::SetCurrentMessageRead(bool read) 835 { 836 BNode node(fRef); 837 if (node.InitCheck() == B_NO_ERROR) { 838 BString status; 839 if (ReadAttrString(&node, B_MAIL_ATTR_STATUS, &status) == B_NO_ERROR) { 840 if (read && !status.ICompare("New")) { 841 node.RemoveAttr(B_MAIL_ATTR_STATUS); 842 WriteAttrString(&node, B_MAIL_ATTR_STATUS, "Read"); 843 } 844 if (!read && !status.ICompare("Read")) { 845 node.RemoveAttr(B_MAIL_ATTR_STATUS); 846 WriteAttrString(&node, B_MAIL_ATTR_STATUS, "New"); 847 } 848 } 849 } 850 } 851 852 853 void 854 TMailWindow::FrameResized(float width, float height) 855 { 856 fContentView->FrameResized(width, height); 857 } 858 859 860 void 861 TMailWindow::MenusBeginning() 862 { 863 bool enable; 864 int32 finish = 0; 865 int32 start = 0; 866 BTextView *textView; 867 868 if (!fIncoming) { 869 bool gotToField = fHeaderView->fTo->Text()[0] != 0; 870 bool gotCcField = fHeaderView->fCc->Text()[0] != 0; 871 bool gotBccField = fHeaderView->fBcc->Text()[0] != 0; 872 bool gotSubjectField = fHeaderView->fSubject->Text()[0] != 0; 873 bool gotText = fContentView->fTextView->Text()[0] != 0; 874 fSendNow->SetEnabled(gotToField || gotBccField); 875 fSendLater->SetEnabled(fChanged && (gotToField || gotCcField 876 || gotBccField || gotSubjectField || gotText)); 877 878 be_clipboard->Lock(); 879 fPaste->SetEnabled(be_clipboard->Data()->HasData("text/plain", 880 B_MIME_TYPE) 881 && (fEnclosuresView == NULL || !fEnclosuresView->fList->IsFocus())); 882 be_clipboard->Unlock(); 883 884 fQuote->SetEnabled(false); 885 fRemoveQuote->SetEnabled(false); 886 887 fAdd->SetEnabled(true); 888 fRemove->SetEnabled(fEnclosuresView != NULL 889 && fEnclosuresView->fList->CurrentSelection() >= 0); 890 } else { 891 if (fResending) { 892 enable = strlen(fHeaderView->fTo->Text()); 893 fSendNow->SetEnabled(enable); 894 // fSendLater->SetEnabled(enable); 895 896 if (fHeaderView->fTo->HasFocus()) { 897 textView = fHeaderView->fTo->TextView(); 898 textView->GetSelection(&start, &finish); 899 900 fCut->SetEnabled(start != finish); 901 be_clipboard->Lock(); 902 fPaste->SetEnabled(be_clipboard->Data()->HasData( 903 "text/plain", B_MIME_TYPE)); 904 be_clipboard->Unlock(); 905 } else { 906 fCut->SetEnabled(false); 907 fPaste->SetEnabled(false); 908 } 909 } else { 910 fCut->SetEnabled(false); 911 fPaste->SetEnabled(false); 912 913 if (!fTrackerMessenger.IsValid()) { 914 fNextMsg->SetEnabled(false); 915 fPrevMsg->SetEnabled(false); 916 } 917 } 918 } 919 920 fPrint->SetEnabled(fContentView->fTextView->TextLength()); 921 922 textView = dynamic_cast<BTextView *>(CurrentFocus()); 923 if (textView != NULL 924 && dynamic_cast<TTextControl *>(textView->Parent()) != NULL) { 925 // one of To:, Subject:, Account:, Cc:, Bcc: 926 textView->GetSelection(&start, &finish); 927 } else if (fContentView->fTextView->IsFocus()) { 928 fContentView->fTextView->GetSelection(&start, &finish); 929 if (!fIncoming) { 930 fQuote->SetEnabled(true); 931 fRemoveQuote->SetEnabled(true); 932 } 933 } 934 935 fCopy->SetEnabled(start != finish); 936 if (!fIncoming) 937 fCut->SetEnabled(start != finish); 938 939 // Undo stuff 940 bool isRedo = false; 941 undo_state undoState = B_UNDO_UNAVAILABLE; 942 943 BTextView *focusTextView = dynamic_cast<BTextView *>(CurrentFocus()); 944 if (focusTextView != NULL) 945 undoState = focusTextView->UndoState(&isRedo); 946 947 // fUndo->SetLabel((isRedo) 948 // ? kRedoStrings[undoState] : kUndoStrings[undoState]); 949 fUndo->SetEnabled(undoState != B_UNDO_UNAVAILABLE); 950 } 951 952 953 void 954 TMailWindow::MessageReceived(BMessage *msg) 955 { 956 switch (msg->what) { 957 case FIELD_CHANGED: 958 { 959 int32 prevState = fFieldState; 960 int32 fieldMask = msg->FindInt32("bitmask"); 961 void *source; 962 963 if (msg->FindPointer("source", &source) == B_OK) { 964 int32 length; 965 966 if (fieldMask == FIELD_BODY) 967 length = ((TTextView *)source)->TextLength(); 968 else 969 length = ((BComboBox *)source)->TextView()->TextLength(); 970 971 if (length) 972 fFieldState |= fieldMask; 973 else 974 fFieldState &= ~fieldMask; 975 } 976 977 // Has anything changed? 978 if (prevState != fFieldState || !fChanged) { 979 // Change Buttons to reflect this 980 if (fSaveButton) 981 fSaveButton->SetEnabled(fFieldState); 982 if (fPrintButton) 983 fPrintButton->SetEnabled(fFieldState); 984 if (fSendButton) 985 fSendButton->SetEnabled((fFieldState & FIELD_TO) 986 || (fFieldState & FIELD_BCC)); 987 } 988 fChanged = true; 989 990 // Update title bar if "subject" has changed 991 if (!fIncoming && fieldMask & FIELD_SUBJECT) { 992 // If no subject, set to "Mail" 993 if (!fHeaderView->fSubject->TextView()->TextLength()) 994 SetTitle("Mail"); 995 else 996 SetTitle(fHeaderView->fSubject->Text()); 997 } 998 break; 999 } 1000 case LIST_INVOKED: 1001 PostMessage(msg, fEnclosuresView); 1002 break; 1003 1004 case CHANGE_FONT: 1005 PostMessage(msg, fContentView); 1006 break; 1007 1008 case M_NEW: 1009 { 1010 BMessage message(M_NEW); 1011 message.AddInt32("type", msg->what); 1012 be_app->PostMessage(&message); 1013 break; 1014 } 1015 1016 case M_SPAM_BUTTON: 1017 { 1018 /* 1019 A popup from a button is good only when the behavior has some 1020 consistency and there is some visual indication that a menu 1021 will be shown when clicked. A workable implementation would 1022 have an extra button attached to the main one which has a 1023 downward-pointing arrow. Mozilla Thunderbird's 'Get Mail' 1024 button is a good example of this. 1025 1026 TODO: Replace this code with a split toolbar button 1027 */ 1028 uint32 buttons; 1029 if (msg->FindInt32("buttons", (int32 *)&buttons) == B_OK 1030 && buttons == B_SECONDARY_MOUSE_BUTTON) { 1031 BPopUpMenu menu("Spam Actions", false, false); 1032 for (int i = 0; i < 4; i++) 1033 menu.AddItem(new BMenuItem(kSpamMenuItemTextArray[i], 1034 new BMessage(M_TRAIN_SPAM_AND_DELETE + i))); 1035 1036 BPoint where; 1037 msg->FindPoint("where", &where); 1038 BMenuItem *item; 1039 if ((item = menu.Go(where, false, false)) != NULL) 1040 PostMessage(item->Message()); 1041 break; 1042 } else { 1043 // Default action for left clicking on the spam button. 1044 PostMessage(new BMessage(M_TRAIN_SPAM_AND_DELETE)); 1045 } 1046 break; 1047 } 1048 1049 case M_TRAIN_SPAM_AND_DELETE: 1050 PostMessage(M_DELETE_NEXT); 1051 case M_TRAIN_SPAM: 1052 TrainMessageAs("Spam"); 1053 break; 1054 1055 case M_UNTRAIN: 1056 TrainMessageAs("Uncertain"); 1057 break; 1058 1059 case M_TRAIN_GENUINE: 1060 TrainMessageAs("Genuine"); 1061 break; 1062 1063 case M_REPLY: 1064 { 1065 // TODO: This needs removed in favor of a split toolbar button. 1066 // See comments for Spam button 1067 uint32 buttons; 1068 if (msg->FindInt32("buttons", (int32 *)&buttons) == B_OK 1069 && buttons == B_SECONDARY_MOUSE_BUTTON) { 1070 BPopUpMenu menu("Reply To", false, false); 1071 menu.AddItem(new BMenuItem(B_TRANSLATE("Reply"), 1072 new BMessage(M_REPLY))); 1073 menu.AddItem(new BMenuItem(B_TRANSLATE("Reply to sender"), 1074 new BMessage(M_REPLY_TO_SENDER))); 1075 menu.AddItem(new BMenuItem(B_TRANSLATE("Reply to all"), 1076 new BMessage(M_REPLY_ALL))); 1077 1078 BPoint where; 1079 msg->FindPoint("where", &where); 1080 1081 BMenuItem *item; 1082 if ((item = menu.Go(where, false, false)) != NULL) { 1083 item->SetTarget(this); 1084 PostMessage(item->Message()); 1085 } 1086 break; 1087 } 1088 // Fall through 1089 } 1090 case M_FORWARD: 1091 { 1092 // TODO: This needs removed in favor of a split toolbar button. 1093 // See comments for Spam button 1094 uint32 buttons; 1095 if (msg->FindInt32("buttons", (int32 *)&buttons) == B_OK 1096 && buttons == B_SECONDARY_MOUSE_BUTTON) { 1097 BPopUpMenu menu("Forward", false, false); 1098 menu.AddItem(new BMenuItem(B_TRANSLATE("Forward"), 1099 new BMessage(M_FORWARD))); 1100 menu.AddItem(new BMenuItem( 1101 B_TRANSLATE("Forward without attachments"), 1102 new BMessage(M_FORWARD_WITHOUT_ATTACHMENTS))); 1103 1104 BPoint where; 1105 msg->FindPoint("where", &where); 1106 1107 BMenuItem *item; 1108 if ((item = menu.Go(where, false, false)) != NULL) { 1109 item->SetTarget(this); 1110 PostMessage(item->Message()); 1111 } 1112 break; 1113 } 1114 } 1115 1116 // Fall Through 1117 case M_REPLY_ALL: 1118 case M_REPLY_TO_SENDER: 1119 case M_FORWARD_WITHOUT_ATTACHMENTS: 1120 case M_RESEND: 1121 case M_COPY_TO_NEW: 1122 { 1123 BMessage message(M_NEW); 1124 message.AddRef("ref", fRef); 1125 message.AddPointer("window", this); 1126 message.AddInt32("type", msg->what); 1127 be_app->PostMessage(&message); 1128 break; 1129 } 1130 case M_DELETE: 1131 case M_DELETE_PREV: 1132 case M_DELETE_NEXT: 1133 { 1134 if (msg->what == M_DELETE_NEXT && (modifiers() & B_SHIFT_KEY)) 1135 msg->what = M_DELETE_PREV; 1136 1137 bool foundRef = false; 1138 entry_ref nextRef; 1139 if ((msg->what == M_DELETE_PREV || msg->what == M_DELETE_NEXT) 1140 && fRef != NULL) { 1141 // Find the next message that should be displayed 1142 nextRef = *fRef; 1143 foundRef = GetTrackerWindowFile(&nextRef, 1144 msg->what == M_DELETE_NEXT); 1145 } 1146 if (fIncoming && fAutoMarkRead) 1147 SetCurrentMessageRead(); 1148 1149 if (!fTrackerMessenger.IsValid() || !fIncoming) { 1150 // Not associated with a tracker window. Create a new 1151 // messenger and ask the tracker to delete this entry 1152 if (fDraft || fIncoming) { 1153 BMessenger tracker("application/x-vnd.Be-TRAK"); 1154 if (tracker.IsValid()) { 1155 BMessage msg('Ttrs'); 1156 msg.AddRef("refs", fRef); 1157 tracker.SendMessage(&msg); 1158 } else { 1159 (new BAlert("", 1160 B_TRANSLATE("Need Tracker to move items to trash"), 1161 B_TRANSLATE("Sorry")))->Go(); 1162 } 1163 } 1164 } else { 1165 // This is associated with a tracker window. Ask the 1166 // window to delete this entry. Do it this way if we 1167 // can instead of the above way because it doesn't reset 1168 // the selection (even though we set selection below, this 1169 // still causes problems). 1170 BMessage delmsg(B_DELETE_PROPERTY); 1171 BMessage entryspec('sref'); 1172 entryspec.AddRef("refs", fRef); 1173 entryspec.AddString("property", "Entry"); 1174 delmsg.AddSpecifier(&entryspec); 1175 fTrackerMessenger.SendMessage(&delmsg); 1176 } 1177 1178 // If the next file was found, open it. If it was not, 1179 // we have no choice but to close this window. 1180 if (foundRef) { 1181 TMailWindow *window 1182 = static_cast<TMailApp *>(be_app)->FindWindow(nextRef); 1183 if (window == NULL) 1184 OpenMessage(&nextRef, fHeaderView->fCharacterSetUserSees); 1185 else 1186 window->Activate(); 1187 1188 SetTrackerSelectionToCurrent(); 1189 1190 if (window == NULL) 1191 break; 1192 } 1193 1194 fSent = true; 1195 BMessage msg(B_CLOSE_REQUESTED); 1196 PostMessage(&msg); 1197 break; 1198 } 1199 1200 case M_CLOSE_READ: 1201 { 1202 BMessage message(B_CLOSE_REQUESTED); 1203 message.AddString("status", "Read"); 1204 PostMessage(&message); 1205 break; 1206 } 1207 case M_CLOSE_SAVED: 1208 { 1209 BMessage message(B_QUIT_REQUESTED); 1210 message.AddString("status", "Saved"); 1211 PostMessage(&message); 1212 break; 1213 } 1214 case kMsgQuitAndKeepAllStatus: 1215 sKeepStatusOnQuit = true; 1216 be_app->PostMessage(B_QUIT_REQUESTED); 1217 break; 1218 case M_CLOSE_SAME: 1219 { 1220 BMessage message(B_QUIT_REQUESTED); 1221 message.AddString("status", ""); 1222 message.AddString("same", ""); 1223 PostMessage(&message); 1224 break; 1225 } 1226 case M_CLOSE_CUSTOM: 1227 if (msg->HasString("status")) { 1228 const char *str; 1229 msg->FindString("status", (const char**) &str); 1230 BMessage message(B_CLOSE_REQUESTED); 1231 message.AddString("status", str); 1232 PostMessage(&message); 1233 } else { 1234 BRect r = Frame(); 1235 r.left += ((r.Width() - STATUS_WIDTH) / 2); 1236 r.right = r.left + STATUS_WIDTH; 1237 r.top += 40; 1238 r.bottom = r.top + STATUS_HEIGHT; 1239 1240 BString string = "could not read"; 1241 BNode node(fRef); 1242 if (node.InitCheck() == B_OK) 1243 ReadAttrString(&node, B_MAIL_ATTR_STATUS, &string); 1244 1245 new TStatusWindow(r, this, string.String()); 1246 } 1247 break; 1248 1249 case M_STATUS: 1250 { 1251 const char* attribute; 1252 if (msg->FindString("attribute", &attribute) != B_OK) 1253 break; 1254 1255 BMessage message(B_CLOSE_REQUESTED); 1256 message.AddString("status", attribute); 1257 PostMessage(&message); 1258 break; 1259 } 1260 case M_HEADER: 1261 { 1262 bool showHeader = !fHeader->IsMarked(); 1263 fHeader->SetMarked(showHeader); 1264 1265 BMessage message(M_HEADER); 1266 message.AddBool("header", showHeader); 1267 PostMessage(&message, fContentView->fTextView); 1268 break; 1269 } 1270 case M_RAW: 1271 { 1272 bool raw = !(fRaw->IsMarked()); 1273 fRaw->SetMarked(raw); 1274 BMessage message(M_RAW); 1275 message.AddBool("raw", raw); 1276 PostMessage(&message, fContentView->fTextView); 1277 break; 1278 } 1279 case M_SEND_NOW: 1280 case M_SAVE_AS_DRAFT: 1281 Send(msg->what == M_SEND_NOW); 1282 break; 1283 1284 case M_SAVE: 1285 { 1286 char *str; 1287 if (msg->FindString("address", (const char **)&str) == B_NO_ERROR) { 1288 char *arg = (char *)malloc(strlen("META:email ") 1289 + strlen(str) + 1); 1290 BVolumeRoster volumeRoster; 1291 BVolume volume; 1292 volumeRoster.GetBootVolume(&volume); 1293 1294 BQuery query; 1295 query.SetVolume(&volume); 1296 sprintf(arg, "META:email=%s", str); 1297 query.SetPredicate(arg); 1298 query.Fetch(); 1299 1300 BEntry entry; 1301 if (query.GetNextEntry(&entry) == B_NO_ERROR) { 1302 BMessenger tracker("application/x-vnd.Be-TRAK"); 1303 if (tracker.IsValid()) { 1304 entry_ref ref; 1305 entry.GetRef(&ref); 1306 1307 BMessage open(B_REFS_RECEIVED); 1308 open.AddRef("refs", &ref); 1309 tracker.SendMessage(&open); 1310 } 1311 } else { 1312 sprintf(arg, "META:email %s", str); 1313 status_t result = be_roster->Launch("application/x-person", 1314 1, &arg); 1315 1316 if (result != B_NO_ERROR) 1317 (new BAlert("", B_TRANSLATE( 1318 "Sorry, could not find an application that " 1319 "supports the 'Person' data type."), 1320 B_TRANSLATE("OK")))->Go(); 1321 } 1322 free(arg); 1323 } 1324 break; 1325 } 1326 1327 case M_PRINT_SETUP: 1328 PrintSetup(); 1329 break; 1330 1331 case M_PRINT: 1332 Print(); 1333 break; 1334 1335 case M_SELECT: 1336 break; 1337 1338 case M_FIND: 1339 FindWindow::Find(this); 1340 break; 1341 1342 case M_FIND_AGAIN: 1343 FindWindow::FindAgain(this); 1344 break; 1345 1346 case M_QUOTE: 1347 case M_REMOVE_QUOTE: 1348 PostMessage(msg->what, fContentView); 1349 break; 1350 1351 case M_RANDOM_SIG: 1352 { 1353 BList sigList; 1354 BMessage *message; 1355 1356 BVolume volume; 1357 BVolumeRoster().GetBootVolume(&volume); 1358 1359 BQuery query; 1360 query.SetVolume(&volume); 1361 1362 char predicate[128]; 1363 sprintf(predicate, "%s = *", INDEX_SIGNATURE); 1364 query.SetPredicate(predicate); 1365 query.Fetch(); 1366 1367 BEntry entry; 1368 while (query.GetNextEntry(&entry) == B_NO_ERROR) { 1369 BFile file(&entry, O_RDONLY); 1370 if (file.InitCheck() == B_NO_ERROR) { 1371 entry_ref ref; 1372 entry.GetRef(&ref); 1373 1374 message = new BMessage(M_SIGNATURE); 1375 message->AddRef("ref", &ref); 1376 sigList.AddItem(message); 1377 } 1378 } 1379 if (sigList.CountItems() > 0) { 1380 srand(time(0)); 1381 PostMessage((BMessage *)sigList.ItemAt(rand() 1382 % sigList.CountItems())); 1383 1384 for (int32 i = 0; (message = (BMessage *)sigList.ItemAt(i)) 1385 != NULL; i++) 1386 delete message; 1387 } 1388 break; 1389 } 1390 case M_SIGNATURE: 1391 { 1392 BMessage message(*msg); 1393 PostMessage(&message, fContentView); 1394 fSigAdded = true; 1395 break; 1396 } 1397 case M_SIG_MENU: 1398 { 1399 TMenu *menu; 1400 BMenuItem *item; 1401 menu = new TMenu("Add Signature", INDEX_SIGNATURE, M_SIGNATURE, 1402 true); 1403 1404 BPoint where; 1405 bool open_anyway = true; 1406 1407 if (msg->FindPoint("where", &where) != B_OK) { 1408 BRect bounds; 1409 bounds = fSigButton->Bounds(); 1410 where = fSigButton->ConvertToScreen(BPoint( 1411 (bounds.right - bounds.left) / 2, 1412 (bounds.bottom - bounds.top) / 2)); 1413 } else if (msg->FindInt32("buttons") == B_SECONDARY_MOUSE_BUTTON) { 1414 open_anyway = false; 1415 } 1416 1417 if ((item = menu->Go(where, false, open_anyway)) != NULL) { 1418 item->SetTarget(this); 1419 (dynamic_cast<BInvoker *>(item))->Invoke(); 1420 } 1421 delete menu; 1422 break; 1423 } 1424 1425 case M_ADD: 1426 if (!fPanel) { 1427 BMessenger me(this); 1428 BMessage msg(REFS_RECEIVED); 1429 fPanel = new BFilePanel(B_OPEN_PANEL, &me, &fOpenFolder, false, 1430 true, &msg); 1431 } else if (!fPanel->Window()->IsHidden()) { 1432 fPanel->Window()->Activate(); 1433 } 1434 1435 if (fPanel->Window()->IsHidden()) 1436 fPanel->Window()->Show(); 1437 break; 1438 1439 case M_REMOVE: 1440 PostMessage(msg->what, fEnclosuresView); 1441 break; 1442 1443 case CHARSET_CHOICE_MADE: 1444 if (fIncoming && !fResending) { 1445 // The user wants to see the message they are reading (not 1446 // composing) displayed with a different kind of character set 1447 // for decoding. Reload the whole message and redisplay. For 1448 // messages which are being composed, the character set is 1449 // retrieved from the header view when it is needed. 1450 1451 entry_ref fileRef = *fRef; 1452 int32 characterSet; 1453 msg->FindInt32("charset", &characterSet); 1454 OpenMessage(&fileRef, characterSet); 1455 } 1456 break; 1457 1458 case REFS_RECEIVED: 1459 AddEnclosure(msg); 1460 break; 1461 1462 // 1463 // Navigation Messages 1464 // 1465 case M_UNREAD: 1466 SetCurrentMessageRead(false); 1467 _UpdateReadButton(); 1468 break; 1469 case M_READ: 1470 SetCurrentMessageRead(); 1471 msg->what = M_NEXTMSG; 1472 case M_PREVMSG: 1473 case M_NEXTMSG: 1474 if (fRef != NULL) { 1475 entry_ref nextRef = *fRef; 1476 if (GetTrackerWindowFile(&nextRef, (msg->what == M_NEXTMSG))) { 1477 TMailWindow *window 1478 = static_cast<TMailApp *>(be_app)->FindWindow(nextRef); 1479 if (window == NULL) { 1480 if (fAutoMarkRead) 1481 SetCurrentMessageRead(); 1482 OpenMessage(&nextRef, 1483 fHeaderView->fCharacterSetUserSees); 1484 } else { 1485 window->Activate(); 1486 1487 //fSent = true; 1488 BMessage msg(B_CLOSE_REQUESTED); 1489 PostMessage(&msg); 1490 } 1491 1492 SetTrackerSelectionToCurrent(); 1493 } else { 1494 beep(); 1495 } 1496 } 1497 break; 1498 case M_SAVE_POSITION: 1499 if (fRef != NULL) 1500 SaveTrackerPosition(fRef); 1501 break; 1502 1503 case RESET_BUTTONS: 1504 fChanged = false; 1505 fFieldState = 0; 1506 if (fHeaderView->fTo->TextView()->TextLength()) 1507 fFieldState |= FIELD_TO; 1508 if (fHeaderView->fSubject->TextView()->TextLength()) 1509 fFieldState |= FIELD_SUBJECT; 1510 if (fHeaderView->fCc->TextView()->TextLength()) 1511 fFieldState |= FIELD_CC; 1512 if (fHeaderView->fBcc->TextView()->TextLength()) 1513 fFieldState |= FIELD_BCC; 1514 if (fContentView->fTextView->TextLength()) 1515 fFieldState |= FIELD_BODY; 1516 1517 if (fSaveButton) 1518 fSaveButton->SetEnabled(false); 1519 if (fPrintButton) 1520 fPrintButton->SetEnabled(fFieldState); 1521 if (fSendButton) 1522 fSendButton->SetEnabled((fFieldState & FIELD_TO) 1523 || (fFieldState & FIELD_BCC)); 1524 break; 1525 1526 case M_CHECK_SPELLING: 1527 if (gDictCount == 0) 1528 // Give the application time to init and load dictionaries. 1529 snooze (1500000); 1530 if (!gDictCount) { 1531 beep(); 1532 (new BAlert("", 1533 B_TRANSLATE("Mail couldn't find its dictionary."), 1534 B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, 1535 B_OFFSET_SPACING, B_STOP_ALERT))->Go(); 1536 } else { 1537 fSpelling->SetMarked(!fSpelling->IsMarked()); 1538 fContentView->fTextView->EnableSpellCheck( 1539 fSpelling->IsMarked()); 1540 } 1541 break; 1542 1543 case M_EDIT_QUERIES: 1544 { 1545 BPath path; 1546 if (_GetQueryPath(&path) < B_OK) 1547 break; 1548 1549 // the user used this command, make sure the folder actually 1550 // exists - if it didn't inform the user what to do with it 1551 BEntry entry(path.Path()); 1552 bool showAlert = false; 1553 if (!entry.Exists()) { 1554 showAlert = true; 1555 create_directory(path.Path(), 0777); 1556 } 1557 1558 BEntry folderEntry; 1559 if (folderEntry.SetTo(path.Path()) == B_OK 1560 && folderEntry.Exists()) { 1561 BMessage openFolderCommand(B_REFS_RECEIVED); 1562 BMessenger tracker("application/x-vnd.Be-TRAK"); 1563 1564 entry_ref ref; 1565 folderEntry.GetRef(&ref); 1566 openFolderCommand.AddRef("refs", &ref); 1567 tracker.SendMessage(&openFolderCommand); 1568 } 1569 1570 if (showAlert) { 1571 // just some patience before Tracker pops up the folder 1572 snooze(250000); 1573 BAlert* alert = new BAlert("helpful message", 1574 "Put your favorite e-mail queries and query " 1575 "templates in this folder.", 1576 "OK", NULL, NULL, B_WIDTH_AS_USUAL, B_IDEA_ALERT); 1577 alert->Go(NULL); 1578 } 1579 1580 break; 1581 } 1582 1583 case B_PATH_MONITOR: 1584 _RebuildQueryMenu(); 1585 break; 1586 1587 default: 1588 BWindow::MessageReceived(msg); 1589 } 1590 } 1591 1592 1593 void 1594 TMailWindow::AddEnclosure(BMessage *msg) 1595 { 1596 if (fEnclosuresView == NULL && !fIncoming) { 1597 BRect r; 1598 r.left = 0; 1599 r.top = fHeaderView->Frame().bottom - 1; 1600 r.right = Frame().Width() + 2; 1601 r.bottom = r.top + ENCLOSURES_HEIGHT; 1602 1603 fEnclosuresView = new TEnclosuresView(r, Frame()); 1604 AddChild(fEnclosuresView, fContentView); 1605 fContentView->ResizeBy(0, -ENCLOSURES_HEIGHT); 1606 fContentView->MoveBy(0, ENCLOSURES_HEIGHT); 1607 } 1608 1609 if (fEnclosuresView == NULL) 1610 return; 1611 1612 if (msg && msg->HasRef("refs")) { 1613 // Add enclosure to view 1614 PostMessage(msg, fEnclosuresView); 1615 1616 fChanged = true; 1617 BEntry entry; 1618 entry_ref ref; 1619 msg->FindRef("refs", &ref); 1620 entry.SetTo(&ref); 1621 entry.GetParent(&entry); 1622 entry.GetRef(&fOpenFolder); 1623 } 1624 } 1625 1626 1627 bool 1628 TMailWindow::QuitRequested() 1629 { 1630 int32 result; 1631 1632 if ((!fIncoming || (fIncoming && fResending)) && fChanged && !fSent 1633 && (strlen(fHeaderView->fTo->Text()) 1634 || strlen(fHeaderView->fSubject->Text()) 1635 || (fHeaderView->fCc && strlen(fHeaderView->fCc->Text())) 1636 || (fHeaderView->fBcc && strlen(fHeaderView->fBcc->Text())) 1637 || (strlen(fContentView->fTextView->Text()) && (!fStartingText 1638 || (fStartingText 1639 && strcmp(fContentView->fTextView->Text(), fStartingText)))) 1640 || (fEnclosuresView != NULL 1641 && fEnclosuresView->fList->CountItems()))) { 1642 if (fResending) { 1643 BAlert *alert = new BAlert("", B_TRANSLATE( 1644 "Do you wish to send this message before closing?"), 1645 B_TRANSLATE("Discard"), 1646 B_TRANSLATE("Cancel"), 1647 B_TRANSLATE("Send"), 1648 B_WIDTH_AS_USUAL, B_OFFSET_SPACING, B_WARNING_ALERT); 1649 alert->SetShortcut(0, 'd'); 1650 alert->SetShortcut(1, B_ESCAPE); 1651 result = alert->Go(); 1652 1653 switch (result) { 1654 case 0: // Discard 1655 break; 1656 case 1: // Cancel 1657 return false; 1658 case 2: // Send 1659 Send(true); 1660 break; 1661 } 1662 } else { 1663 BAlert *alert = new BAlert("", 1664 B_TRANSLATE("Do you wish to save this message as a draft " 1665 "before closing?"), 1666 B_TRANSLATE("Don't save"), 1667 B_TRANSLATE("Cancel"), 1668 B_TRANSLATE("Save"), 1669 B_WIDTH_AS_USUAL, B_OFFSET_SPACING, B_WARNING_ALERT); 1670 alert->SetShortcut(0, 'd'); 1671 alert->SetShortcut(1, B_ESCAPE); 1672 result = alert->Go(); 1673 switch (result) { 1674 case 0: // Don't Save 1675 break; 1676 case 1: // Cancel 1677 return false; 1678 case 2: // Save 1679 Send(false); 1680 break; 1681 } 1682 } 1683 } 1684 1685 BMessage message(WINDOW_CLOSED); 1686 message.AddInt32("kind", MAIL_WINDOW); 1687 message.AddPointer("window", this); 1688 be_app->PostMessage(&message); 1689 1690 if (CurrentMessage() && CurrentMessage()->HasString("status")) { 1691 // User explicitly requests a status to set this message to. 1692 if (!CurrentMessage()->HasString("same")) { 1693 const char *status = CurrentMessage()->FindString("status"); 1694 if (status != NULL) { 1695 BNode node(fRef); 1696 if (node.InitCheck() == B_NO_ERROR) { 1697 node.RemoveAttr(B_MAIL_ATTR_STATUS); 1698 WriteAttrString(&node, B_MAIL_ATTR_STATUS, status); 1699 } 1700 } 1701 } 1702 } else if (fRef != NULL && !sKeepStatusOnQuit) { 1703 // ...Otherwise just set the message read 1704 if (fAutoMarkRead) 1705 SetCurrentMessageRead(); 1706 } 1707 1708 BPrivate::BPathMonitor::StopWatching(BMessenger(this, this)); 1709 1710 return true; 1711 } 1712 1713 1714 void 1715 TMailWindow::Show() 1716 { 1717 if (Lock()) { 1718 if (!fResending && (fIncoming || fReplying)) { 1719 fContentView->fTextView->MakeFocus(true); 1720 } else { 1721 BTextView *textView = fHeaderView->fTo->TextView(); 1722 fHeaderView->fTo->MakeFocus(true); 1723 textView->Select(0, textView->TextLength()); 1724 } 1725 Unlock(); 1726 } 1727 BWindow::Show(); 1728 } 1729 1730 1731 void 1732 TMailWindow::Zoom(BPoint /*pos*/, float /*x*/, float /*y*/) 1733 { 1734 float height; 1735 float width; 1736 BScreen screen(this); 1737 BRect r; 1738 BRect s_frame = screen.Frame(); 1739 1740 r = Frame(); 1741 width = 80 * fApp->ContentFont().StringWidth("M") 1742 + (r.Width() - fContentView->fTextView->Bounds().Width() + 6); 1743 if (width > (s_frame.Width() - 8)) 1744 width = s_frame.Width() - 8; 1745 1746 height = max_c(fContentView->fTextView->CountLines(), 20) 1747 * fContentView->fTextView->LineHeight(0) 1748 + (r.Height() - fContentView->fTextView->Bounds().Height()); 1749 if (height > (s_frame.Height() - 29)) 1750 height = s_frame.Height() - 29; 1751 1752 r.right = r.left + width; 1753 r.bottom = r.top + height; 1754 1755 if (abs((int)(Frame().Width() - r.Width())) < 5 1756 && abs((int)(Frame().Height() - r.Height())) < 5) { 1757 r = fZoom; 1758 } else { 1759 fZoom = Frame(); 1760 s_frame.InsetBy(6, 6); 1761 1762 if (r.Width() > s_frame.Width()) 1763 r.right = r.left + s_frame.Width(); 1764 if (r.Height() > s_frame.Height()) 1765 r.bottom = r.top + s_frame.Height(); 1766 1767 if (r.right > s_frame.right) 1768 { 1769 r.left -= r.right - s_frame.right; 1770 r.right = s_frame.right; 1771 } 1772 if (r.bottom > s_frame.bottom) 1773 { 1774 r.top -= r.bottom - s_frame.bottom; 1775 r.bottom = s_frame.bottom; 1776 } 1777 if (r.left < s_frame.left) 1778 { 1779 r.right += s_frame.left - r.left; 1780 r.left = s_frame.left; 1781 } 1782 if (r.top < s_frame.top) 1783 { 1784 r.bottom += s_frame.top - r.top; 1785 r.top = s_frame.top; 1786 } 1787 } 1788 1789 ResizeTo(r.Width(), r.Height()); 1790 MoveTo(r.LeftTop()); 1791 } 1792 1793 1794 void 1795 TMailWindow::WindowActivated(bool status) 1796 { 1797 if (status) { 1798 BAutolock locker(sWindowListLock); 1799 sWindowList.RemoveItem(this); 1800 sWindowList.AddItem(this, 0); 1801 } 1802 } 1803 1804 1805 void 1806 TMailWindow::Forward(entry_ref *ref, TMailWindow *window, 1807 bool includeAttachments) 1808 { 1809 BEmailMessage *mail = window->Mail(); 1810 if (mail == NULL) 1811 return; 1812 1813 uint32 useAccountFrom = fApp->UseAccountFrom(); 1814 1815 fMail = mail->ForwardMessage(useAccountFrom == ACCOUNT_FROM_MAIL, 1816 includeAttachments); 1817 1818 BFile file(ref, O_RDONLY); 1819 if (file.InitCheck() < B_NO_ERROR) 1820 return; 1821 1822 fHeaderView->fSubject->SetText(fMail->Subject()); 1823 1824 // set mail account 1825 1826 if (useAccountFrom == ACCOUNT_FROM_MAIL) { 1827 fHeaderView->fChain = fMail->Account(); 1828 1829 BMenu *menu = fHeaderView->fAccountMenu; 1830 for (int32 i = menu->CountItems(); i-- > 0;) { 1831 BMenuItem *item = menu->ItemAt(i); 1832 BMessage *msg; 1833 if (item && (msg = item->Message()) != NULL 1834 && msg->FindInt32("id") == fHeaderView->fChain) 1835 item->SetMarked(true); 1836 } 1837 } 1838 1839 if (fMail->CountComponents() > 1) { 1840 // if there are any enclosures to be added, first add the enclosures 1841 // view to the window 1842 AddEnclosure(NULL); 1843 if (fEnclosuresView) 1844 fEnclosuresView->AddEnclosuresFromMail(fMail); 1845 } 1846 1847 fContentView->fTextView->LoadMessage(fMail, false, NULL); 1848 fChanged = false; 1849 fFieldState = 0; 1850 } 1851 1852 1853 class HorizontalLine : public BView { 1854 public: 1855 HorizontalLine(BRect rect) 1856 : 1857 BView (rect, NULL, B_FOLLOW_ALL, B_WILL_DRAW) {} 1858 1859 virtual void Draw(BRect rect) 1860 { 1861 FillRect(rect, B_SOLID_HIGH); 1862 } 1863 }; 1864 1865 1866 void 1867 TMailWindow::Print() 1868 { 1869 BPrintJob print(Title()); 1870 1871 if (!fApp->HasPrintSettings()) { 1872 if (print.Settings()) { 1873 fApp->SetPrintSettings(print.Settings()); 1874 } else { 1875 PrintSetup(); 1876 if (!fApp->HasPrintSettings()) 1877 return; 1878 } 1879 } 1880 1881 print.SetSettings(new BMessage(fApp->PrintSettings())); 1882 1883 if (print.ConfigJob() == B_OK) { 1884 int32 curPage = 1; 1885 int32 lastLine = 0; 1886 BTextView header_view(print.PrintableRect(), "header", 1887 print.PrintableRect().OffsetByCopy(BPoint( 1888 -print.PrintableRect().left, -print.PrintableRect().top)), 1889 B_FOLLOW_ALL_SIDES); 1890 1891 //---------Init the header fields 1892 #define add_header_field(field) { \ 1893 /*header_view.SetFontAndColor(be_bold_font);*/ \ 1894 header_view.Insert(fHeaderView->field->Label()); \ 1895 header_view.Insert(" "); \ 1896 /*header_view.SetFontAndColor(be_plain_font);*/ \ 1897 header_view.Insert(fHeaderView->field->Text()); \ 1898 header_view.Insert("\n"); \ 1899 } 1900 1901 add_header_field(fSubject); 1902 add_header_field(fTo); 1903 if ((fHeaderView->fCc != NULL) 1904 && (strcmp(fHeaderView->fCc->Text(),"") != 0)) 1905 add_header_field(fCc); 1906 1907 if (fHeaderView->fDate != NULL) 1908 header_view.Insert(fHeaderView->fDate->Text()); 1909 1910 int32 maxLine = fContentView->fTextView->CountLines(); 1911 BRect pageRect = print.PrintableRect(); 1912 BRect curPageRect = pageRect; 1913 1914 print.BeginJob(); 1915 float header_height = header_view.TextHeight(0, 1916 header_view.CountLines()); 1917 1918 BRect rect(0, 0, pageRect.Width(), header_height); 1919 BBitmap bmap(rect, B_BITMAP_ACCEPTS_VIEWS, B_RGBA32); 1920 bmap.Lock(); 1921 bmap.AddChild(&header_view); 1922 print.DrawView(&header_view, rect, BPoint(0.0, 0.0)); 1923 HorizontalLine line(BRect(0, 0, pageRect.right, 0)); 1924 bmap.AddChild(&line); 1925 print.DrawView(&line, line.Bounds(), BPoint(0, header_height + 1)); 1926 bmap.Unlock(); 1927 header_height += 5; 1928 1929 do { 1930 int32 lineOffset = fContentView->fTextView->OffsetAt(lastLine); 1931 curPageRect.OffsetTo(0, 1932 fContentView->fTextView->PointAt(lineOffset).y); 1933 1934 int32 fromLine = lastLine; 1935 lastLine = fContentView->fTextView->LineAt( 1936 BPoint(0.0, curPageRect.bottom - ((curPage == 1) 1937 ? header_height : 0))); 1938 1939 float curPageHeight = fContentView->fTextView-> 1940 TextHeight(fromLine, lastLine) + ((curPage == 1) 1941 ? header_height : 0); 1942 1943 if (curPageHeight > pageRect.Height()) { 1944 curPageHeight = fContentView->fTextView->TextHeight( 1945 fromLine, --lastLine) + ((curPage == 1) 1946 ? header_height : 0); 1947 } 1948 curPageRect.bottom = curPageRect.top + curPageHeight - 1.0; 1949 1950 if ((curPage >= print.FirstPage()) 1951 && (curPage <= print.LastPage())) { 1952 print.DrawView(fContentView->fTextView, curPageRect, 1953 BPoint(0.0, (curPage == 1) ? header_height : 0.0)); 1954 print.SpoolPage(); 1955 } 1956 1957 curPageRect = pageRect; 1958 lastLine++; 1959 curPage++; 1960 1961 } while (print.CanContinue() && lastLine < maxLine); 1962 1963 print.CommitJob(); 1964 bmap.RemoveChild(&header_view); 1965 bmap.RemoveChild(&line); 1966 } 1967 } 1968 1969 1970 void 1971 TMailWindow::PrintSetup() 1972 { 1973 BPrintJob printJob("mail_print"); 1974 1975 if (fApp->HasPrintSettings()) { 1976 BMessage printSettings = fApp->PrintSettings(); 1977 printJob.SetSettings(new BMessage(printSettings)); 1978 } 1979 1980 if (printJob.ConfigPage() == B_OK) 1981 fApp->SetPrintSettings(printJob.Settings()); 1982 } 1983 1984 1985 void 1986 TMailWindow::SetTo(const char *mailTo, const char *subject, const char *ccTo, 1987 const char *bccTo, const BString *body, BMessage *enclosures) 1988 { 1989 Lock(); 1990 1991 if (mailTo && mailTo[0]) 1992 fHeaderView->fTo->SetText(mailTo); 1993 if (subject && subject[0]) 1994 fHeaderView->fSubject->SetText(subject); 1995 if (ccTo && ccTo[0]) 1996 fHeaderView->fCc->SetText(ccTo); 1997 if (bccTo && bccTo[0]) 1998 fHeaderView->fBcc->SetText(bccTo); 1999 2000 if (body && body->Length()) { 2001 fContentView->fTextView->SetText(body->String(), body->Length()); 2002 fContentView->fTextView->GoToLine(0); 2003 } 2004 2005 if (enclosures && enclosures->HasRef("refs")) 2006 AddEnclosure(enclosures); 2007 2008 Unlock(); 2009 } 2010 2011 2012 void 2013 TMailWindow::CopyMessage(entry_ref *ref, TMailWindow *src) 2014 { 2015 BNode file(ref); 2016 if (file.InitCheck() == B_OK) { 2017 BString string; 2018 if (fHeaderView->fTo 2019 && ReadAttrString(&file, B_MAIL_ATTR_TO, &string) == B_OK) 2020 fHeaderView->fTo->SetText(string.String()); 2021 2022 if (fHeaderView->fSubject 2023 && ReadAttrString(&file, B_MAIL_ATTR_SUBJECT, &string) == B_OK) 2024 fHeaderView->fSubject->SetText(string.String()); 2025 2026 if (fHeaderView->fCc 2027 && ReadAttrString(&file, B_MAIL_ATTR_CC, &string) == B_OK) 2028 fHeaderView->fCc->SetText(string.String()); 2029 } 2030 2031 TTextView *text = src->fContentView->fTextView; 2032 text_run_array *style = text->RunArray(0, text->TextLength()); 2033 2034 fContentView->fTextView->SetText(text->Text(), text->TextLength(), style); 2035 2036 free(style); 2037 } 2038 2039 2040 void 2041 TMailWindow::Reply(entry_ref *ref, TMailWindow *window, uint32 type) 2042 { 2043 const char *notImplementedString = "<Not Yet Implemented>"; 2044 2045 fRepliedMail = *ref; 2046 SetOriginatingWindow(window); 2047 2048 BEmailMessage *mail = window->Mail(); 2049 if (mail == NULL) 2050 return; 2051 2052 if (type == M_REPLY_ALL) 2053 type = B_MAIL_REPLY_TO_ALL; 2054 else if (type == M_REPLY_TO_SENDER) 2055 type = B_MAIL_REPLY_TO_SENDER; 2056 else 2057 type = B_MAIL_REPLY_TO; 2058 2059 uint32 useAccountFrom = fApp->UseAccountFrom(); 2060 2061 fMail = mail->ReplyMessage(mail_reply_to_mode(type), 2062 useAccountFrom == ACCOUNT_FROM_MAIL, QUOTE); 2063 2064 // set header fields 2065 fHeaderView->fTo->SetText(fMail->To()); 2066 fHeaderView->fCc->SetText(fMail->CC()); 2067 fHeaderView->fSubject->SetText(fMail->Subject()); 2068 2069 int32 chainID; 2070 BFile file(window->fRef, B_READ_ONLY); 2071 if (file.ReadAttr("MAIL:reply_with", B_INT32_TYPE, 0, &chainID, 4) < B_OK) 2072 chainID = -1; 2073 2074 // set mail account 2075 2076 if ((useAccountFrom == ACCOUNT_FROM_MAIL) || (chainID > -1)) { 2077 if (useAccountFrom == ACCOUNT_FROM_MAIL) 2078 fHeaderView->fChain = fMail->Account(); 2079 else 2080 fHeaderView->fChain = chainID; 2081 2082 BMenu *menu = fHeaderView->fAccountMenu; 2083 for (int32 i = menu->CountItems(); i-- > 0;) { 2084 BMenuItem *item = menu->ItemAt(i); 2085 BMessage *msg; 2086 if (item && (msg = item->Message()) != NULL 2087 && msg->FindInt32("id") == fHeaderView->fChain) 2088 item->SetMarked(true); 2089 } 2090 } 2091 2092 // create preamble string 2093 2094 BString replyPreamble = fApp->ReplyPreamble(); 2095 2096 char preamble[1024]; 2097 const char* from = replyPreamble.String(); 2098 char* to = preamble; 2099 2100 while (*from) { 2101 if (*from == '%') { 2102 // insert special content 2103 int32 length; 2104 2105 switch (*++from) { 2106 case 'n': // full name 2107 { 2108 BString fullName(mail->From()); 2109 if (fullName.Length() <= 0) 2110 fullName = "No-From-Address-Available"; 2111 2112 extract_address_name(fullName); 2113 length = fullName.Length(); 2114 memcpy(to, fullName.String(), length); 2115 to += length; 2116 break; 2117 } 2118 2119 case 'e': // eMail address 2120 { 2121 const char *address = mail->From(); 2122 if (address == NULL) 2123 address = "<unknown>"; 2124 length = strlen(address); 2125 memcpy(to, address, length); 2126 to += length; 2127 break; 2128 } 2129 2130 case 'd': // date 2131 { 2132 const char *date = mail->Date(); 2133 if (date == NULL) 2134 date = "No-Date-Available"; 2135 length = strlen(date); 2136 memcpy(to, date, length); 2137 to += length; 2138 break; 2139 } 2140 2141 // ToDo: parse stuff! 2142 case 'f': // first name 2143 case 'l': // last name 2144 length = strlen(notImplementedString); 2145 memcpy(to, notImplementedString, length); 2146 to += length; 2147 break; 2148 2149 default: // Sometimes a % is just a %. 2150 *to++ = *from; 2151 } 2152 } else if (*from == '\\') { 2153 switch (*++from) { 2154 case 'n': 2155 *to++ = '\n'; 2156 break; 2157 2158 default: 2159 *to++ = *from; 2160 } 2161 } else 2162 *to++ = *from; 2163 2164 from++; 2165 } 2166 *to = '\0'; 2167 2168 // insert (if selection) or load (if whole mail) message text into text view 2169 2170 int32 finish, start; 2171 window->fContentView->fTextView->GetSelection(&start, &finish); 2172 if (start != finish) { 2173 char *text = (char *)malloc(finish - start + 1); 2174 if (text == NULL) 2175 return; 2176 2177 window->fContentView->fTextView->GetText(start, finish - start, text); 2178 if (text[strlen(text) - 1] != '\n') { 2179 text[strlen(text)] = '\n'; 2180 finish++; 2181 } 2182 fContentView->fTextView->SetText(text, finish - start); 2183 free(text); 2184 2185 finish = fContentView->fTextView->CountLines(); 2186 for (int32 loop = 0; loop < finish; loop++) { 2187 fContentView->fTextView->GoToLine(loop); 2188 fContentView->fTextView->Insert((const char *)QUOTE); 2189 } 2190 2191 if (fApp->ColoredQuotes()) { 2192 const BFont *font = fContentView->fTextView->Font(); 2193 int32 length = fContentView->fTextView->TextLength(); 2194 2195 TextRunArray style(length / 8 + 8); 2196 2197 FillInQuoteTextRuns(fContentView->fTextView, NULL, 2198 fContentView->fTextView->Text(), length, font, &style.Array(), 2199 style.MaxEntries()); 2200 2201 fContentView->fTextView->SetRunArray(0, length, &style.Array()); 2202 } 2203 2204 fContentView->fTextView->GoToLine(0); 2205 if (strlen(preamble) > 0) 2206 fContentView->fTextView->Insert(preamble); 2207 } else { 2208 fContentView->fTextView->LoadMessage(mail, true, preamble); 2209 } 2210 2211 fReplying = true; 2212 } 2213 2214 2215 status_t 2216 TMailWindow::Send(bool now) 2217 { 2218 uint32 characterSetToUse = fApp->MailCharacterSet(); 2219 mail_encoding encodingForBody = quoted_printable; 2220 mail_encoding encodingForHeaders = quoted_printable; 2221 2222 if (!now) { 2223 status_t status = SaveAsDraft(); 2224 if (status != B_OK) { 2225 beep(); 2226 (new BAlert("", B_TRANSLATE("E-mail draft could not be saved!"), 2227 B_TRANSLATE("OK")))->Go(); 2228 } 2229 return status; 2230 } 2231 2232 if (fHeaderView != NULL) 2233 characterSetToUse = fHeaderView->fCharacterSetUserSees; 2234 2235 // Set up the encoding to use for converting binary to printable ASCII. 2236 // Normally this will be quoted printable, but for some old software, 2237 // particularly Japanese stuff, they only understand base64. They also 2238 // prefer it for the smaller size. Later on this will be reduced to 7bit 2239 // if the encoded text is just 7bit characters. 2240 if (characterSetToUse == B_SJIS_CONVERSION 2241 || characterSetToUse == B_EUC_CONVERSION) 2242 encodingForBody = base64; 2243 else if (characterSetToUse == B_JIS_CONVERSION 2244 || characterSetToUse == B_MAIL_US_ASCII_CONVERSION 2245 || characterSetToUse == B_ISO1_CONVERSION 2246 || characterSetToUse == B_EUC_KR_CONVERSION) 2247 encodingForBody = eight_bit; 2248 2249 // Using quoted printable headers on almost completely non-ASCII Japanese 2250 // is a waste of time. Besides, some stupid cell phone services need 2251 // base64 in the headers. 2252 if (characterSetToUse == B_SJIS_CONVERSION 2253 || characterSetToUse == B_EUC_CONVERSION 2254 || characterSetToUse == B_JIS_CONVERSION 2255 || characterSetToUse == B_EUC_KR_CONVERSION) 2256 encodingForHeaders = base64; 2257 2258 // Count the number of characters in the message body which aren't in the 2259 // currently selected character set. Also see if the resulting encoded 2260 // text can safely use 7 bit characters. 2261 if (fContentView->fTextView->TextLength() > 0) { 2262 // First do a trial encoding with the user's character set. 2263 int32 converterState = 0; 2264 int32 originalLength; 2265 BString tempString; 2266 int32 tempStringLength; 2267 char* tempStringPntr; 2268 originalLength = fContentView->fTextView->TextLength(); 2269 tempStringLength = originalLength * 6; 2270 // Some character sets bloat up on escape codes 2271 tempStringPntr = tempString.LockBuffer (tempStringLength); 2272 if (tempStringPntr != NULL && mail_convert_from_utf8(characterSetToUse, 2273 fContentView->fTextView->Text(), &originalLength, 2274 tempStringPntr, &tempStringLength, &converterState, 2275 0x1A /* used for unknown characters */) == B_OK) { 2276 // Check for any characters which don't fit in a 7 bit encoding. 2277 int i; 2278 bool has8Bit = false; 2279 for (i = 0; i < tempStringLength; i++) { 2280 if (tempString[i] == 0 || (tempString[i] & 0x80)) { 2281 has8Bit = true; 2282 break; 2283 } 2284 } 2285 if (!has8Bit) 2286 encodingForBody = seven_bit; 2287 tempString.UnlockBuffer (tempStringLength); 2288 2289 // Count up the number of unencoded characters and warn the user 2290 if (fApp->WarnAboutUnencodableCharacters()) { 2291 // TODO: ideally, the encoding should be silently changed to 2292 // one that can express this character 2293 int32 offset = 0; 2294 int count = 0; 2295 while (offset >= 0) { 2296 offset = tempString.FindFirst (0x1A, offset); 2297 if (offset >= 0) { 2298 count++; 2299 offset++; 2300 // Don't get stuck finding the same character again. 2301 } 2302 } 2303 if (count > 0) { 2304 int32 userAnswer; 2305 BString messageString; 2306 BString countString; 2307 countString << count; 2308 messageString << B_TRANSLATE("Your main text contains %ld" 2309 " unencodable characters. Perhaps a different " 2310 "character set would work better? Hit Send to send it " 2311 "anyway " 2312 "(a substitute character will be used in place of " 2313 "the unencodable ones), or choose Cancel to go back " 2314 "and try fixing it up."); 2315 messageString.ReplaceFirst("%ld", countString); 2316 userAnswer = (new BAlert("Question", messageString.String(), 2317 B_TRANSLATE("Send"), 2318 B_TRANSLATE("Cancel"), 2319 NULL, B_WIDTH_AS_USUAL, B_OFFSET_SPACING, 2320 B_WARNING_ALERT))->Go(); 2321 if (userAnswer == 1) { 2322 // Cancel was picked. 2323 return -1; 2324 } 2325 } 2326 } 2327 } 2328 } 2329 2330 Hide(); 2331 // depending on the system (and I/O) load, this could take a while 2332 // but the user shouldn't be left waiting 2333 2334 status_t result; 2335 2336 if (fResending) { 2337 BFile file(fRef, O_RDONLY); 2338 result = file.InitCheck(); 2339 if (result == B_OK) { 2340 BEmailMessage mail(&file); 2341 mail.SetTo(fHeaderView->fTo->Text(), characterSetToUse, 2342 encodingForHeaders); 2343 2344 if (fHeaderView->fChain != ~0L) 2345 mail.SendViaAccount(fHeaderView->fChain); 2346 2347 result = mail.Send(now); 2348 } 2349 } else { 2350 if (fMail == NULL) 2351 // the mail will be deleted when the window is closed 2352 fMail = new BEmailMessage; 2353 2354 // Had an embarrassing bug where replying to a message and clearing the 2355 // CC field meant that it got sent out anyway, so pass in empty strings 2356 // when changing the header to force it to remove the header. 2357 2358 fMail->SetTo(fHeaderView->fTo->Text(), characterSetToUse, 2359 encodingForHeaders); 2360 fMail->SetSubject(fHeaderView->fSubject->Text(), characterSetToUse, 2361 encodingForHeaders); 2362 fMail->SetCC(fHeaderView->fCc->Text(), characterSetToUse, 2363 encodingForHeaders); 2364 fMail->SetBCC(fHeaderView->fBcc->Text()); 2365 2366 //--- Add X-Mailer field 2367 { 2368 // get app version 2369 version_info info; 2370 memset(&info, 0, sizeof(version_info)); 2371 2372 app_info appInfo; 2373 if (be_app->GetAppInfo(&appInfo) == B_OK) { 2374 BFile file(&appInfo.ref, B_READ_ONLY); 2375 if (file.InitCheck() == B_OK) { 2376 BAppFileInfo appFileInfo(&file); 2377 if (appFileInfo.InitCheck() == B_OK) 2378 appFileInfo.GetVersionInfo(&info, B_APP_VERSION_KIND); 2379 } 2380 } 2381 2382 char versionString[255]; 2383 sprintf(versionString, 2384 "Mail/Haiku %ld.%ld.%ld", 2385 info.major, info.middle, info.minor); 2386 fMail->SetHeaderField("X-Mailer", versionString); 2387 } 2388 2389 /****/ 2390 2391 // the content text is always added to make sure there is a mail body 2392 fMail->SetBodyTextTo(""); 2393 fContentView->fTextView->AddAsContent(fMail, fApp->WrapMode(), 2394 characterSetToUse, encodingForBody); 2395 2396 if (fEnclosuresView != NULL) { 2397 TListItem *item; 2398 int32 index = 0; 2399 while ((item = (TListItem *)fEnclosuresView->fList->ItemAt(index++)) 2400 != NULL) { 2401 if (item->Component()) 2402 continue; 2403 2404 // leave out missing enclosures 2405 BEntry entry(item->Ref()); 2406 if (!entry.Exists()) 2407 continue; 2408 2409 fMail->Attach(item->Ref(), fApp->AttachAttributes()); 2410 } 2411 } 2412 if (fHeaderView->fChain != ~0L) 2413 fMail->SendViaAccount(fHeaderView->fChain); 2414 2415 result = fMail->Send(now); 2416 2417 if (fReplying) { 2418 // Set status of the replied mail 2419 2420 BNode node(&fRepliedMail); 2421 if (node.InitCheck() >= B_OK) { 2422 if (fOriginatingWindow) { 2423 BMessage msg(M_SAVE_POSITION), reply; 2424 fOriginatingWindow->SendMessage(&msg, &reply); 2425 } 2426 WriteAttrString(&node, B_MAIL_ATTR_STATUS, "Replied"); 2427 } 2428 } 2429 } 2430 2431 bool close = false; 2432 BString errorMessage; 2433 2434 switch (result) { 2435 case B_OK: 2436 close = true; 2437 fSent = true; 2438 2439 // If it's a draft, remove the draft file 2440 if (fDraft) { 2441 BEntry entry(fRef); 2442 entry.Remove(); 2443 } 2444 break; 2445 2446 case B_MAIL_NO_DAEMON: 2447 { 2448 close = true; 2449 fSent = true; 2450 2451 int32 start = (new BAlert("no daemon", 2452 B_TRANSLATE("The mail_daemon is not running. The message is " 2453 "queued and will be sent when the mail_daemon is started."), 2454 B_TRANSLATE("Start now"), B_TRANSLATE("OK")))->Go(); 2455 2456 if (start == 0) { 2457 result = be_roster->Launch("application/x-vnd.Be-POST"); 2458 if (result == B_OK) { 2459 BMailDaemon::SendQueuedMail(); 2460 } else { 2461 errorMessage 2462 << B_TRANSLATE("The mail_daemon could not be " 2463 "started:\n\t") 2464 << strerror(result); 2465 } 2466 } 2467 break; 2468 } 2469 2470 // case B_MAIL_UNKNOWN_HOST: 2471 // case B_MAIL_ACCESS_ERROR: 2472 // sprintf(errorMessage, 2473 // "An error occurred trying to connect with the SMTP " 2474 // "host. Check your SMTP host name."); 2475 // break; 2476 // 2477 // case B_MAIL_NO_RECIPIENT: 2478 // sprintf(errorMessage, 2479 // "You must have either a \"To\" or \"Bcc\" recipient."); 2480 // break; 2481 2482 default: 2483 errorMessage << "An error occurred trying to send mail:\n\t" 2484 << strerror(result); 2485 break; 2486 } 2487 2488 if (result != B_NO_ERROR && result != B_MAIL_NO_DAEMON) { 2489 beep(); 2490 (new BAlert("", errorMessage.String(), B_TRANSLATE("OK")))->Go(); 2491 } 2492 if (close) { 2493 PostMessage(B_QUIT_REQUESTED); 2494 } else { 2495 // The window was hidden earlier 2496 Show(); 2497 } 2498 2499 return result; 2500 } 2501 2502 2503 status_t 2504 TMailWindow::SaveAsDraft() 2505 { 2506 status_t status; 2507 BPath draftPath; 2508 BDirectory dir; 2509 BFile draft; 2510 uint32 flags = 0; 2511 2512 if (fDraft) { 2513 if ((status = draft.SetTo(fRef, 2514 B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE)) != B_OK) { 2515 return status; 2516 } 2517 } else { 2518 // Get the user home directory 2519 if ((status = find_directory(B_USER_DIRECTORY, &draftPath)) != B_OK) 2520 return status; 2521 2522 // Append the relative path of the draft directory 2523 draftPath.Append(kDraftPath); 2524 2525 // Create the file 2526 status = dir.SetTo(draftPath.Path()); 2527 switch (status) { 2528 // Create the directory if it does not exist 2529 case B_ENTRY_NOT_FOUND: 2530 if ((status = dir.CreateDirectory(draftPath.Path(), &dir)) 2531 != B_OK) 2532 return status; 2533 case B_OK: 2534 { 2535 char fileName[512], *eofn; 2536 int32 i; 2537 2538 // save as some version of the message's subject 2539 strncpy(fileName, fHeaderView->fSubject->Text(), 2540 sizeof(fileName)-10); 2541 fileName[sizeof(fileName)-10]='\0'; 2542 // terminate like strncpy doesn't 2543 eofn = fileName + strlen(fileName); 2544 2545 // convert /, \ and : to - 2546 for (char *bad = fileName; (bad = strchr(bad, '/')) != NULL; 2547 ++bad) *bad = '-'; 2548 for (char *bad = fileName; (bad = strchr(bad, '\\')) != NULL; 2549 ++bad) *bad = '-'; 2550 for (char *bad = fileName; (bad = strchr(bad, ':')) != NULL; 2551 ++bad) *bad = '-'; 2552 2553 // Create the file; if the name exists, find a unique name 2554 flags = B_WRITE_ONLY | B_CREATE_FILE | B_FAIL_IF_EXISTS; 2555 for (i = 1; (status = draft.SetTo(&dir, fileName, flags)) 2556 != B_OK; i++) { 2557 if (status != B_FILE_EXISTS) 2558 return status; 2559 sprintf(eofn, "%ld", i); 2560 } 2561 2562 // Cache the ref 2563 if (fRef == NULL) 2564 fRef = new entry_ref; 2565 BEntry entry(&dir, fileName); 2566 entry.GetRef(fRef); 2567 break; 2568 } 2569 default: 2570 return status; 2571 } 2572 } 2573 2574 // Write the content of the message 2575 draft.Write(fContentView->fTextView->Text(), 2576 fContentView->fTextView->TextLength()); 2577 2578 // 2579 // Add the header stuff as attributes 2580 // 2581 WriteAttrString(&draft, B_MAIL_ATTR_NAME, fHeaderView->fTo->Text()); 2582 WriteAttrString(&draft, B_MAIL_ATTR_TO, fHeaderView->fTo->Text()); 2583 WriteAttrString(&draft, B_MAIL_ATTR_SUBJECT, fHeaderView->fSubject->Text()); 2584 if (fHeaderView->fCc != NULL) 2585 WriteAttrString(&draft, B_MAIL_ATTR_CC, fHeaderView->fCc->Text()); 2586 if (fHeaderView->fBcc != NULL) 2587 WriteAttrString(&draft, "MAIL:bcc", fHeaderView->fBcc->Text()); 2588 2589 // Add the draft attribute for indexing 2590 uint32 draftAttr = true; 2591 draft.WriteAttr("MAIL:draft", B_INT32_TYPE, 0, &draftAttr, sizeof(uint32)); 2592 2593 // Add Attachment paths in attribute 2594 if (fEnclosuresView != NULL) { 2595 TListItem *item; 2596 BPath path; 2597 BString pathStr; 2598 2599 for (int32 i = 0; (item = (TListItem *) 2600 fEnclosuresView->fList->ItemAt(i)) != NULL; i++) { 2601 if (i > 0) 2602 pathStr.Append(":"); 2603 2604 BEntry entry(item->Ref(), true); 2605 if (!entry.Exists()) 2606 continue; 2607 2608 entry.GetPath(&path); 2609 pathStr.Append(path.Path()); 2610 } 2611 if (pathStr.Length()) 2612 WriteAttrString(&draft, "MAIL:attachments", pathStr.String()); 2613 } 2614 2615 // Set the MIME Type of the file 2616 BNodeInfo info(&draft); 2617 info.SetType(kDraftType); 2618 2619 fDraft = true; 2620 fChanged = false; 2621 2622 return B_OK; 2623 } 2624 2625 2626 status_t 2627 TMailWindow::TrainMessageAs(const char *CommandWord) 2628 { 2629 status_t errorCode = -1; 2630 char errorString[1500]; 2631 BEntry fileEntry; 2632 BPath filePath; 2633 BMessage replyMessage; 2634 BMessage scriptingMessage; 2635 team_id serverTeam; 2636 2637 if (fRef == NULL) 2638 goto ErrorExit; // Need to have a real file and name. 2639 errorCode = fileEntry.SetTo(fRef, true /* traverse */); 2640 if (errorCode != B_OK) 2641 goto ErrorExit; 2642 errorCode = fileEntry.GetPath(&filePath); 2643 if (errorCode != B_OK) 2644 goto ErrorExit; 2645 fileEntry.Unset(); 2646 2647 // Get a connection to the spam database server. Launch if needed. 2648 2649 if (!fMessengerToSpamServer.IsValid()) { 2650 // Make sure the server is running. 2651 if (!be_roster->IsRunning (kSpamServerSignature)) { 2652 errorCode = be_roster->Launch (kSpamServerSignature); 2653 if (errorCode != B_OK) { 2654 BPath path; 2655 entry_ref ref; 2656 directory_which places[] 2657 = {B_COMMON_BIN_DIRECTORY, B_BEOS_BIN_DIRECTORY}; 2658 for (int32 i = 0; i < 2; i++) { 2659 find_directory(places[i],&path); 2660 path.Append("spamdbm"); 2661 if (!BEntry(path.Path()).Exists()) 2662 continue; 2663 get_ref_for_path(path.Path(),&ref); 2664 if ((errorCode = be_roster->Launch (&ref)) == B_OK) 2665 break; 2666 } 2667 if (errorCode != B_OK) 2668 goto ErrorExit; 2669 } 2670 } 2671 2672 // Set up the messenger to the database server. 2673 errorCode = B_SERVER_NOT_FOUND; 2674 serverTeam = be_roster->TeamFor(kSpamServerSignature); 2675 if (serverTeam < 0) 2676 goto ErrorExit; 2677 2678 fMessengerToSpamServer = BMessenger (kSpamServerSignature, serverTeam, 2679 &errorCode); 2680 2681 if (!fMessengerToSpamServer.IsValid()) 2682 goto ErrorExit; 2683 } 2684 2685 // Ask the server to train on the message. Give it the command word and 2686 // the absolute path name to use. 2687 2688 scriptingMessage.MakeEmpty(); 2689 scriptingMessage.what = B_SET_PROPERTY; 2690 scriptingMessage.AddSpecifier(CommandWord); 2691 errorCode = scriptingMessage.AddData("data", B_STRING_TYPE, 2692 filePath.Path(), strlen(filePath.Path()) + 1, false /* fixed size */); 2693 if (errorCode != B_OK) 2694 goto ErrorExit; 2695 replyMessage.MakeEmpty(); 2696 errorCode = fMessengerToSpamServer.SendMessage(&scriptingMessage, 2697 &replyMessage); 2698 if (errorCode != B_OK 2699 || replyMessage.FindInt32("error", &errorCode) != B_OK 2700 || errorCode != B_OK) 2701 goto ErrorExit; // Classification failed in one of many ways. 2702 2703 SetTitleForMessage(); 2704 // Update window title to show new spam classification. 2705 return B_OK; 2706 2707 ErrorExit: 2708 beep(); 2709 sprintf(errorString, "Unable to train the message file \"%s\" as %s. " 2710 "Possibly useful error code: %s (%ld).", 2711 filePath.Path(), CommandWord, strerror (errorCode), errorCode); 2712 (new BAlert("", errorString, 2713 B_TRANSLATE("OK")))->Go(); 2714 return errorCode; 2715 } 2716 2717 2718 void 2719 TMailWindow::SetTitleForMessage() 2720 { 2721 // 2722 // Figure out the title of this message and set the title bar 2723 // 2724 BString title = "Mail"; 2725 2726 if (fIncoming) { 2727 if (fMail->GetName(&title) == B_OK) 2728 title << ": \"" << fMail->Subject() << "\""; 2729 else 2730 title = fMail->Subject(); 2731 2732 if (fApp->ShowSpamGUI() && fRef != NULL) { 2733 BString classification; 2734 BNode node (fRef); 2735 char numberString [30]; 2736 BString oldTitle (title); 2737 float spamRatio; 2738 if (node.InitCheck() != B_OK || node.ReadAttrString 2739 ("MAIL:classification", &classification) != B_OK) 2740 classification = "Unrated"; 2741 if (classification != "Spam" && classification != "Genuine") { 2742 // Uncertain, Unrated and other unknown classes, show the ratio. 2743 if (node.InitCheck() == B_OK && sizeof (spamRatio) == 2744 node.ReadAttr("MAIL:ratio_spam", B_FLOAT_TYPE, 0, 2745 &spamRatio, sizeof (spamRatio))) { 2746 sprintf (numberString, "%.4f", spamRatio); 2747 classification << " " << numberString; 2748 } 2749 } 2750 title = ""; 2751 title << "[" << classification << "] " << oldTitle; 2752 } 2753 } 2754 SetTitle(title.String()); 2755 } 2756 2757 2758 // 2759 // Open *another* message in the existing mail window. Some code here is 2760 // duplicated from various constructors. 2761 // The duplicated code should be in a private initializer method -- axeld. 2762 // 2763 2764 status_t 2765 TMailWindow::OpenMessage(entry_ref *ref, uint32 characterSetForDecoding) 2766 { 2767 // 2768 // Set some references to the email file 2769 // 2770 delete fRef; 2771 fRef = new entry_ref(*ref); 2772 2773 if (fStartingText) { 2774 free(fStartingText); 2775 fStartingText = NULL; 2776 } 2777 fPrevTrackerPositionSaved = false; 2778 fNextTrackerPositionSaved = false; 2779 2780 fContentView->fTextView->StopLoad(); 2781 delete fMail; 2782 2783 BFile file(fRef, B_READ_ONLY); 2784 status_t err = file.InitCheck(); 2785 if (err != B_OK) 2786 return err; 2787 2788 char mimeType[256]; 2789 BNodeInfo fileInfo(&file); 2790 fileInfo.GetType(mimeType); 2791 2792 // Check if it's a draft file, which contains only the text, and has the 2793 // from, to, bcc, attachments listed as attributes. 2794 if (!strcmp(kDraftType, mimeType)) { 2795 BNode node(fRef); 2796 off_t size; 2797 BString string; 2798 2799 fMail = new BEmailMessage; // Not really used much, but still needed. 2800 2801 // Load the raw UTF-8 text from the file. 2802 file.GetSize(&size); 2803 fContentView->fTextView->SetText(&file, 0, size); 2804 2805 // Restore Fields from attributes 2806 if (ReadAttrString(&node, B_MAIL_ATTR_TO, &string) == B_OK) 2807 fHeaderView->fTo->SetText(string.String()); 2808 if (ReadAttrString(&node, B_MAIL_ATTR_SUBJECT, &string) == B_OK) 2809 fHeaderView->fSubject->SetText(string.String()); 2810 if (ReadAttrString(&node, B_MAIL_ATTR_CC, &string) == B_OK) 2811 fHeaderView->fCc->SetText(string.String()); 2812 if (ReadAttrString(&node, "MAIL:bcc", &string) == B_OK) 2813 fHeaderView->fBcc->SetText(string.String()); 2814 2815 // Restore attachments 2816 if (ReadAttrString(&node, "MAIL:attachments", &string) == B_OK) { 2817 BMessage msg(REFS_RECEIVED); 2818 entry_ref enc_ref; 2819 2820 char *s = strtok((char *)string.String(), ":"); 2821 while (s) { 2822 BEntry entry(s, true); 2823 if (entry.Exists()) { 2824 entry.GetRef(&enc_ref); 2825 msg.AddRef("refs", &enc_ref); 2826 } 2827 s = strtok(NULL, ":"); 2828 } 2829 AddEnclosure(&msg); 2830 } 2831 PostMessage(RESET_BUTTONS); 2832 fIncoming = false; 2833 fDraft = true; 2834 } else { 2835 // A real mail message, parse its headers to get from, to, etc. 2836 fMail = new BEmailMessage(fRef, characterSetForDecoding); 2837 fIncoming = true; 2838 fHeaderView->LoadMessage(fMail); 2839 } 2840 2841 err = fMail->InitCheck(); 2842 if (err < B_OK) { 2843 delete fMail; 2844 fMail = NULL; 2845 return err; 2846 } 2847 2848 SetTitleForMessage(); 2849 2850 if (fIncoming) { 2851 // 2852 // Put the addresses in the 'Save Address' Menu 2853 // 2854 BMenuItem *item; 2855 while ((item = fSaveAddrMenu->RemoveItem(0L)) != NULL) 2856 delete item; 2857 2858 // create the list of addresses 2859 2860 BList addressList; 2861 get_address_list(addressList, fMail->To(), extract_address); 2862 get_address_list(addressList, fMail->CC(), extract_address); 2863 get_address_list(addressList, fMail->From(), extract_address); 2864 get_address_list(addressList, fMail->ReplyTo(), extract_address); 2865 2866 BMessage *msg; 2867 2868 for (int32 i = addressList.CountItems(); i-- > 0;) { 2869 char *address = (char *)addressList.RemoveItem(0L); 2870 2871 // insert the new address in alphabetical order 2872 int32 index = 0; 2873 while ((item = fSaveAddrMenu->ItemAt(index)) != NULL) { 2874 if (!strcmp(address, item->Label())) { 2875 // item already in list 2876 goto skip; 2877 } 2878 2879 if (strcmp(address, item->Label()) < 0) 2880 break; 2881 2882 index++; 2883 } 2884 2885 msg = new BMessage(M_SAVE); 2886 msg->AddString("address", address); 2887 fSaveAddrMenu->AddItem(new BMenuItem(address, msg), index); 2888 2889 skip: 2890 free(address); 2891 } 2892 2893 // 2894 // Clear out existing contents of text view. 2895 // 2896 fContentView->fTextView->SetText("", (int32)0); 2897 2898 fContentView->fTextView->LoadMessage(fMail, false, NULL); 2899 2900 if (fApp->ShowButtonBar()) 2901 _UpdateReadButton(); 2902 } 2903 2904 return B_OK; 2905 } 2906 2907 2908 TMailWindow * 2909 TMailWindow::FrontmostWindow() 2910 { 2911 BAutolock locker(sWindowListLock); 2912 if (sWindowList.CountItems() > 0) 2913 return (TMailWindow *)sWindowList.ItemAt(0); 2914 2915 return NULL; 2916 } 2917 2918 2919 /* 2920 // Copied from src/kits/tracker/FindPanel.cpp. 2921 uint32 2922 TMailWindow::InitialMode(const BNode *node) 2923 { 2924 if (!node || node->InitCheck() != B_OK) 2925 return kByNameItem; 2926 2927 uint32 result; 2928 if (node->ReadAttr(kAttrQueryInitialMode, B_INT32_TYPE, 0, 2929 (int32 *)&result, sizeof(int32)) <= 0) 2930 return kByNameItem; 2931 2932 return result; 2933 } 2934 2935 2936 // Copied from src/kits/tracker/FindPanel.cpp. 2937 int32 2938 TMailWindow::InitialAttrCount(const BNode *node) 2939 { 2940 if (!node || node->InitCheck() != B_OK) 2941 return 1; 2942 2943 int32 result; 2944 if (node->ReadAttr(kAttrQueryInitialNumAttrs, B_INT32_TYPE, 0, 2945 &result, sizeof(int32)) <= 0) 2946 return 1; 2947 2948 return result; 2949 }*/ 2950 2951 2952 // #pragma mark - 2953 2954 2955 void 2956 TMailWindow::_UpdateSizeLimits() 2957 { 2958 float minWidth, maxWidth, minHeight, maxHeight; 2959 GetSizeLimits(&minWidth, &maxWidth, &minHeight, &maxHeight); 2960 2961 float height; 2962 fMenuBar->GetPreferredSize(&minWidth, &height); 2963 2964 minHeight = height; 2965 2966 if (fButtonBar) { 2967 fButtonBar->GetPreferredSize(&minWidth, &height); 2968 minHeight += height; 2969 } else { 2970 minWidth = WIND_WIDTH; 2971 } 2972 2973 minHeight += fHeaderView->Bounds().Height() + ENCLOSURES_HEIGHT + 60; 2974 2975 SetSizeLimits(minWidth, RIGHT_BOUNDARY, minHeight, RIGHT_BOUNDARY); 2976 } 2977 2978 2979 status_t 2980 TMailWindow::_GetQueryPath(BPath* queryPath) const 2981 { 2982 // get the user home directory and from there the query folder 2983 status_t ret = find_directory(B_USER_DIRECTORY, queryPath); 2984 if (ret == B_OK) 2985 ret = queryPath->Append(kQueriesDirectory); 2986 2987 return ret; 2988 } 2989 2990 2991 void 2992 TMailWindow::_RebuildQueryMenu(bool firstTime) 2993 { 2994 while (fQueryMenu->ItemAt(0)) { 2995 BMenuItem* item = fQueryMenu->RemoveItem((int32)0); 2996 delete item; 2997 } 2998 2999 fQueryMenu->AddItem(new BMenuItem(B_TRANSLATE("Edit queries" 3000 B_UTF8_ELLIPSIS), 3001 new BMessage(M_EDIT_QUERIES), 'E', B_SHIFT_KEY)); 3002 3003 bool queryItemsAdded = false; 3004 3005 BPath queryPath; 3006 if (_GetQueryPath(&queryPath) < B_OK) 3007 return; 3008 3009 BDirectory queryDir(queryPath.Path()); 3010 3011 if (firstTime) { 3012 BPrivate::BPathMonitor::StartWatching(queryPath.Path(), 3013 B_WATCH_RECURSIVELY, BMessenger(this, this)); 3014 } 3015 3016 // If we find the named query, add it to the menu. 3017 BEntry entry; 3018 while (queryDir.GetNextEntry(&entry) == B_OK) { 3019 char name[B_FILE_NAME_LENGTH + 1]; 3020 entry.GetName(name); 3021 3022 char* queryString = _BuildQueryString(&entry); 3023 if (queryString == NULL) 3024 continue; 3025 3026 queryItemsAdded = true; 3027 3028 QueryMenu* queryMenu = new QueryMenu(name, false); 3029 queryMenu->SetTargetForItems(be_app); 3030 queryMenu->SetPredicate(queryString); 3031 fQueryMenu->AddItem(queryMenu); 3032 3033 free(queryString); 3034 } 3035 3036 if (queryItemsAdded) 3037 fQueryMenu->AddItem(new BSeparatorItem(), 1); 3038 } 3039 3040 3041 char* 3042 TMailWindow::_BuildQueryString(BEntry* entry) const 3043 { 3044 BNode node(entry); 3045 if (node.InitCheck() != B_OK) 3046 return NULL; 3047 3048 uint32 mode; 3049 if (node.ReadAttr(kAttrQueryInitialMode, B_INT32_TYPE, 0, (int32*)&mode, 3050 sizeof(int32)) <= 0) { 3051 mode = kByNameItem; 3052 } 3053 3054 BString queryString; 3055 switch (mode) { 3056 case kByForumlaItem: 3057 { 3058 BString buffer; 3059 if (node.ReadAttrString(kAttrQueryInitialString, &buffer) == B_OK) 3060 queryString << buffer; 3061 break; 3062 } 3063 3064 case kByNameItem: 3065 { 3066 BString buffer; 3067 if (node.ReadAttrString(kAttrQueryInitialString, &buffer) == B_OK) 3068 queryString << "(name==*" << buffer << "*)"; 3069 break; 3070 } 3071 3072 case kByAttributeItem: 3073 { 3074 int32 count = 1; 3075 if (node.ReadAttr(kAttrQueryInitialNumAttrs, B_INT32_TYPE, 0, 3076 (int32 *)&mode, sizeof(int32)) <= 0) { 3077 count = 1; 3078 } 3079 3080 attr_info info; 3081 if (node.GetAttrInfo(kAttrQueryInitialAttrs, &info) != B_OK) 3082 break; 3083 3084 if (count > 1) 3085 queryString << "("; 3086 3087 char *buffer = new char[info.size]; 3088 if (node.ReadAttr(kAttrQueryInitialAttrs, B_MESSAGE_TYPE, 0, 3089 buffer, (size_t)info.size) == info.size) { 3090 BMessage message; 3091 if (message.Unflatten(buffer) == B_OK) { 3092 for (int32 index = 0; /*index < count*/; index++) { 3093 const char *field; 3094 const char *value; 3095 if (message.FindString("menuSelection", index, &field) 3096 != B_OK 3097 || message.FindString("attrViewText", index, &value) 3098 != B_OK) { 3099 break; 3100 } 3101 3102 // ignore the mime type, we'll force it to be email 3103 // later 3104 if (strcmp(field, "BEOS:TYPE") != 0) { 3105 // TODO: check if subMenu contains the type of 3106 // comparison we are suppose to make here 3107 queryString << "(" << field << "==\"" 3108 << value << "\")"; 3109 3110 int32 logicMenuSelectedIndex; 3111 if (message.FindInt32("logicalRelation", index, 3112 &logicMenuSelectedIndex) == B_OK) { 3113 if (logicMenuSelectedIndex == 0) 3114 queryString << "&&"; 3115 else if (logicMenuSelectedIndex == 1) 3116 queryString << "||"; 3117 } else 3118 break; 3119 } 3120 } 3121 } 3122 } 3123 3124 if (count > 1) 3125 queryString << ")"; 3126 3127 delete [] buffer; 3128 break; 3129 } 3130 3131 default: 3132 break; 3133 } 3134 3135 if (queryString.Length() == 0) 3136 return NULL; 3137 3138 // force it to check for email only 3139 if (queryString.FindFirst("text/x-email") < 0) { 3140 BString temp; 3141 temp << "(" << queryString << "&&(BEOS:TYPE==\"text/x-email\"))"; 3142 queryString = temp; 3143 } 3144 3145 return strdup(queryString.String()); 3146 } 3147 3148 3149 void 3150 TMailWindow::_AddReadButton() 3151 { 3152 bool newMail = false; 3153 BNode node(fRef); 3154 if (node.InitCheck() == B_NO_ERROR) { 3155 BString status; 3156 if (ReadAttrString(&node, B_MAIL_ATTR_STATUS, &status) == B_NO_ERROR 3157 && !status.ICompare("New")) { 3158 newMail = true; 3159 } 3160 } 3161 3162 int32 buttonIndex = fButtonBar->IndexOf(fNextButton); 3163 if (newMail) 3164 fReadButton = fButtonBar->AddButton( 3165 B_TRANSLATE(" Read "), 24, new BMessage(M_READ), buttonIndex); 3166 else 3167 fReadButton = fButtonBar->AddButton( 3168 B_TRANSLATE("Unread"), 28, new BMessage(M_UNREAD), buttonIndex); 3169 } 3170 3171 3172 void 3173 TMailWindow::_UpdateReadButton() 3174 { 3175 if (fApp->ShowButtonBar()) { 3176 fButtonBar->RemoveButton(fReadButton); 3177 fReadButton = NULL; 3178 if (!fAutoMarkRead && !fReadButton) { 3179 _AddReadButton(); 3180 } 3181 } 3182 UpdateViews(); 3183 } 3184 3185