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