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//
37//	Enclosures.cpp
38//	The enclosures list view (TListView), the list items (TListItem),
39//	and the view containing the list
40//	and handling the messages (TEnclosuresView).
41//--------------------------------------------------------------------
42
43#include "Enclosures.h"
44
45#include <Alert.h>
46#include <Beep.h>
47#include <Bitmap.h>
48#include <ControlLook.h>
49#include <Debug.h>
50#include <LayoutBuilder.h>
51#include <Locale.h>
52#include <MenuItem.h>
53#include <NodeMonitor.h>
54#include <PopUpMenu.h>
55#include <StringForSize.h>
56#include <StringView.h>
57
58#include <MailAttachment.h>
59#include <MailMessage.h>
60
61#include <stdio.h>
62#include <stdlib.h>
63#include <string.h>
64
65#include "MailApp.h"
66#include "MailSupport.h"
67#include "MailWindow.h"
68#include "Messages.h"
69
70
71#define B_TRANSLATION_CONTEXT "Mail"
72
73
74static const float kPlainFontSizeScale = 0.9;
75
76
77static void
78recursive_attachment_search(TEnclosuresView* us, BMailContainer* mail,
79	BMailComponent *body)
80{
81	if (mail == NULL)
82		return;
83
84	for (int32 i = 0; i < mail->CountComponents(); i++) {
85		BMailComponent *component = mail->GetComponent(i);
86		if (component == body)
87			continue;
88
89		if (component->ComponentType() == B_MAIL_MULTIPART_CONTAINER) {
90			recursive_attachment_search(us,
91				dynamic_cast<BMIMEMultipartMailContainer *>(component), body);
92		}
93
94		us->fList->AddItem(new TListItem(component));
95	}
96}
97
98
99//	#pragma mark -
100
101
102TEnclosuresView::TEnclosuresView()
103	:
104	BView("m_enclosures", B_WILL_DRAW),
105	fFocus(false)
106{
107	SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
108
109	BFont font(be_plain_font);
110	font.SetSize(font.Size() * kPlainFontSizeScale);
111	SetFont(&font);
112
113	fList = new TListView(this);
114	fList->SetInvocationMessage(new BMessage(LIST_INVOKED));
115
116	BStringView* label = new BStringView("label", B_TRANSLATE("Attachments:"));
117	BScrollView* scroll = new BScrollView("", fList, 0, false, true);
118
119	BLayoutBuilder::Group<>(this, B_HORIZONTAL)
120		.SetInsets(B_USE_SMALL_INSETS, 0,
121			scroll->ScrollBar(B_VERTICAL)->PreferredSize().width - 2, -2)
122		.Add(label)
123		.Add(scroll)
124	.End();
125}
126
127
128TEnclosuresView::~TEnclosuresView()
129{
130	for (int32 index = fList->CountItems();index-- > 0;) {
131		TListItem *item = static_cast<TListItem *>(fList->ItemAt(index));
132		fList->RemoveItem(index);
133
134		if (item->Component() == NULL)
135			watch_node(item->NodeRef(), B_STOP_WATCHING, this);
136		delete item;
137	}
138}
139
140
141void
142TEnclosuresView::MessageReceived(BMessage *msg)
143{
144	switch (msg->what)
145	{
146		case LIST_INVOKED:
147		{
148			BListView *list;
149			msg->FindPointer("source", (void **)&list);
150			if (list) {
151				TListItem *item = (TListItem *) (list->ItemAt(msg->FindInt32("index")));
152				if (item) {
153					BMessenger tracker("application/x-vnd.Be-TRAK");
154					if (tracker.IsValid()) {
155						BMessage message(B_REFS_RECEIVED);
156						message.AddRef("refs", item->Ref());
157
158						tracker.SendMessage(&message);
159					}
160				}
161			}
162			break;
163		}
164
165		case M_REMOVE:
166		{
167			int32 index;
168			while ((index = fList->CurrentSelection()) >= 0) {
169				TListItem *item = (TListItem *) fList->ItemAt(index);
170				fList->RemoveItem(index);
171
172				if (item->Component()) {
173					TMailWindow *window = dynamic_cast<TMailWindow *>(Window());
174					if (window && window->Mail())
175						window->Mail()->RemoveComponent(item->Component());
176
177					BAlert* alert = new BAlert("", B_TRANSLATE(
178						"Removing attachments from a forwarded mail is not yet "
179						"implemented!\nIt will not yet work correctly."),
180						B_TRANSLATE("OK"));
181					alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
182					alert->Go();
183				} else
184					watch_node(item->NodeRef(), B_STOP_WATCHING, this);
185				delete item;
186			}
187			break;
188		}
189
190		case M_SELECT:
191			fList->Select(0, fList->CountItems() - 1, true);
192			break;
193
194		case B_SIMPLE_DATA:
195		case B_REFS_RECEIVED:
196		case REFS_RECEIVED:
197			if (msg->HasRef("refs")) {
198				bool badType = false;
199
200				int32 index = 0;
201				entry_ref ref;
202				while (msg->FindRef("refs", index++, &ref) == B_NO_ERROR) {
203					BEntry entry(&ref, true);
204					entry.GetRef(&ref);
205					BFile file(&ref, O_RDONLY);
206					if (file.InitCheck() == B_OK && file.IsFile()) {
207						TListItem *item;
208						bool exists = false;
209						for (int32 loop = 0; loop < fList->CountItems(); loop++) {
210							item = (TListItem *) fList->ItemAt(loop);
211							if (ref == *(item->Ref())) {
212								fList->Select(loop);
213								fList->ScrollToSelection();
214								exists = true;
215								continue;
216							}
217						}
218						if (exists == false) {
219							fList->AddItem(item = new TListItem(&ref));
220							fList->Select(fList->CountItems() - 1);
221							fList->ScrollToSelection();
222
223							watch_node(item->NodeRef(), B_WATCH_NAME, this);
224						}
225					} else
226						badType = true;
227				}
228				if (badType) {
229					beep();
230					BAlert* alert = new BAlert("",
231						B_TRANSLATE("Only files can be added as attachments."),
232						B_TRANSLATE("OK"));
233					alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
234					alert->Go();
235				}
236			}
237			break;
238
239		case B_NODE_MONITOR:
240		{
241			int32 opcode;
242			if (msg->FindInt32("opcode", &opcode) == B_NO_ERROR) {
243				dev_t device;
244				if (msg->FindInt32("device", &device) < B_OK)
245					break;
246				ino_t inode;
247				if (msg->FindInt64("node", &inode) < B_OK)
248					break;
249
250				for (int32 index = fList->CountItems();index-- > 0;) {
251					TListItem *item = static_cast<TListItem *>(fList->ItemAt(index));
252
253					if (device == item->NodeRef()->device
254						&& inode == item->NodeRef()->node)
255					{
256						if (opcode == B_ENTRY_REMOVED) {
257							// don't hide the <missing attachment> item
258
259							//fList->RemoveItem(index);
260							//
261							//watch_node(item->NodeRef(), B_STOP_WATCHING, this);
262							//delete item;
263						} else if (opcode == B_ENTRY_MOVED) {
264							item->Ref()->device = device;
265							msg->FindInt64("to directory", &item->Ref()->directory);
266
267							const char *name;
268							msg->FindString("name", &name);
269							item->Ref()->set_name(name);
270						}
271
272						fList->InvalidateItem(index);
273						break;
274					}
275				}
276			}
277			break;
278		}
279
280		default:
281			BView::MessageReceived(msg);
282			break;
283	}
284}
285
286
287void
288TEnclosuresView::Focus(bool focus)
289{
290	if (fFocus != focus) {
291		fFocus = focus;
292		Invalidate();
293	}
294}
295
296
297void
298TEnclosuresView::AddEnclosuresFromMail(BEmailMessage *mail)
299{
300	for (int32 i = 0; i < mail->CountComponents(); i++) {
301		BMailComponent *component = mail->GetComponent(i);
302		if (component == mail->Body())
303			continue;
304
305		if (component->ComponentType() == B_MAIL_MULTIPART_CONTAINER) {
306			recursive_attachment_search(this,
307				dynamic_cast<BMIMEMultipartMailContainer *>(component),
308				mail->Body());
309		}
310
311		fList->AddItem(new TListItem(component));
312	}
313}
314
315
316//	#pragma mark -
317
318
319TListView::TListView(TEnclosuresView *view)
320	:
321	BListView("", B_MULTIPLE_SELECTION_LIST),
322	fParent(view)
323{
324}
325
326
327void
328TListView::AttachedToWindow()
329{
330	BListView::AttachedToWindow();
331
332	BFont font(be_plain_font);
333	font.SetSize(font.Size() * kPlainFontSizeScale);
334	SetFont(&font);
335}
336
337
338BSize
339TListView::MinSize()
340{
341	BSize size = BListView::MinSize();
342	size.height = be_control_look->DefaultLabelSpacing() * 11.0f;
343	return size;
344}
345
346
347void
348TListView::MakeFocus(bool focus)
349{
350	BListView::MakeFocus(focus);
351	fParent->Focus(focus);
352}
353
354
355void
356TListView::MouseDown(BPoint point)
357{
358	int32 buttons;
359	Looper()->CurrentMessage()->FindInt32("buttons", &buttons);
360
361	BListView::MouseDown(point);
362
363	if ((buttons & B_SECONDARY_MOUSE_BUTTON) != 0 && IndexOf(point) >= 0) {
364		BPopUpMenu menu("enclosure", false, false);
365		menu.SetFont(be_plain_font);
366		menu.AddItem(new BMenuItem(B_TRANSLATE("Open attachment"),
367			new BMessage(LIST_INVOKED)));
368		menu.AddItem(new BMenuItem(B_TRANSLATE("Remove attachment"),
369			new BMessage(M_REMOVE)));
370
371		BPoint menuStart = ConvertToScreen(point);
372
373		BMenuItem* item = menu.Go(menuStart);
374		if (item != NULL) {
375			if (item->Command() == LIST_INVOKED) {
376				BMessage msg(LIST_INVOKED);
377				msg.AddPointer("source",this);
378				msg.AddInt32("index",IndexOf(point));
379				Window()->PostMessage(&msg,fParent);
380			} else {
381				Select(IndexOf(point));
382				Window()->PostMessage(item->Command(),fParent);
383			}
384		}
385	}
386}
387
388
389void
390TListView::KeyDown(const char *bytes, int32 numBytes)
391{
392	BListView::KeyDown(bytes,numBytes);
393
394	if (numBytes == 1 && *bytes == B_DELETE)
395		Window()->PostMessage(M_REMOVE, fParent);
396}
397
398
399//	#pragma mark -
400
401
402TListItem::TListItem(entry_ref *ref)
403{
404	fComponent = NULL;
405	fRef = *ref;
406
407	BEntry entry(ref);
408	entry.GetNodeRef(&fNodeRef);
409}
410
411
412TListItem::TListItem(BMailComponent *component)
413	:
414	fComponent(component)
415{
416}
417
418
419void
420TListItem::Update(BView* owner, const BFont* font)
421{
422	BListItem::Update(owner, font);
423
424	const float minimalHeight =
425		be_control_look->ComposeIconSize(B_MINI_ICON).Height() +
426		(be_control_look->DefaultLabelSpacing() / 3.0f);
427	if (Height() < minimalHeight)
428		SetHeight(minimalHeight);
429}
430
431
432void
433TListItem::DrawItem(BView *owner, BRect frame, bool /* complete */)
434{
435	rgb_color kHighlight = ui_color(B_LIST_SELECTED_BACKGROUND_COLOR);
436	rgb_color kHighlightText = ui_color(B_LIST_SELECTED_ITEM_TEXT_COLOR);
437	rgb_color kText = ui_color(B_LIST_ITEM_TEXT_COLOR);
438
439	BRect r(frame);
440
441	if (IsSelected()) {
442		owner->SetHighColor(kHighlight);
443		owner->SetLowColor(kHighlight);
444		owner->FillRect(r);
445	}
446
447	const BRect iconRect(BPoint(0, 0), be_control_look->ComposeIconSize(B_MINI_ICON));
448	BBitmap iconBitmap(iconRect, B_RGBA32);
449	status_t iconStatus = B_NO_INIT;
450	BString label;
451
452	if (fComponent) {
453		// if it's already a mail component, we don't have an icon to
454		// draw, and the entry_ref is invalid
455		BMailAttachment *attachment = dynamic_cast<BMailAttachment *>(fComponent);
456
457		char name[B_FILE_NAME_LENGTH * 2];
458		if ((attachment == NULL) || (attachment->FileName(name) < B_OK))
459			strcpy(name, "unnamed");
460
461		BMimeType type;
462		if (fComponent->MIMEType(&type) == B_OK)
463			label.SetToFormat("%s, Type: %s", name, type.Type());
464		else
465			label = name;
466
467		iconStatus = type.GetIcon(&iconBitmap,
468			(icon_size)(iconRect.IntegerWidth() + 1));
469	} else {
470		BFile file(&fRef, O_RDONLY);
471		BEntry entry(&fRef);
472		BPath path;
473		if (entry.GetPath(&path) == B_OK && file.InitCheck() == B_OK) {
474			off_t bytes;
475			file.GetSize(&bytes);
476			char size[B_PATH_NAME_LENGTH];
477			string_for_size(bytes, size, sizeof(size));
478			label << path.Path() << " (" << size << ")";
479			BNodeInfo info(&file);
480			iconStatus = info.GetTrackerIcon(&iconBitmap,
481				(icon_size)(iconRect.IntegerWidth() + 1));
482		} else
483			label = "<missing attachment>";
484	}
485
486	BRect iconFrame(frame);
487	iconFrame.left += be_control_look->DefaultLabelSpacing() / 2;
488	iconFrame.Set(iconFrame.left, iconFrame.top + 1,
489		iconFrame.left + iconRect.Width(),
490		iconFrame.top + iconRect.Height() + 1);
491
492	if (iconStatus == B_OK) {
493		owner->SetDrawingMode(B_OP_ALPHA);
494		owner->SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY);
495		owner->DrawBitmap(&iconBitmap, iconFrame);
496		owner->SetDrawingMode(B_OP_COPY);
497	} else {
498		// ToDo: find some nicer image for this :-)
499		owner->SetHighColor(150, 150, 150);
500		owner->FillEllipse(iconFrame);
501	}
502
503	BFont font;
504	owner->GetFont(&font);
505	font_height finfo;
506	font.GetHeight(&finfo);
507	owner->MovePenTo(frame.left + (iconFrame.Width() * 1.5f),
508		frame.top + ((frame.Height()
509			- (finfo.ascent + finfo.descent + finfo.leading)) / 2)
510		+ finfo.ascent);
511
512	owner->SetHighColor(IsSelected() ? kHighlightText : kText);
513	owner->DrawString(label.String());
514}
515