1/*
2Open Tracker License
3
4Terms and Conditions
5
6Copyright (c) 1991-2001, Be Incorporated. All rights reserved.
7
8Permission is hereby granted, free of charge, to any person obtaining a copy of
9this software and associated documentation files (the "Software"), to deal in
10the Software without restriction, including without limitation the rights to
11use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
12of the Software, and to permit persons to whom the Software is furnished to do
13so, subject to the following conditions:
14
15The above copyright notice and this permission notice applies to all licensees
16and shall be included in all copies or substantial portions of the Software.
17
18THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY,
20FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
22AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN
23CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
25Except as contained in this notice, the name of Be Incorporated shall not be
26used in advertising or otherwise to promote the sale, use or other dealings in
27this Software without prior written authorization from Be Incorporated.
28
29BeMail(TM), Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or
30registered trademarks of Be Incorporated in the United States and other
31countries. Other brand product names are registered trademarks or trademarks
32of their respective holders. All rights reserved.
33*/
34
35
36#include "Header.h"
37
38#include <stdio.h>
39
40#include <ControlLook.h>
41#include <DateTimeFormat.h>
42#include <E-mail.h>
43#include <LayoutBuilder.h>
44#include <Locale.h>
45#include <MenuBar.h>
46#include <MenuField.h>
47#include <MenuItem.h>
48#include <ObjectList.h>
49#include <PopUpMenu.h>
50#include <Query.h>
51#include <String.h>
52#include <StringView.h>
53#include <Volume.h>
54#include <VolumeRoster.h>
55#include <Window.h>
56#include <fs_index.h>
57#include <fs_info.h>
58
59#include <MailSettings.h>
60#include <MailMessage.h>
61
62#include "MailApp.h"
63#include "MailSupport.h"
64#include "MailWindow.h"
65#include "Messages.h"
66#include "Utilities.h"
67#include "QueryMenu.h"
68#include "FieldMsg.h"
69#include "Prefs.h"
70
71
72#define B_TRANSLATION_CONTEXT "Mail"
73
74
75using namespace BPrivate;
76using std::map;
77
78
79const uint32 kMsgFrom = 'hFrm';
80const uint32 kMsgAddressChosen = 'acsn';
81
82
83struct CompareBStrings {
84	bool
85	operator()(const BString *s1, const BString *s2) const
86	{
87		return (s1->Compare(*s2) < 0);
88	}
89};
90
91
92class LabelView : public BStringView {
93public:
94								LabelView(const char* label);
95
96			bool				IsEnabled() const
97									{ return fEnabled; }
98			void				SetEnabled(bool enabled);
99
100	virtual	void				Draw(BRect updateRect);
101
102private:
103			bool				fEnabled;
104};
105
106
107class HeaderTextControl : public BTextControl {
108public:
109								HeaderTextControl(const char* label,
110									const char* name, BMessage* message);
111
112	virtual	void				AttachedToWindow();
113	virtual	void				SetEnabled(bool enabled);
114	virtual	void				Draw(BRect updateRect);
115	virtual	void				MessageReceived(BMessage* message);
116
117private:
118			void				_UpdateTextViewColors();
119};
120
121
122// #pragma mark - LabelView
123
124
125LabelView::LabelView(const char* label)
126	:
127	BStringView("label", label),
128	fEnabled(true)
129{
130	SetAlignment(B_ALIGN_RIGHT);
131}
132
133
134void
135LabelView::SetEnabled(bool enabled)
136{
137	if (enabled != fEnabled) {
138		fEnabled = enabled;
139		Invalidate();
140	}
141}
142
143
144void
145LabelView::Draw(BRect updateRect)
146{
147	if (Text() != NULL) {
148		BRect rect = Bounds();
149		// TODO: solve this better (alignment of label and text)
150		rect.top++;
151
152		rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
153		uint32 flags = 0;
154		if (!IsEnabled())
155			flags |= BControlLook::B_DISABLED;
156
157		be_control_look->DrawLabel(this, Text(), rect, updateRect,
158			base, flags, BAlignment(Alignment(), B_ALIGN_MIDDLE));
159	}
160}
161
162
163// #pragma mark - HeaderTextControl
164
165
166HeaderTextControl::HeaderTextControl(const char* label, const char* name,
167	BMessage* message)
168	:
169	BTextControl(label, name, message)
170{
171}
172
173
174void
175HeaderTextControl::AttachedToWindow()
176{
177	BTextControl::AttachedToWindow();
178	_UpdateTextViewColors();
179}
180
181
182void
183HeaderTextControl::SetEnabled(bool enabled)
184{
185	BTextControl::SetEnabled(enabled);
186	_UpdateTextViewColors();
187}
188
189
190void
191HeaderTextControl::Draw(BRect updateRect)
192{
193	bool enabled = IsEnabled();
194	bool active = TextView()->IsFocus() && Window()->IsActive();
195
196	BRect rect = TextView()->Frame();
197	rect.InsetBy(-2, -2);
198
199	rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
200	uint32 flags = 0;
201	if (!enabled)
202		flags = BControlLook::B_DISABLED;
203
204	if (enabled) {
205		if (active)
206			flags |= BControlLook::B_FOCUSED;
207
208		be_control_look->DrawTextControlBorder(this, rect, updateRect, base,
209			flags);
210	}
211
212	if (Label() != NULL) {
213		rect = CreateLabelLayoutItem()->Frame().OffsetByCopy(-Frame().left,
214			-Frame().top);
215		// TODO: solve this better (alignment of label and text)
216		rect.top++;
217
218		alignment labelAlignment;
219		GetAlignment(&labelAlignment, NULL);
220
221		be_control_look->DrawLabel(this, Label(), rect, updateRect,
222			base, flags, BAlignment(labelAlignment, B_ALIGN_MIDDLE));
223	}
224}
225
226
227void
228HeaderTextControl::MessageReceived(BMessage* message)
229{
230	switch (message->what) {
231		case M_SELECT:
232		{
233			BTextView* textView = TextView();
234			if (textView != NULL)
235				textView->SelectAll();
236			break;
237		}
238
239		default:
240			BTextControl::MessageReceived(message);
241			break;
242	}
243}
244
245
246void
247HeaderTextControl::_UpdateTextViewColors()
248{
249	BTextView* textView = TextView();
250
251	BFont font;
252	textView->GetFontAndColor(0, &font);
253
254	rgb_color textColor;
255	if (!textView->IsEditable() || IsEnabled())
256		textColor = ui_color(B_DOCUMENT_TEXT_COLOR);
257	else {
258		textColor = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR),
259			B_DISABLED_LABEL_TINT);
260	}
261
262	textView->SetFontAndColor(&font, B_FONT_ALL, &textColor);
263
264	rgb_color color;
265	if (!textView->IsEditable())
266		color = ui_color(B_PANEL_BACKGROUND_COLOR);
267	else if (IsEnabled())
268		color = ui_color(B_DOCUMENT_BACKGROUND_COLOR);
269	else {
270		color = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR),
271			B_LIGHTEN_2_TINT);
272	}
273
274	textView->SetViewColor(color);
275	textView->SetLowColor(color);
276}
277
278
279// #pragma mark - THeaderView
280
281
282THeaderView::THeaderView(bool incoming, bool resending, int32 defaultAccount)
283	:
284	fAccountMenu(NULL),
285	fAccountID(defaultAccount),
286	fFromControl(NULL),
287	fCcControl(NULL),
288	fBccControl(NULL),
289	fDateControl(NULL),
290	fIncoming(incoming),
291	fResending(resending)
292{
293	// From
294	if (fIncoming) {
295		fFromControl = new HeaderTextControl(B_TRANSLATE("From:"), NULL, NULL);
296		fFromControl->SetAlignment(B_ALIGN_RIGHT, B_ALIGN_LEFT);
297		fFromControl->SetEnabled(false);
298	}
299
300	// From accounts menu
301	BMenuField* fromField = NULL;
302	if (!fIncoming || fResending) {
303		// And now the "from account" pop-up menu, on the left side, taking the
304		// remaining space.
305
306		fAccountMenu = new BPopUpMenu(B_EMPTY_STRING);
307
308		BMailAccounts accounts;
309		bool marked = false;
310		for (int32 i = 0; i < accounts.CountAccounts(); i++) {
311			BMailAccountSettings* account = accounts.AccountAt(i);
312			BString name = account->Name();
313			name << ":   " << account->RealName() << "  <"
314				<< account->ReturnAddress() << ">";
315
316			BMessage* msg = new BMessage(kMsgFrom);
317			BMenuItem *item = new BMenuItem(name, msg);
318
319			msg->AddInt32("id", account->AccountID());
320
321			if (defaultAccount == account->AccountID()) {
322				item->SetMarked(true);
323				marked = true;
324			}
325			fAccountMenu->AddItem(item);
326		}
327
328		if (!marked) {
329			BMenuItem *item = fAccountMenu->ItemAt(0);
330			if (item != NULL) {
331				item->SetMarked(true);
332				fAccountID = item->Message()->FindInt32("id");
333			} else {
334				fAccountMenu->AddItem(
335					item = new BMenuItem(B_TRANSLATE("<none>"), NULL));
336				item->SetEnabled(false);
337				fAccountID = ~(int32)0;
338			}
339			// default account is invalid, set to marked
340			// TODO: do this differently, no casting and knowledge
341			// of TMailApp here....
342			if (TMailApp* app = dynamic_cast<TMailApp*>(be_app))
343				app->SetDefaultAccount(fAccountID);
344		}
345
346		fromField = new BMenuField("account", B_TRANSLATE("From:"),
347			fAccountMenu, B_WILL_DRAW | B_NAVIGABLE | B_NAVIGABLE_JUMP);
348		fromField->SetAlignment(B_ALIGN_RIGHT);
349	}
350
351	// To
352	fToLabel = new LabelView(B_TRANSLATE("To:"));
353	fToControl = new AddressTextControl(B_TRANSLATE("To:"),
354		new BMessage(TO_FIELD));
355	if (fIncoming || fResending) {
356		fToLabel->SetEnabled(false);
357		fToControl->SetEditable(false);
358		fToControl->SetEnabled(false);
359	}
360
361	BMessage* msg = new BMessage(FIELD_CHANGED);
362	msg->AddInt32("bitmask", FIELD_TO);
363	fToControl->SetModificationMessage(msg);
364
365	// Carbon copy
366	fCcLabel = new LabelView(B_TRANSLATE("Cc:"));
367	fCcControl = new AddressTextControl("cc", new BMessage(CC_FIELD));
368	if (fIncoming) {
369		fCcLabel->SetEnabled(false);
370		fCcControl->SetEditable(false);
371		fCcControl->SetEnabled(false);
372		fCcControl->Hide();
373		fCcLabel->Hide();
374	}
375	msg = new BMessage(FIELD_CHANGED);
376	msg->AddInt32("bitmask", FIELD_CC);
377	fCcControl->SetModificationMessage(msg);
378
379	// Blind carbon copy
380	if (!fIncoming) {
381		fBccControl = new AddressTextControl("bcc", new BMessage(BCC_FIELD));
382		msg = new BMessage(FIELD_CHANGED);
383		msg->AddInt32("bitmask", FIELD_BCC);
384		fBccControl->SetModificationMessage(msg);
385	}
386
387	// Subject
388	fSubjectControl = new HeaderTextControl(B_TRANSLATE("Subject:"), NULL,
389		new BMessage(SUBJECT_FIELD));
390	msg = new BMessage(FIELD_CHANGED);
391	msg->AddInt32("bitmask", FIELD_SUBJECT);
392	fSubjectControl->SetModificationMessage(msg);
393	fSubjectControl->SetAlignment(B_ALIGN_RIGHT, B_ALIGN_LEFT);
394	if (fIncoming || fResending)
395		fSubjectControl->SetEnabled(false);
396
397	// Date
398	if (fIncoming) {
399		fDateControl = new HeaderTextControl(B_TRANSLATE("Date:"), NULL, NULL);
400		fDateControl->SetAlignment(B_ALIGN_RIGHT, B_ALIGN_LEFT);
401		fDateControl->SetEnabled(false);
402	}
403
404	BGridLayout* layout = GridLayout();
405
406	if (fIncoming) {
407		layout->SetHorizontalSpacing(0);
408		layout->SetVerticalSpacing(0);
409	} else
410		layout->SetVerticalSpacing(B_USE_HALF_ITEM_SPACING);
411
412	int32 row = 0;
413	if (fromField != NULL) {
414		layout->AddItem(fromField->CreateLabelLayoutItem(), 0, row);
415		layout->AddItem(fromField->CreateMenuBarLayoutItem(), 1, row++, 3, 1);
416	} else if (fFromControl != NULL) {
417		layout->AddItem(fFromControl->CreateLabelLayoutItem(), 0, row);
418		layout->AddItem(fFromControl->CreateTextViewLayoutItem(), 1, row);
419	}
420
421	if (fIncoming && !fResending) {
422		layout->AddView(fToLabel, 2, row);
423		layout->AddView(fToControl, 3, row++);
424	} else {
425		row++;
426		layout->AddView(fToLabel, 0, row);
427		layout->AddView(fToControl, 1, row++, 3, 1);
428	}
429
430	if (fDateControl != NULL) {
431		layout->AddItem(fDateControl->CreateLabelLayoutItem(), 0, row);
432		layout->AddItem(fDateControl->CreateTextViewLayoutItem(), 1, row);
433	}
434
435	if (fIncoming && (fCcControl != NULL)) {
436		layout->AddView(fCcLabel, 2, row);
437		layout->AddView(fCcControl, 3, row++);
438	} else {
439		row++;
440		layout->AddView(fCcLabel, 0, row);
441		layout->AddView(fCcControl, 1, row, 1, 1);
442	}
443
444	if (fBccControl != NULL) {
445		layout->AddView(new LabelView(B_TRANSLATE("Bcc:")), 2, row);
446		layout->AddView(fBccControl, 3, row++);
447	} else
448		row++;
449	layout->AddItem(fSubjectControl->CreateLabelLayoutItem(), 0, row);
450	layout->AddItem(fSubjectControl->CreateTextViewLayoutItem(), 1, row++,
451		3, 1);
452}
453
454
455const char*
456THeaderView::From() const
457{
458	return fFromControl != NULL ? fFromControl->Text() : NULL;
459}
460
461
462void
463THeaderView::SetFrom(const char* from)
464{
465	if (fFromControl != NULL)
466		fFromControl->SetText(from);
467}
468
469
470bool
471THeaderView::IsToEmpty() const
472{
473	return To() == NULL || To()[0] == '\0';
474}
475
476
477const char*
478THeaderView::To() const
479{
480	return fToControl->Text();
481}
482
483
484void
485THeaderView::SetTo(const char* to)
486{
487	fToControl->SetText(to);
488}
489
490
491bool
492THeaderView::IsCcEmpty() const
493{
494	return Cc() == NULL || Cc()[0] == '\0';
495}
496
497
498const char*
499THeaderView::Cc() const
500{
501	return fCcControl != NULL ? fCcControl->Text() : NULL;
502}
503
504
505void
506THeaderView::SetCc(const char* cc)
507{
508	fCcControl->SetText(cc);
509
510	if (fIncoming) {
511		if (cc != NULL && cc[0] != '\0') {
512			if (fCcControl->IsHidden(this)) {
513				fCcControl->Show();
514				fCcLabel->Show();
515			}
516		} else if (!fCcControl->IsHidden(this)) {
517			fCcControl->Hide();
518			fCcLabel->Hide();
519		}
520	}
521}
522
523
524bool
525THeaderView::IsBccEmpty() const
526{
527	return Bcc() == NULL || Bcc()[0] == '\0';
528}
529
530
531const char*
532THeaderView::Bcc() const
533{
534	return fBccControl != NULL ? fBccControl->Text() : NULL;
535}
536
537
538void
539THeaderView::SetBcc(const char* bcc)
540{
541	if (fBccControl != NULL)
542		fBccControl->SetText(bcc);
543}
544
545
546bool
547THeaderView::IsSubjectEmpty() const
548{
549	return Subject() == NULL || Subject()[0] == '\0';
550}
551
552
553const char*
554THeaderView::Subject() const
555{
556	return fSubjectControl->Text();
557}
558
559
560void
561THeaderView::SetSubject(const char* subject)
562{
563	fSubjectControl->SetText(subject);
564}
565
566
567bool
568THeaderView::IsDateEmpty() const
569{
570	return Date() == NULL || Date()[0] == '\0';
571}
572
573
574const char*
575THeaderView::Date() const
576{
577	return fDateControl != NULL ? fDateControl->Text() : NULL;
578}
579
580
581void
582THeaderView::SetDate(time_t date)
583{
584	fDate = date;
585
586	if (fDateControl != NULL) {
587		BDateTimeFormat formatter;
588
589		BString string;
590		formatter.Format(string, date, B_FULL_DATE_FORMAT,
591			B_MEDIUM_TIME_FORMAT);
592		SetDate(string);
593	}
594}
595
596
597void
598THeaderView::SetDate(const char* date)
599{
600	if (fDateControl != NULL)
601		fDateControl->SetText(date);
602}
603
604
605int32
606THeaderView::AccountID() const
607{
608	return fAccountID;
609}
610
611
612const char*
613THeaderView::AccountName() const
614{
615	BMenuItem* menuItem = fAccountMenu->FindMarked();
616	if (menuItem != NULL)
617		return menuItem->Label();
618
619	return NULL;
620}
621
622
623void
624THeaderView::SetAccount(int32 id)
625{
626	fAccountID = id;
627
628	if (fAccountMenu == NULL)
629		return;
630
631	for (int32 i = fAccountMenu->CountItems(); i-- > 0;) {
632		BMenuItem* item = fAccountMenu->ItemAt(i);
633		if (item == NULL)
634			continue;
635
636		BMessage* message = item->Message();
637		if (message->GetInt32("id", -1) == id) {
638			item->SetMarked(true);
639			break;
640		}
641	}
642}
643
644
645void
646THeaderView::SetAccount(const char* name)
647{
648	if (fAccountMenu == NULL)
649		return;
650
651	BMenuItem* item = fAccountMenu->FindItem(name);
652	if (item != NULL) {
653		item->SetMarked(true);
654		fAccountID = item->Message()->GetInt32("id", -1);
655	}
656}
657
658
659status_t
660THeaderView::SetFromMessage(BEmailMessage* mail)
661{
662	// Set Subject:, From:, To: & Cc: fields
663	SetSubject(mail->Subject());
664	SetFrom(mail->From());
665	SetTo(mail->To());
666	SetCc(mail->CC());
667
668	BString accountName;
669	if (mail->GetAccountName(accountName) == B_OK)
670		SetAccount(accountName);
671
672	// Set the date on this message
673	time_t date = mail->Date();
674	if (date <= 0) {
675		const char* dateField = mail->HeaderField("Date");
676		SetDate(dateField != NULL ? dateField : B_TRANSLATE("Unknown"));
677	} else
678		SetDate(date);
679
680	return B_OK;
681}
682
683
684void
685THeaderView::MessageReceived(BMessage *msg)
686{
687	switch (msg->what) {
688		case B_SIMPLE_DATA:
689		{
690			BTextView* textView = dynamic_cast<BTextView*>(Window()->CurrentFocus());
691			if (dynamic_cast<AddressTextControl *>(textView->Parent()) != NULL)
692			BMessage message(*msg);
693			textView->Parent()->MessageReceived(msg);
694			break;
695		}
696		case B_REFS_RECEIVED:
697		{
698			BMessage message(*msg);
699			message.what = REFS_RECEIVED;
700			Window()->PostMessage(&message, Window());
701			break;
702		}
703
704		case kMsgFrom:
705		{
706			BMenuItem *item;
707			if (msg->FindPointer("source", (void **)&item) >= B_OK)
708				item->SetMarked(true);
709
710			int32 account;
711			if (msg->FindInt32("id",(int32 *)&account) >= B_OK)
712				fAccountID = account;
713
714			BMessage message(FIELD_CHANGED);
715			// field doesn't matter; no special processing for this field
716			// it's just to turn on the save button
717			message.AddInt32("bitmask", 0);
718			Window()->PostMessage(&message, Window());
719			break;
720		}
721	}
722}
723
724
725void
726THeaderView::AttachedToWindow()
727{
728	fToControl->SetTarget(Looper());
729	fSubjectControl->SetTarget(Looper());
730	fCcControl->SetTarget(Looper());
731	if (fBccControl != NULL)
732		fBccControl->SetTarget(Looper());
733	if (fAccountMenu != NULL)
734		fAccountMenu->SetTargetForItems(this);
735
736	BView::AttachedToWindow();
737}
738