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