xref: /haiku/src/apps/mail/Enclosures.cpp (revision 9760dcae2038d47442f4658c2575844c6cf92c40)
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 CONNECTION
23 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 registered trademarks
30 of Be Incorporated in the United States and other countries. Other brand product
31 names are registered trademarks or trademarks of their respective holders.
32 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 and handling the messages (TEnclosuresView).
40 //--------------------------------------------------------------------
41 
42 #include "Enclosures.h"
43 
44 #include <Alert.h>
45 #include <Beep.h>
46 #include <Bitmap.h>
47 #include <Debug.h>
48 #include <Locale.h>
49 #include <MenuItem.h>
50 #include <NodeMonitor.h>
51 #include <PopUpMenu.h>
52 
53 #include <MailAttachment.h>
54 #include <MailMessage.h>
55 
56 #include <stdio.h>
57 #include <stdlib.h>
58 #include <string.h>
59 
60 #include "MailApp.h"
61 #include "MailSupport.h"
62 #include "MailWindow.h"
63 #include "Messages.h"
64 
65 
66 #define TR_CONTEXT "Mail"
67 
68 
69 static const float kPlainFontSizeScale = 0.9;
70 
71 static status_t
72 GetTrackerIcon(BMimeType &type, BBitmap *icon, icon_size iconSize)
73 {
74 	// set some icon size related variables
75 	status_t error = B_OK;
76 	BRect bounds;
77 	switch (iconSize) {
78 		case B_MINI_ICON:
79 			bounds.Set(0, 0, 15, 15);
80 			break;
81 		case B_LARGE_ICON:
82 			bounds.Set(0, 0, 31, 31);
83 			break;
84 		default:
85 			error = B_BAD_VALUE;
86 			break;
87 	}
88 	// check parameters and initialization
89 	if (error == B_OK
90 		&& (!icon || icon->InitCheck() != B_OK || icon->Bounds() != bounds))
91 		return B_BAD_VALUE;
92 
93 	bool success = false;
94 
95 	// Ask the MIME database for the preferred application for the file type
96 	// and whether this application has a special icon for the type.
97 	char signature[B_MIME_TYPE_LENGTH];
98 	if (type.GetPreferredApp(signature) == B_OK) {
99 		BMimeType type(signature);
100 		success = (type.GetIconForType(type.Type(), icon, iconSize) == B_OK);
101 	}
102 
103 	// Ask the MIME database whether there is an icon for the node's file type.
104 	if (error == B_OK && !success)
105 		success = (type.GetIcon(icon, iconSize) == B_OK);
106 
107 	// Ask the MIME database for the super type and start all over
108 	if (error == B_OK && !success) {
109 		BMimeType super;
110 		if (type.GetSupertype(&super) == B_OK)
111 			return GetTrackerIcon(super, icon, iconSize);
112 	}
113 
114 	// Return the icon for "application/octet-stream" from the MIME database.
115 	if (error == B_OK && !success) {
116 		// get the "application/octet-stream" icon
117 		BMimeType type("application/octet-stream");
118 		error = type.GetIcon(icon, iconSize);
119 	}
120 
121 	return error;
122 }
123 
124 
125 //	#pragma mark -
126 
127 
128 TEnclosuresView::TEnclosuresView(BRect rect, BRect wind_rect)
129 	: BView(rect, "m_enclosures", B_FOLLOW_TOP | B_FOLLOW_LEFT_RIGHT, B_WILL_DRAW),
130 	fFocus(false)
131 {
132 	SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
133 
134 	BFont font(be_plain_font);
135 	font.SetSize(font.Size() * kPlainFontSizeScale);
136 	SetFont(&font);
137 
138 	fOffset = 12;
139 
140 	BRect r;
141 	r.left = ENCLOSE_TEXT_H + font.StringWidth(
142 		TR("Attachments: ")) + 5;
143 	r.top = ENCLOSE_FIELD_V;
144 	r.right = wind_rect.right - wind_rect.left - B_V_SCROLL_BAR_WIDTH - 9;
145 	r.bottom = Frame().Height() - 8;
146 	fList = new TListView(r, this);
147 	fList->SetInvocationMessage(new BMessage(LIST_INVOKED));
148 
149 	BScrollView	*scroll = new BScrollView("", fList, B_FOLLOW_LEFT_RIGHT |
150 			B_FOLLOW_TOP, 0, false, true);
151 	AddChild(scroll);
152 	scroll->ScrollBar(B_VERTICAL)->SetRange(0, 0);
153 }
154 
155 
156 TEnclosuresView::~TEnclosuresView()
157 {
158 	for (int32 index = fList->CountItems();index-- > 0;)
159 	{
160 		TListItem *item = static_cast<TListItem *>(fList->ItemAt(index));
161 		fList->RemoveItem(index);
162 
163 		if (item->Component() == NULL)
164 			watch_node(item->NodeRef(), B_STOP_WATCHING, this);
165 		delete item;
166 	}
167 }
168 
169 
170 void
171 TEnclosuresView::Draw(BRect where)
172 {
173 	BView::Draw(where);
174 
175 	SetHighColor(0, 0, 0);
176 	SetLowColor(ViewColor());
177 
178 	font_height fh;
179 	GetFontHeight(&fh);
180 
181 	MovePenTo(ENCLOSE_TEXT_H, ENCLOSE_TEXT_V + fh.ascent);
182 	DrawString(ENCLOSE_TEXT);
183 }
184 
185 
186 void
187 TEnclosuresView::MessageReceived(BMessage *msg)
188 {
189 	switch (msg->what)
190 	{
191 		case LIST_INVOKED:
192 		{
193 			BListView *list;
194 			msg->FindPointer("source", (void **)&list);
195 			if (list)
196 			{
197 				TListItem *item = (TListItem *) (list->ItemAt(msg->FindInt32("index")));
198 				if (item)
199 				{
200 					BMessenger tracker("application/x-vnd.Be-TRAK");
201 					if (tracker.IsValid())
202 					{
203 						BMessage message(B_REFS_RECEIVED);
204 						message.AddRef("refs", item->Ref());
205 
206 						tracker.SendMessage(&message);
207 					}
208 				}
209 			}
210 			break;
211 		}
212 
213 		case M_REMOVE:
214 		{
215 			int32 index;
216 			while ((index = fList->CurrentSelection()) >= 0)
217 			{
218 				TListItem *item = (TListItem *) fList->ItemAt(index);
219 				fList->RemoveItem(index);
220 
221 				if (item->Component())
222 				{
223 					TMailWindow *window = dynamic_cast<TMailWindow *>(Window());
224 					if (window && window->Mail())
225 						window->Mail()->RemoveComponent(item->Component());
226 
227 					(new BAlert("", TR(
228 						"Removing attachments from a forwarded mail is not yet "
229 						"implemented!\nIt will not yet work correctly."),
230 						TR("OK")))->Go();
231 				}
232 				else
233 					watch_node(item->NodeRef(), B_STOP_WATCHING, this);
234 				delete item;
235 			}
236 			break;
237 		}
238 
239 		case M_SELECT:
240 			fList->Select(0, fList->CountItems() - 1, true);
241 			break;
242 
243 		case B_SIMPLE_DATA:
244 		case B_REFS_RECEIVED:
245 		case REFS_RECEIVED:
246 			if (msg->HasRef("refs"))
247 			{
248 				bool badType = false;
249 
250 				int32 index = 0;
251 				entry_ref ref;
252 				while (msg->FindRef("refs", index++, &ref) == B_NO_ERROR)
253 				{
254 					BFile file(&ref, O_RDONLY);
255 					if (file.InitCheck() == B_OK && file.IsFile())
256 					{
257 						TListItem *item;
258 						for (int16 loop = 0; loop < fList->CountItems(); loop++)
259 						{
260 							item = (TListItem *) fList->ItemAt(loop);
261 							if (ref == *(item->Ref()))
262 							{
263 								fList->Select(loop);
264 								fList->ScrollToSelection();
265 								continue;
266 							}
267 						}
268 						fList->AddItem(item = new TListItem(&ref));
269 						fList->Select(fList->CountItems() - 1);
270 						fList->ScrollToSelection();
271 
272 						watch_node(item->NodeRef(), B_WATCH_NAME, this);
273 					}
274 					else
275 						badType = true;
276 				}
277 				if (badType)
278 				{
279 					beep();
280 					(new BAlert("",
281 						TR("Only files can be added as attachments."),
282 						TR("OK")))->Go();
283 				}
284 			}
285 			break;
286 
287 		case B_NODE_MONITOR:
288 		{
289 			int32 opcode;
290 			if (msg->FindInt32("opcode", &opcode) == B_NO_ERROR)
291 			{
292 				dev_t device;
293 				if (msg->FindInt32("device", &device) < B_OK)
294 					break;
295 				ino_t inode;
296 				if (msg->FindInt64("node", &inode) < B_OK)
297 					break;
298 
299 				for (int32 index = fList->CountItems();index-- > 0;)
300 				{
301 					TListItem *item = static_cast<TListItem *>(fList->ItemAt(index));
302 
303 					if (device == item->NodeRef()->device
304 						&& inode == item->NodeRef()->node)
305 					{
306 						if (opcode == B_ENTRY_REMOVED)
307 						{
308 							// don't hide the <missing attachment> item
309 
310 							//fList->RemoveItem(index);
311 							//
312 							//watch_node(item->NodeRef(), B_STOP_WATCHING, this);
313 							//delete item;
314 						}
315 						else if (opcode == B_ENTRY_MOVED)
316 						{
317 							item->Ref()->device = device;
318 							msg->FindInt64("to directory", &item->Ref()->directory);
319 
320 							const char *name;
321 							msg->FindString("name", &name);
322 							item->Ref()->set_name(name);
323 						}
324 
325 						fList->InvalidateItem(index);
326 						break;
327 					}
328 				}
329 			}
330 			break;
331 		}
332 
333 		default:
334 			BView::MessageReceived(msg);
335 	}
336 }
337 
338 
339 void
340 TEnclosuresView::Focus(bool focus)
341 {
342 	if (fFocus != focus)
343 	{
344 		fFocus = focus;
345 		Draw(Frame());
346 	}
347 }
348 
349 
350 static void recursive_attachment_search(TEnclosuresView *us,BMailContainer *mail,BMailComponent *body) {
351 	if (mail == NULL)
352 		return;
353 	for (int32 i = 0; i < mail->CountComponents(); i++)
354 	{
355 		BMailComponent *component = mail->GetComponent(i);
356 		if (component == body)
357 			continue;
358 
359 		if (component->ComponentType() == B_MAIL_MULTIPART_CONTAINER)
360 			recursive_attachment_search(us,dynamic_cast<BMIMEMultipartMailContainer *>(component),body);
361 
362 		us->fList->AddItem(new TListItem(component));
363 	}
364 }
365 
366 void
367 TEnclosuresView::AddEnclosuresFromMail(BEmailMessage *mail)
368 {
369 	for (int32 i = 0; i < mail->CountComponents(); i++)
370 	{
371 		BMailComponent *component = mail->GetComponent(i);
372 		if (component == mail->Body())
373 			continue;
374 
375 		if (component->ComponentType() == B_MAIL_MULTIPART_CONTAINER)
376 			recursive_attachment_search(this,dynamic_cast<BMIMEMultipartMailContainer *>(component),mail->Body());
377 
378 		fList->AddItem(new TListItem(component));
379 	}
380 }
381 
382 
383 //====================================================================
384 //	#pragma mark -
385 
386 
387 TListView::TListView(BRect rect, TEnclosuresView *view)
388 	:	BListView(rect, "", B_MULTIPLE_SELECTION_LIST, B_FOLLOW_TOP | B_FOLLOW_LEFT_RIGHT),
389 	fParent(view)
390 {
391 }
392 
393 
394 void
395 TListView::AttachedToWindow()
396 {
397 	BListView::AttachedToWindow();
398 
399 	BFont font(be_plain_font);
400 	font.SetSize(font.Size() * kPlainFontSizeScale);
401 	SetFont(&font);
402 }
403 
404 
405 void
406 TListView::MakeFocus(bool focus)
407 {
408 	BListView::MakeFocus(focus);
409 	fParent->Focus(focus);
410 }
411 
412 
413 void
414 TListView::MouseDown(BPoint point)
415 {
416 	int32 buttons;
417 	Looper()->CurrentMessage()->FindInt32("buttons",&buttons);
418 
419 	if (buttons & B_SECONDARY_MOUSE_BUTTON)
420 	{
421 		BFont font = *be_plain_font;
422 		font.SetSize(10);
423 
424 		BPopUpMenu menu("enclosure", false, false);
425 		menu.SetFont(&font);
426 		menu.AddItem(new BMenuItem(TR("Open attachment"),
427 			new BMessage(LIST_INVOKED)));
428 		menu.AddItem(new BMenuItem(TR("Remove attachment"),
429 			new BMessage(M_REMOVE)));
430 
431 		BPoint menuStart = ConvertToScreen(point);
432 
433 		BMenuItem *item;
434 		if ((item = menu.Go(menuStart)) != NULL)
435 		{
436 			if (item->Command() == LIST_INVOKED)
437 			{
438 				BMessage msg(LIST_INVOKED);
439 				msg.AddPointer("source",this);
440 				msg.AddInt32("index",IndexOf(point));
441 				Window()->PostMessage(&msg,fParent);
442 			}
443 			else
444 			{
445 				Select(IndexOf(point));
446 				Window()->PostMessage(item->Command(),fParent);
447 			}
448 		}
449 	}
450 	else
451 		BListView::MouseDown(point);
452 }
453 
454 
455 void
456 TListView::KeyDown(const char *bytes, int32 numBytes)
457 {
458 	BListView::KeyDown(bytes,numBytes);
459 
460 	if (numBytes == 1 && *bytes == B_DELETE)
461 		Window()->PostMessage(M_REMOVE, fParent);
462 }
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_COLOR_8_BIT);
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_OVER);
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_COLOR_8_BIT);
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_OVER);
558 			owner->DrawBitmap(&bitmap, sr, dr);
559 			owner->SetDrawingMode(B_OP_COPY);
560 		}
561 	} else
562 		owner->DrawString("<missing attachment>");
563 }
564 
565