xref: /haiku/src/apps/mail/Enclosures.cpp (revision 2db0fbd87ec3eeed8ab9b08ecfd1c41891c7e3a6)
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 //
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 <Debug.h>
49 #include <Locale.h>
50 #include <MenuItem.h>
51 #include <NodeMonitor.h>
52 #include <PopUpMenu.h>
53 
54 #include <MailAttachment.h>
55 #include <MailMessage.h>
56 
57 #include <stdio.h>
58 #include <stdlib.h>
59 #include <string.h>
60 
61 #include "MailApp.h"
62 #include "MailSupport.h"
63 #include "MailWindow.h"
64 #include "Messages.h"
65 
66 
67 #define B_TRANSLATION_CONTEXT "Mail"
68 
69 
70 static const float kPlainFontSizeScale = 0.9;
71 
72 static status_t
73 GetTrackerIcon(BMimeType &type, BBitmap *icon, icon_size iconSize)
74 {
75 	// set some icon size related variables
76 	status_t error = B_OK;
77 	BRect bounds;
78 	switch (iconSize) {
79 		case B_MINI_ICON:
80 			bounds.Set(0, 0, 15, 15);
81 			break;
82 		case B_LARGE_ICON:
83 			bounds.Set(0, 0, 31, 31);
84 			break;
85 		default:
86 			error = B_BAD_VALUE;
87 			break;
88 	}
89 	// check parameters and initialization
90 	if (error == B_OK
91 		&& (!icon || icon->InitCheck() != B_OK || icon->Bounds() != bounds))
92 		return B_BAD_VALUE;
93 
94 	bool success = false;
95 
96 	// Ask the MIME database for the preferred application for the file type
97 	// and whether this application has a special icon for the type.
98 	char signature[B_MIME_TYPE_LENGTH];
99 	if (type.GetPreferredApp(signature) == B_OK) {
100 		BMimeType type(signature);
101 		success = (type.GetIconForType(type.Type(), icon, iconSize) == B_OK);
102 	}
103 
104 	// Ask the MIME database whether there is an icon for the node's file type.
105 	if (error == B_OK && !success)
106 		success = (type.GetIcon(icon, iconSize) == B_OK);
107 
108 	// Ask the MIME database for the super type and start all over
109 	if (error == B_OK && !success) {
110 		BMimeType super;
111 		if (type.GetSupertype(&super) == B_OK)
112 			return GetTrackerIcon(super, icon, iconSize);
113 	}
114 
115 	// Return the icon for "application/octet-stream" from the MIME database.
116 	if (error == B_OK && !success) {
117 		// get the "application/octet-stream" icon
118 		BMimeType type("application/octet-stream");
119 		error = type.GetIcon(icon, iconSize);
120 	}
121 
122 	return error;
123 }
124 
125 
126 static void
127 recursive_attachment_search(TEnclosuresView* us, BMailContainer* mail,
128 	BMailComponent *body)
129 {
130 	if (mail == NULL)
131 		return;
132 
133 	for (int32 i = 0; i < mail->CountComponents(); i++) {
134 		BMailComponent *component = mail->GetComponent(i);
135 		if (component == body)
136 			continue;
137 
138 		if (component->ComponentType() == B_MAIL_MULTIPART_CONTAINER) {
139 			recursive_attachment_search(us,
140 				dynamic_cast<BMIMEMultipartMailContainer *>(component), body);
141 		}
142 
143 		us->fList->AddItem(new TListItem(component));
144 	}
145 }
146 
147 
148 //	#pragma mark -
149 
150 
151 TEnclosuresView::TEnclosuresView(BRect rect, BRect windowRect)
152 	:
153 	BView(rect, "m_enclosures", B_FOLLOW_TOP | B_FOLLOW_LEFT_RIGHT,
154 		B_WILL_DRAW),
155 	fFocus(false)
156 {
157 	SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
158 
159 	BFont font(be_plain_font);
160 	font.SetSize(font.Size() * kPlainFontSizeScale);
161 	SetFont(&font);
162 
163 	fOffset = 12;
164 
165 	BRect r;
166 	r.left = ENCLOSE_TEXT_H + font.StringWidth(
167 		B_TRANSLATE("Attachments: ")) + 5;
168 	r.top = ENCLOSE_FIELD_V;
169 	r.right = windowRect.right - windowRect.left - B_V_SCROLL_BAR_WIDTH - 9;
170 	r.bottom = Frame().Height() - 8;
171 	fList = new TListView(r, this);
172 	fList->SetInvocationMessage(new BMessage(LIST_INVOKED));
173 
174 	BScrollView	*scroll = new BScrollView("", fList, B_FOLLOW_LEFT_RIGHT |
175 			B_FOLLOW_TOP, 0, false, true);
176 	AddChild(scroll);
177 	scroll->ScrollBar(B_VERTICAL)->SetRange(0, 0);
178 }
179 
180 
181 TEnclosuresView::~TEnclosuresView()
182 {
183 	for (int32 index = fList->CountItems();index-- > 0;)
184 	{
185 		TListItem *item = static_cast<TListItem *>(fList->ItemAt(index));
186 		fList->RemoveItem(index);
187 
188 		if (item->Component() == NULL)
189 			watch_node(item->NodeRef(), B_STOP_WATCHING, this);
190 		delete item;
191 	}
192 }
193 
194 
195 void
196 TEnclosuresView::Draw(BRect where)
197 {
198 	BView::Draw(where);
199 
200 	SetHighColor(0, 0, 0);
201 	SetLowColor(ViewColor());
202 
203 	font_height fh;
204 	GetFontHeight(&fh);
205 
206 	MovePenTo(ENCLOSE_TEXT_H, ENCLOSE_TEXT_V + fh.ascent);
207 	DrawString(ENCLOSE_TEXT);
208 }
209 
210 
211 void
212 TEnclosuresView::MessageReceived(BMessage *msg)
213 {
214 	switch (msg->what)
215 	{
216 		case LIST_INVOKED:
217 		{
218 			BListView *list;
219 			msg->FindPointer("source", (void **)&list);
220 			if (list)
221 			{
222 				TListItem *item = (TListItem *) (list->ItemAt(msg->FindInt32("index")));
223 				if (item)
224 				{
225 					BMessenger tracker("application/x-vnd.Be-TRAK");
226 					if (tracker.IsValid())
227 					{
228 						BMessage message(B_REFS_RECEIVED);
229 						message.AddRef("refs", item->Ref());
230 
231 						tracker.SendMessage(&message);
232 					}
233 				}
234 			}
235 			break;
236 		}
237 
238 		case M_REMOVE:
239 		{
240 			int32 index;
241 			while ((index = fList->CurrentSelection()) >= 0)
242 			{
243 				TListItem *item = (TListItem *) fList->ItemAt(index);
244 				fList->RemoveItem(index);
245 
246 				if (item->Component())
247 				{
248 					TMailWindow *window = dynamic_cast<TMailWindow *>(Window());
249 					if (window && window->Mail())
250 						window->Mail()->RemoveComponent(item->Component());
251 
252 					BAlert* alert = new BAlert("", B_TRANSLATE(
253 						"Removing attachments from a forwarded mail is not yet "
254 						"implemented!\nIt will not yet work correctly."),
255 						B_TRANSLATE("OK"));
256 					alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
257 					alert->Go();
258 				}
259 				else
260 					watch_node(item->NodeRef(), B_STOP_WATCHING, this);
261 				delete item;
262 			}
263 			break;
264 		}
265 
266 		case M_SELECT:
267 			fList->Select(0, fList->CountItems() - 1, true);
268 			break;
269 
270 		case B_SIMPLE_DATA:
271 		case B_REFS_RECEIVED:
272 		case REFS_RECEIVED:
273 			if (msg->HasRef("refs"))
274 			{
275 				bool badType = false;
276 
277 				int32 index = 0;
278 				entry_ref ref;
279 				while (msg->FindRef("refs", index++, &ref) == B_NO_ERROR)
280 				{
281 					BFile file(&ref, O_RDONLY);
282 					if (file.InitCheck() == B_OK && file.IsFile())
283 					{
284 						TListItem *item;
285 						for (int16 loop = 0; loop < fList->CountItems(); loop++)
286 						{
287 							item = (TListItem *) fList->ItemAt(loop);
288 							if (ref == *(item->Ref()))
289 							{
290 								fList->Select(loop);
291 								fList->ScrollToSelection();
292 								continue;
293 							}
294 						}
295 						fList->AddItem(item = new TListItem(&ref));
296 						fList->Select(fList->CountItems() - 1);
297 						fList->ScrollToSelection();
298 
299 						watch_node(item->NodeRef(), B_WATCH_NAME, this);
300 					}
301 					else
302 						badType = true;
303 				}
304 				if (badType)
305 				{
306 					beep();
307 					BAlert* alert = new BAlert("",
308 						B_TRANSLATE("Only files can be added as attachments."),
309 						B_TRANSLATE("OK"));
310 					alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
311 					alert->Go();
312 				}
313 			}
314 			break;
315 
316 		case B_NODE_MONITOR:
317 		{
318 			int32 opcode;
319 			if (msg->FindInt32("opcode", &opcode) == B_NO_ERROR)
320 			{
321 				dev_t device;
322 				if (msg->FindInt32("device", &device) < B_OK)
323 					break;
324 				ino_t inode;
325 				if (msg->FindInt64("node", &inode) < B_OK)
326 					break;
327 
328 				for (int32 index = fList->CountItems();index-- > 0;)
329 				{
330 					TListItem *item = static_cast<TListItem *>(fList->ItemAt(index));
331 
332 					if (device == item->NodeRef()->device
333 						&& inode == item->NodeRef()->node)
334 					{
335 						if (opcode == B_ENTRY_REMOVED)
336 						{
337 							// don't hide the <missing attachment> item
338 
339 							//fList->RemoveItem(index);
340 							//
341 							//watch_node(item->NodeRef(), B_STOP_WATCHING, this);
342 							//delete item;
343 						}
344 						else if (opcode == B_ENTRY_MOVED)
345 						{
346 							item->Ref()->device = device;
347 							msg->FindInt64("to directory", &item->Ref()->directory);
348 
349 							const char *name;
350 							msg->FindString("name", &name);
351 							item->Ref()->set_name(name);
352 						}
353 
354 						fList->InvalidateItem(index);
355 						break;
356 					}
357 				}
358 			}
359 			break;
360 		}
361 
362 		default:
363 			BView::MessageReceived(msg);
364 			break;
365 	}
366 }
367 
368 
369 void
370 TEnclosuresView::Focus(bool focus)
371 {
372 	if (fFocus != focus)
373 	{
374 		fFocus = focus;
375 		Draw(Frame());
376 	}
377 }
378 
379 
380 void
381 TEnclosuresView::AddEnclosuresFromMail(BEmailMessage *mail)
382 {
383 	for (int32 i = 0; i < mail->CountComponents(); i++)
384 	{
385 		BMailComponent *component = mail->GetComponent(i);
386 		if (component == mail->Body())
387 			continue;
388 
389 		if (component->ComponentType() == B_MAIL_MULTIPART_CONTAINER)
390 			recursive_attachment_search(this,dynamic_cast<BMIMEMultipartMailContainer *>(component),mail->Body());
391 
392 		fList->AddItem(new TListItem(component));
393 	}
394 }
395 
396 
397 //	#pragma mark -
398 
399 
400 TListView::TListView(BRect rect, TEnclosuresView *view)
401 	:
402 	BListView(rect, "", B_MULTIPLE_SELECTION_LIST,
403 		B_FOLLOW_TOP | B_FOLLOW_LEFT_RIGHT),
404 	fParent(view)
405 {
406 }
407 
408 
409 void
410 TListView::AttachedToWindow()
411 {
412 	BListView::AttachedToWindow();
413 
414 	BFont font(be_plain_font);
415 	font.SetSize(font.Size() * kPlainFontSizeScale);
416 	SetFont(&font);
417 }
418 
419 
420 void
421 TListView::MakeFocus(bool focus)
422 {
423 	BListView::MakeFocus(focus);
424 	fParent->Focus(focus);
425 }
426 
427 
428 void
429 TListView::MouseDown(BPoint point)
430 {
431 	int32 buttons;
432 	Looper()->CurrentMessage()->FindInt32("buttons", &buttons);
433 
434 	BListView::MouseDown(point);
435 
436 	if ((buttons & B_SECONDARY_MOUSE_BUTTON) != 0 && IndexOf(point) >= 0) {
437 		BPopUpMenu menu("enclosure", false, false);
438 		menu.SetFont(be_plain_font);
439 		menu.AddItem(new BMenuItem(B_TRANSLATE("Open attachment"),
440 			new BMessage(LIST_INVOKED)));
441 		menu.AddItem(new BMenuItem(B_TRANSLATE("Remove attachment"),
442 			new BMessage(M_REMOVE)));
443 
444 		BPoint menuStart = ConvertToScreen(point);
445 
446 		BMenuItem* item = menu.Go(menuStart);
447 		if (item != NULL) {
448 			if (item->Command() == LIST_INVOKED) {
449 				BMessage msg(LIST_INVOKED);
450 				msg.AddPointer("source",this);
451 				msg.AddInt32("index",IndexOf(point));
452 				Window()->PostMessage(&msg,fParent);
453 			} else {
454 				Select(IndexOf(point));
455 				Window()->PostMessage(item->Command(),fParent);
456 			}
457 		}
458 	}
459 }
460 
461 
462 void
463 TListView::KeyDown(const char *bytes, int32 numBytes)
464 {
465 	BListView::KeyDown(bytes,numBytes);
466 
467 	if (numBytes == 1 && *bytes == B_DELETE)
468 		Window()->PostMessage(M_REMOVE, fParent);
469 }
470 
471 
472 //	#pragma mark -
473 
474 
475 TListItem::TListItem(entry_ref *ref)
476 {
477 	fComponent = NULL;
478 	fRef = *ref;
479 
480 	BEntry entry(ref);
481 	entry.GetNodeRef(&fNodeRef);
482 }
483 
484 
485 TListItem::TListItem(BMailComponent *component)
486 	:
487 	fComponent(component)
488 {
489 }
490 
491 
492 void
493 TListItem::Update(BView *owner, const BFont *font)
494 {
495 	BListItem::Update(owner, font);
496 
497 	if (Height() < 17)	// mini icon height + 1
498 		SetHeight(17);
499 }
500 
501 
502 void
503 TListItem::DrawItem(BView *owner, BRect r, bool /* complete */)
504 {
505 	if (IsSelected()) {
506 		owner->SetHighColor(180, 180, 180);
507 		owner->SetLowColor(180, 180, 180);
508 	} else {
509 		owner->SetHighColor(255, 255, 255);
510 		owner->SetLowColor(255, 255, 255);
511 	}
512 	owner->FillRect(r);
513 	owner->SetHighColor(0, 0, 0);
514 
515 	BFont font = *be_plain_font;
516 	font.SetSize(font.Size() * kPlainFontSizeScale);
517 	owner->SetFont(&font);
518 	owner->MovePenTo(r.left + 24, r.bottom - 4);
519 
520 	if (fComponent) {
521 		// if it's already a mail component, we don't have an icon to
522 		// draw, and the entry_ref is invalid
523 		BMailAttachment *attachment = dynamic_cast<BMailAttachment *>(fComponent);
524 
525 		char name[B_FILE_NAME_LENGTH * 2];
526 		if ((attachment == NULL) || (attachment->FileName(name) < B_OK))
527 			strcpy(name, "unnamed");
528 
529 		BMimeType type;
530 		if (fComponent->MIMEType(&type) == B_OK)
531 			sprintf(name + strlen(name), ", Type: %s", type.Type());
532 
533 		owner->DrawString(name);
534 
535 		BRect iconRect(0, 0, B_MINI_ICON - 1, B_MINI_ICON - 1);
536 
537 		BBitmap bitmap(iconRect, B_RGBA32);
538 		if (GetTrackerIcon(type, &bitmap, B_MINI_ICON) == B_NO_ERROR) {
539 			BRect rect(r.left + 4, r.top + 1, r.left + 4 + 15, r.top + 1 + 15);
540 			owner->SetDrawingMode(B_OP_ALPHA);
541 			owner->DrawBitmap(&bitmap, iconRect, rect);
542 			owner->SetDrawingMode(B_OP_COPY);
543 		} else {
544 			// ToDo: find some nicer image for this :-)
545 			owner->SetHighColor(150, 150, 150);
546 			owner->FillEllipse(BRect(r.left + 8, r.top + 4, r.left + 16, r.top + 13));
547 		}
548 		return;
549 	}
550 
551 	BFile file(&fRef, O_RDONLY);
552 	BEntry entry(&fRef);
553 	BPath path;
554 	if (entry.GetPath(&path) == B_OK && file.InitCheck() == B_OK) {
555 		owner->DrawString(path.Path());
556 
557 		BNodeInfo info(&file);
558 		BRect sr(0, 0, B_MINI_ICON - 1, B_MINI_ICON - 1);
559 
560 		BBitmap bitmap(sr, B_RGBA32);
561 		if (info.GetTrackerIcon(&bitmap, B_MINI_ICON) == B_NO_ERROR) {
562 			BRect dr(r.left + 4, r.top + 1, r.left + 4 + 15, r.top + 1 + 15);
563 			owner->SetDrawingMode(B_OP_ALPHA);
564 			owner->DrawBitmap(&bitmap, sr, dr);
565 			owner->SetDrawingMode(B_OP_COPY);
566 		}
567 	} else
568 		owner->DrawString("<missing attachment>");
569 }
570