xref: /haiku/src/apps/mail/Enclosures.cpp (revision 4466b89c65970de4c7236ac87faa2bee4589f413)
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_TRANSLATE_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 					(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")))->Go();
256 				}
257 				else
258 					watch_node(item->NodeRef(), B_STOP_WATCHING, this);
259 				delete item;
260 			}
261 			break;
262 		}
263 
264 		case M_SELECT:
265 			fList->Select(0, fList->CountItems() - 1, true);
266 			break;
267 
268 		case B_SIMPLE_DATA:
269 		case B_REFS_RECEIVED:
270 		case REFS_RECEIVED:
271 			if (msg->HasRef("refs"))
272 			{
273 				bool badType = false;
274 
275 				int32 index = 0;
276 				entry_ref ref;
277 				while (msg->FindRef("refs", index++, &ref) == B_NO_ERROR)
278 				{
279 					BFile file(&ref, O_RDONLY);
280 					if (file.InitCheck() == B_OK && file.IsFile())
281 					{
282 						TListItem *item;
283 						for (int16 loop = 0; loop < fList->CountItems(); loop++)
284 						{
285 							item = (TListItem *) fList->ItemAt(loop);
286 							if (ref == *(item->Ref()))
287 							{
288 								fList->Select(loop);
289 								fList->ScrollToSelection();
290 								continue;
291 							}
292 						}
293 						fList->AddItem(item = new TListItem(&ref));
294 						fList->Select(fList->CountItems() - 1);
295 						fList->ScrollToSelection();
296 
297 						watch_node(item->NodeRef(), B_WATCH_NAME, this);
298 					}
299 					else
300 						badType = true;
301 				}
302 				if (badType)
303 				{
304 					beep();
305 					(new BAlert("",
306 						B_TRANSLATE("Only files can be added as attachments."),
307 						B_TRANSLATE("OK")))->Go();
308 				}
309 			}
310 			break;
311 
312 		case B_NODE_MONITOR:
313 		{
314 			int32 opcode;
315 			if (msg->FindInt32("opcode", &opcode) == B_NO_ERROR)
316 			{
317 				dev_t device;
318 				if (msg->FindInt32("device", &device) < B_OK)
319 					break;
320 				ino_t inode;
321 				if (msg->FindInt64("node", &inode) < B_OK)
322 					break;
323 
324 				for (int32 index = fList->CountItems();index-- > 0;)
325 				{
326 					TListItem *item = static_cast<TListItem *>(fList->ItemAt(index));
327 
328 					if (device == item->NodeRef()->device
329 						&& inode == item->NodeRef()->node)
330 					{
331 						if (opcode == B_ENTRY_REMOVED)
332 						{
333 							// don't hide the <missing attachment> item
334 
335 							//fList->RemoveItem(index);
336 							//
337 							//watch_node(item->NodeRef(), B_STOP_WATCHING, this);
338 							//delete item;
339 						}
340 						else if (opcode == B_ENTRY_MOVED)
341 						{
342 							item->Ref()->device = device;
343 							msg->FindInt64("to directory", &item->Ref()->directory);
344 
345 							const char *name;
346 							msg->FindString("name", &name);
347 							item->Ref()->set_name(name);
348 						}
349 
350 						fList->InvalidateItem(index);
351 						break;
352 					}
353 				}
354 			}
355 			break;
356 		}
357 
358 		default:
359 			BView::MessageReceived(msg);
360 	}
361 }
362 
363 
364 void
365 TEnclosuresView::Focus(bool focus)
366 {
367 	if (fFocus != focus)
368 	{
369 		fFocus = focus;
370 		Draw(Frame());
371 	}
372 }
373 
374 
375 void
376 TEnclosuresView::AddEnclosuresFromMail(BEmailMessage *mail)
377 {
378 	for (int32 i = 0; i < mail->CountComponents(); i++)
379 	{
380 		BMailComponent *component = mail->GetComponent(i);
381 		if (component == mail->Body())
382 			continue;
383 
384 		if (component->ComponentType() == B_MAIL_MULTIPART_CONTAINER)
385 			recursive_attachment_search(this,dynamic_cast<BMIMEMultipartMailContainer *>(component),mail->Body());
386 
387 		fList->AddItem(new TListItem(component));
388 	}
389 }
390 
391 
392 //	#pragma mark -
393 
394 
395 TListView::TListView(BRect rect, TEnclosuresView *view)
396 	:
397 	BListView(rect, "", B_MULTIPLE_SELECTION_LIST,
398 		B_FOLLOW_TOP | B_FOLLOW_LEFT_RIGHT),
399 	fParent(view)
400 {
401 }
402 
403 
404 void
405 TListView::AttachedToWindow()
406 {
407 	BListView::AttachedToWindow();
408 
409 	BFont font(be_plain_font);
410 	font.SetSize(font.Size() * kPlainFontSizeScale);
411 	SetFont(&font);
412 }
413 
414 
415 void
416 TListView::MakeFocus(bool focus)
417 {
418 	BListView::MakeFocus(focus);
419 	fParent->Focus(focus);
420 }
421 
422 
423 void
424 TListView::MouseDown(BPoint point)
425 {
426 	int32 buttons;
427 	Looper()->CurrentMessage()->FindInt32("buttons", &buttons);
428 
429 	if ((buttons & B_SECONDARY_MOUSE_BUTTON) != 0) {
430 		BPopUpMenu menu("enclosure", false, false);
431 		menu.SetFont(be_plain_font);
432 		menu.AddItem(new BMenuItem(B_TRANSLATE("Open attachment"),
433 			new BMessage(LIST_INVOKED)));
434 		menu.AddItem(new BMenuItem(B_TRANSLATE("Remove attachment"),
435 			new BMessage(M_REMOVE)));
436 
437 		BPoint menuStart = ConvertToScreen(point);
438 
439 		BMenuItem* item = menu.Go(menuStart);
440 		if (item != NULL) {
441 			if (item->Command() == LIST_INVOKED) {
442 				BMessage msg(LIST_INVOKED);
443 				msg.AddPointer("source",this);
444 				msg.AddInt32("index",IndexOf(point));
445 				Window()->PostMessage(&msg,fParent);
446 			} else {
447 				Select(IndexOf(point));
448 				Window()->PostMessage(item->Command(),fParent);
449 			}
450 		}
451 	} else
452 		BListView::MouseDown(point);
453 }
454 
455 
456 void
457 TListView::KeyDown(const char *bytes, int32 numBytes)
458 {
459 	BListView::KeyDown(bytes,numBytes);
460 
461 	if (numBytes == 1 && *bytes == B_DELETE)
462 		Window()->PostMessage(M_REMOVE, fParent);
463 }
464 
465 
466 //	#pragma mark -
467 
468 
469 TListItem::TListItem(entry_ref *ref)
470 {
471 	fComponent = NULL;
472 	fRef = *ref;
473 
474 	BEntry entry(ref);
475 	entry.GetNodeRef(&fNodeRef);
476 }
477 
478 
479 TListItem::TListItem(BMailComponent *component)
480 	:
481 	fComponent(component)
482 {
483 }
484 
485 
486 void
487 TListItem::Update(BView *owner, const BFont *font)
488 {
489 	BListItem::Update(owner, font);
490 
491 	if (Height() < 17)	// mini icon height + 1
492 		SetHeight(17);
493 }
494 
495 
496 void
497 TListItem::DrawItem(BView *owner, BRect r, bool /* complete */)
498 {
499 	if (IsSelected()) {
500 		owner->SetHighColor(180, 180, 180);
501 		owner->SetLowColor(180, 180, 180);
502 	} else {
503 		owner->SetHighColor(255, 255, 255);
504 		owner->SetLowColor(255, 255, 255);
505 	}
506 	owner->FillRect(r);
507 	owner->SetHighColor(0, 0, 0);
508 
509 	BFont font = *be_plain_font;
510 	font.SetSize(font.Size() * kPlainFontSizeScale);
511 	owner->SetFont(&font);
512 	owner->MovePenTo(r.left + 24, r.bottom - 4);
513 
514 	if (fComponent) {
515 		// if it's already a mail component, we don't have an icon to
516 		// draw, and the entry_ref is invalid
517 		BMailAttachment *attachment = dynamic_cast<BMailAttachment *>(fComponent);
518 
519 		char name[B_FILE_NAME_LENGTH * 2];
520 		if ((attachment == NULL) || (attachment->FileName(name) < B_OK))
521 			strcpy(name, "unnamed");
522 
523 		BMimeType type;
524 		if (fComponent->MIMEType(&type) == B_OK)
525 			sprintf(name + strlen(name), ", Type: %s", type.Type());
526 
527 		owner->DrawString(name);
528 
529 		BRect iconRect(0, 0, B_MINI_ICON - 1, B_MINI_ICON - 1);
530 
531 		BBitmap bitmap(iconRect, B_RGBA32);
532 		if (GetTrackerIcon(type, &bitmap, B_MINI_ICON) == B_NO_ERROR) {
533 			BRect rect(r.left + 4, r.top + 1, r.left + 4 + 15, r.top + 1 + 15);
534 			owner->SetDrawingMode(B_OP_ALPHA);
535 			owner->DrawBitmap(&bitmap, iconRect, rect);
536 			owner->SetDrawingMode(B_OP_COPY);
537 		} else {
538 			// ToDo: find some nicer image for this :-)
539 			owner->SetHighColor(150, 150, 150);
540 			owner->FillEllipse(BRect(r.left + 8, r.top + 4, r.left + 16, r.top + 13));
541 		}
542 		return;
543 	}
544 
545 	BFile file(&fRef, O_RDONLY);
546 	BEntry entry(&fRef);
547 	BPath path;
548 	if (entry.GetPath(&path) == B_OK && file.InitCheck() == B_OK) {
549 		owner->DrawString(path.Path());
550 
551 		BNodeInfo info(&file);
552 		BRect sr(0, 0, B_MINI_ICON - 1, B_MINI_ICON - 1);
553 
554 		BBitmap bitmap(sr, B_RGBA32);
555 		if (info.GetTrackerIcon(&bitmap, B_MINI_ICON) == B_NO_ERROR) {
556 			BRect dr(r.left + 4, r.top + 1, r.left + 4 + 15, r.top + 1 + 15);
557 			owner->SetDrawingMode(B_OP_ALPHA);
558 			owner->DrawBitmap(&bitmap, sr, dr);
559 			owner->SetDrawingMode(B_OP_COPY);
560 		}
561 	} else
562 		owner->DrawString("<missing attachment>");
563 }
564 
565