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