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