xref: /haiku/src/apps/mail/Enclosures.cpp (revision 220d04022750f40f8bac8f01fa551211e28d04f2)
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 	SetViewColor(ui_color(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 	}
365 }
366 
367 
368 void
369 TEnclosuresView::Focus(bool focus)
370 {
371 	if (fFocus != focus)
372 	{
373 		fFocus = focus;
374 		Draw(Frame());
375 	}
376 }
377 
378 
379 void
380 TEnclosuresView::AddEnclosuresFromMail(BEmailMessage *mail)
381 {
382 	for (int32 i = 0; i < mail->CountComponents(); i++)
383 	{
384 		BMailComponent *component = mail->GetComponent(i);
385 		if (component == mail->Body())
386 			continue;
387 
388 		if (component->ComponentType() == B_MAIL_MULTIPART_CONTAINER)
389 			recursive_attachment_search(this,dynamic_cast<BMIMEMultipartMailContainer *>(component),mail->Body());
390 
391 		fList->AddItem(new TListItem(component));
392 	}
393 }
394 
395 
396 //	#pragma mark -
397 
398 
399 TListView::TListView(BRect rect, TEnclosuresView *view)
400 	:
401 	BListView(rect, "", B_MULTIPLE_SELECTION_LIST,
402 		B_FOLLOW_TOP | B_FOLLOW_LEFT_RIGHT),
403 	fParent(view)
404 {
405 }
406 
407 
408 void
409 TListView::AttachedToWindow()
410 {
411 	BListView::AttachedToWindow();
412 
413 	BFont font(be_plain_font);
414 	font.SetSize(font.Size() * kPlainFontSizeScale);
415 	SetFont(&font);
416 }
417 
418 
419 void
420 TListView::MakeFocus(bool focus)
421 {
422 	BListView::MakeFocus(focus);
423 	fParent->Focus(focus);
424 }
425 
426 
427 void
428 TListView::MouseDown(BPoint point)
429 {
430 	int32 buttons;
431 	Looper()->CurrentMessage()->FindInt32("buttons", &buttons);
432 
433 	BListView::MouseDown(point);
434 
435 	if ((buttons & B_SECONDARY_MOUSE_BUTTON) != 0 && IndexOf(point) >= 0) {
436 		BPopUpMenu menu("enclosure", false, false);
437 		menu.SetFont(be_plain_font);
438 		menu.AddItem(new BMenuItem(B_TRANSLATE("Open attachment"),
439 			new BMessage(LIST_INVOKED)));
440 		menu.AddItem(new BMenuItem(B_TRANSLATE("Remove attachment"),
441 			new BMessage(M_REMOVE)));
442 
443 		BPoint menuStart = ConvertToScreen(point);
444 
445 		BMenuItem* item = menu.Go(menuStart);
446 		if (item != NULL) {
447 			if (item->Command() == LIST_INVOKED) {
448 				BMessage msg(LIST_INVOKED);
449 				msg.AddPointer("source",this);
450 				msg.AddInt32("index",IndexOf(point));
451 				Window()->PostMessage(&msg,fParent);
452 			} else {
453 				Select(IndexOf(point));
454 				Window()->PostMessage(item->Command(),fParent);
455 			}
456 		}
457 	}
458 }
459 
460 
461 void
462 TListView::KeyDown(const char *bytes, int32 numBytes)
463 {
464 	BListView::KeyDown(bytes,numBytes);
465 
466 	if (numBytes == 1 && *bytes == B_DELETE)
467 		Window()->PostMessage(M_REMOVE, fParent);
468 }
469 
470 
471 //	#pragma mark -
472 
473 
474 TListItem::TListItem(entry_ref *ref)
475 {
476 	fComponent = NULL;
477 	fRef = *ref;
478 
479 	BEntry entry(ref);
480 	entry.GetNodeRef(&fNodeRef);
481 }
482 
483 
484 TListItem::TListItem(BMailComponent *component)
485 	:
486 	fComponent(component)
487 {
488 }
489 
490 
491 void
492 TListItem::Update(BView *owner, const BFont *font)
493 {
494 	BListItem::Update(owner, font);
495 
496 	if (Height() < 17)	// mini icon height + 1
497 		SetHeight(17);
498 }
499 
500 
501 void
502 TListItem::DrawItem(BView *owner, BRect r, bool /* complete */)
503 {
504 	if (IsSelected()) {
505 		owner->SetHighColor(180, 180, 180);
506 		owner->SetLowColor(180, 180, 180);
507 	} else {
508 		owner->SetHighColor(255, 255, 255);
509 		owner->SetLowColor(255, 255, 255);
510 	}
511 	owner->FillRect(r);
512 	owner->SetHighColor(0, 0, 0);
513 
514 	BFont font = *be_plain_font;
515 	font.SetSize(font.Size() * kPlainFontSizeScale);
516 	owner->SetFont(&font);
517 	owner->MovePenTo(r.left + 24, r.bottom - 4);
518 
519 	if (fComponent) {
520 		// if it's already a mail component, we don't have an icon to
521 		// draw, and the entry_ref is invalid
522 		BMailAttachment *attachment = dynamic_cast<BMailAttachment *>(fComponent);
523 
524 		char name[B_FILE_NAME_LENGTH * 2];
525 		if ((attachment == NULL) || (attachment->FileName(name) < B_OK))
526 			strcpy(name, "unnamed");
527 
528 		BMimeType type;
529 		if (fComponent->MIMEType(&type) == B_OK)
530 			sprintf(name + strlen(name), ", Type: %s", type.Type());
531 
532 		owner->DrawString(name);
533 
534 		BRect iconRect(0, 0, B_MINI_ICON - 1, B_MINI_ICON - 1);
535 
536 		BBitmap bitmap(iconRect, B_RGBA32);
537 		if (GetTrackerIcon(type, &bitmap, B_MINI_ICON) == B_NO_ERROR) {
538 			BRect rect(r.left + 4, r.top + 1, r.left + 4 + 15, r.top + 1 + 15);
539 			owner->SetDrawingMode(B_OP_ALPHA);
540 			owner->DrawBitmap(&bitmap, iconRect, rect);
541 			owner->SetDrawingMode(B_OP_COPY);
542 		} else {
543 			// ToDo: find some nicer image for this :-)
544 			owner->SetHighColor(150, 150, 150);
545 			owner->FillEllipse(BRect(r.left + 8, r.top + 4, r.left + 16, r.top + 13));
546 		}
547 		return;
548 	}
549 
550 	BFile file(&fRef, O_RDONLY);
551 	BEntry entry(&fRef);
552 	BPath path;
553 	if (entry.GetPath(&path) == B_OK && file.InitCheck() == B_OK) {
554 		owner->DrawString(path.Path());
555 
556 		BNodeInfo info(&file);
557 		BRect sr(0, 0, B_MINI_ICON - 1, B_MINI_ICON - 1);
558 
559 		BBitmap bitmap(sr, B_RGBA32);
560 		if (info.GetTrackerIcon(&bitmap, B_MINI_ICON) == B_NO_ERROR) {
561 			BRect dr(r.left + 4, r.top + 1, r.left + 4 + 15, r.top + 1 + 15);
562 			owner->SetDrawingMode(B_OP_ALPHA);
563 			owner->DrawBitmap(&bitmap, sr, dr);
564 			owner->SetDrawingMode(B_OP_COPY);
565 		}
566 	} else
567 		owner->DrawString("<missing attachment>");
568 }
569 
570