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