xref: /haiku/src/apps/mail/Enclosures.cpp (revision 93a78ecaa45114d68952d08c4778f073515102f2)
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 "Mail.h"
43 #include "Enclosures.h"
44 
45 #include <Debug.h>
46 #include <Beep.h>
47 #include <Bitmap.h>
48 #include <MenuItem.h>
49 #include <Alert.h>
50 #include <NodeMonitor.h>
51 
52 #include <MailAttachment.h>
53 #include <MailMessage.h>
54 
55 #include <MDRLanguage.h>
56 
57 #include <stdio.h>
58 #include <stdlib.h>
59 #include <string.h>
60 
61 
62 //====================================================================
63 
64 
65 static status_t
66 GetTrackerIcon(BMimeType &type, BBitmap *icon, icon_size iconSize)
67 {
68 	// set some icon size related variables
69 	status_t error = B_OK;
70 	BRect bounds;
71 	switch (iconSize) {
72 		case B_MINI_ICON:
73 			bounds.Set(0, 0, 15, 15);
74 			break;
75 		case B_LARGE_ICON:
76 			bounds.Set(0, 0, 31, 31);
77 			break;
78 		default:
79 			error = B_BAD_VALUE;
80 			break;
81 	}
82 	// check parameters and initialization
83 	if (error == B_OK
84 		&& (!icon || icon->InitCheck() != B_OK || icon->Bounds() != bounds))
85 		return B_BAD_VALUE;
86 
87 	bool success = false;
88 
89 	// Ask the MIME database for the preferred application for the file type
90 	// and whether this application has a special icon for the type.
91 	char signature[B_MIME_TYPE_LENGTH];
92 	if (type.GetPreferredApp(signature) == B_OK) {
93 		BMimeType type(signature);
94 		success = (type.GetIconForType(type.Type(), icon, iconSize) == B_OK);
95 	}
96 
97 	// Ask the MIME database whether there is an icon for the node's file type.
98 	if (error == B_OK && !success)
99 		success = (type.GetIcon(icon, iconSize) == B_OK);
100 
101 	// Ask the MIME database for the super type and start all over
102 	if (error == B_OK && !success) {
103 		BMimeType super;
104 		if (type.GetSupertype(&super) == B_OK)
105 			return GetTrackerIcon(super, icon, iconSize);
106 	}
107 
108 	// Return the icon for "application/octet-stream" from the MIME database.
109 	if (error == B_OK && !success) {
110 		// get the "application/octet-stream" icon
111 		BMimeType type("application/octet-stream");
112 		error = type.GetIcon(icon, iconSize);
113 	}
114 
115 	return error;
116 }
117 
118 
119 //	#pragma mark -
120 
121 
122 TEnclosuresView::TEnclosuresView(BRect rect, BRect wind_rect)
123 	: BView(rect, "m_enclosures", B_FOLLOW_TOP | B_FOLLOW_LEFT_RIGHT, B_WILL_DRAW),
124 	fFocus(false)
125 {
126 	SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
127 
128 	BFont font = *be_plain_font;
129 	font.SetSize(FONT_SIZE);
130 	font_height fHeight;
131 	font.GetHeight(&fHeight);
132 	fOffset = 12;
133 
134 	BRect r;
135 	r.left = ENCLOSE_TEXT_H + font.StringWidth(
136 		MDR_DIALECT_CHOICE ("Enclosures: ","添付ファイル")) + 5;
137 	r.top = ENCLOSE_FIELD_V;
138 	r.right = wind_rect.right - wind_rect.left - B_V_SCROLL_BAR_WIDTH - 9;
139 	r.bottom = Frame().Height() - 8;
140 	fList = new TListView(r, this);
141 	fList->SetInvocationMessage(new BMessage(LIST_INVOKED));
142 
143 	BScrollView	*scroll = new BScrollView("", fList, B_FOLLOW_LEFT_RIGHT |
144 			B_FOLLOW_TOP, 0, false, true);
145 	AddChild(scroll);
146 	scroll->ScrollBar(B_VERTICAL)->SetRange(0, 0);
147 }
148 
149 
150 TEnclosuresView::~TEnclosuresView()
151 {
152 	for (int32 index = fList->CountItems();index-- > 0;)
153 	{
154 		TListItem *item = static_cast<TListItem *>(fList->ItemAt(index));
155 		fList->RemoveItem(index);
156 
157 		if (item->Component() == NULL)
158 			watch_node(item->NodeRef(), B_STOP_WATCHING, this);
159 		delete item;
160 	}
161 }
162 
163 
164 void
165 TEnclosuresView::Draw(BRect where)
166 {
167 	BView::Draw(where);
168 
169 	BFont font = *be_plain_font;
170 	font.SetSize(FONT_SIZE);
171 	SetFont(&font);
172 	SetHighColor(0, 0, 0);
173 	SetLowColor(ViewColor());
174 
175 	font_height fHeight;
176 	font.GetHeight(&fHeight);
177 
178 	MovePenTo(ENCLOSE_TEXT_H, ENCLOSE_TEXT_V + fHeight.ascent);
179 	DrawString(ENCLOSE_TEXT);
180 }
181 
182 
183 void
184 TEnclosuresView::MessageReceived(BMessage *msg)
185 {
186 	switch (msg->what)
187 	{
188 		case LIST_INVOKED:
189 		{
190 			BListView *list;
191 			msg->FindPointer("source", (void **)&list);
192 			if (list)
193 			{
194 				TListItem *item = (TListItem *) (list->ItemAt(msg->FindInt32("index")));
195 				if (item)
196 				{
197 					BMessenger tracker("application/x-vnd.Be-TRAK");
198 					if (tracker.IsValid())
199 					{
200 						BMessage message(B_REFS_RECEIVED);
201 						message.AddRef("refs", item->Ref());
202 
203 						tracker.SendMessage(&message);
204 					}
205 				}
206 			}
207 			break;
208 		}
209 
210 		case M_REMOVE:
211 		{
212 			int32 index;
213 			while ((index = fList->CurrentSelection()) >= 0)
214 			{
215 				TListItem *item = (TListItem *) fList->ItemAt(index);
216 				fList->RemoveItem(index);
217 
218 				if (item->Component())
219 				{
220 					// remove the component from the mail
221 					TMailWindow *window = dynamic_cast<TMailWindow *>(Window());
222 					if (window && window->Mail())
223 						window->Mail()->RemoveComponent(item->Component());
224 
225 					(new BAlert("", MDR_DIALECT_CHOICE (
226 						"Removing enclosures from a forwarded mail is not yet implemented!\n"
227 						"It will not yet work correctly.",
228 						"転送メールから添付ファイルを削除する機能はまだ実装されていません。"),
229 						MDR_DIALECT_CHOICE ("OK","了解")))->Go();
230 				}
231 				else
232 					watch_node(item->NodeRef(), B_STOP_WATCHING, this);
233 				delete item;
234 			}
235 			break;
236 		}
237 
238 		case M_SELECT:
239 			fList->Select(0, fList->CountItems() - 1, true);
240 			break;
241 
242 		case B_SIMPLE_DATA:
243 		case B_REFS_RECEIVED:
244 		case REFS_RECEIVED:
245 			if (msg->HasRef("refs"))
246 			{
247 				bool badType = false;
248 
249 				int32 index = 0;
250 				entry_ref ref;
251 				while (msg->FindRef("refs", index++, &ref) == B_NO_ERROR)
252 				{
253 					BFile file(&ref, O_RDONLY);
254 					if (file.InitCheck() == B_OK && file.IsFile())
255 					{
256 						TListItem *item;
257 						for (int16 loop = 0; loop < fList->CountItems(); loop++)
258 						{
259 							item = (TListItem *) fList->ItemAt(loop);
260 							if (ref == *(item->Ref()))
261 							{
262 								fList->Select(loop);
263 								fList->ScrollToSelection();
264 								continue;
265 							}
266 						}
267 						fList->AddItem(item = new TListItem(&ref));
268 						fList->Select(fList->CountItems() - 1);
269 						fList->ScrollToSelection();
270 
271 						watch_node(item->NodeRef(), B_WATCH_NAME, this);
272 					}
273 					else
274 						badType = true;
275 				}
276 				if (badType)
277 				{
278 					beep();
279 					(new BAlert("", MDR_DIALECT_CHOICE (
280 						"Only files can be added as enclosures.",
281 						"添付できるのは、ファイルのみです。"),
282 						MDR_DIALECT_CHOICE ("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 enclosure> 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);
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(
427 			MDR_DIALECT_CHOICE ("Open Enclosure","添付ファイルを開く"),
428 			new BMessage(LIST_INVOKED)));
429 		menu.AddItem(new BMenuItem(
430 			MDR_DIALECT_CHOICE ("Remove Enclosure","添付ファイルを削除"),
431 			new BMessage(M_REMOVE)));
432 
433 		BPoint menuStart = ConvertToScreen(point);
434 
435 		BMenuItem *item;
436 		if ((item = menu.Go(menuStart)) != NULL)
437 		{
438 			if (item->Command() == LIST_INVOKED)
439 			{
440 				BMessage msg(LIST_INVOKED);
441 				msg.AddPointer("source",this);
442 				msg.AddInt32("index",IndexOf(point));
443 				Window()->PostMessage(&msg,fParent);
444 			}
445 			else
446 			{
447 				Select(IndexOf(point));
448 				Window()->PostMessage(item->Command(),fParent);
449 			}
450 		}
451 	}
452 	else
453 		BListView::MouseDown(point);
454 }
455 
456 
457 void
458 TListView::KeyDown(const char *bytes, int32 numBytes)
459 {
460 	BListView::KeyDown(bytes,numBytes);
461 
462 	if (numBytes == 1 && *bytes == B_DELETE)
463 		Window()->PostMessage(M_REMOVE, fParent);
464 }
465 
466 
467 //====================================================================
468 //	#pragma mark -
469 
470 
471 TListItem::TListItem(entry_ref *ref)
472 {
473 	fComponent = NULL;
474 	fRef = *ref;
475 
476 	BEntry entry(ref);
477 	entry.GetNodeRef(&fNodeRef);
478 }
479 
480 
481 TListItem::TListItem(BMailComponent *component)
482 	:
483 	fComponent(component)
484 {
485 }
486 
487 
488 void
489 TListItem::Update(BView *owner, const BFont *font)
490 {
491 	BListItem::Update(owner, font);
492 
493 	if (Height() < 17)	// mini icon height + 1
494 		SetHeight(17);
495 }
496 
497 
498 void
499 TListItem::DrawItem(BView *owner, BRect r, bool /* complete */)
500 {
501 	if (IsSelected()) {
502 		owner->SetHighColor(180, 180, 180);
503 		owner->SetLowColor(180, 180, 180);
504 	} else {
505 		owner->SetHighColor(255, 255, 255);
506 		owner->SetLowColor(255, 255, 255);
507 	}
508 	owner->FillRect(r);
509 	owner->SetHighColor(0, 0, 0);
510 
511 	BFont font = *be_plain_font;
512 	font.SetSize(FONT_SIZE);
513 	owner->SetFont(&font);
514 	owner->MovePenTo(r.left + 24, r.bottom - 4);
515 
516 	if (fComponent) {
517 		// if it's already a mail component, we don't have an icon to
518 		// draw, and the entry_ref is invalid
519 		BMailAttachment *attachment = dynamic_cast<BMailAttachment *>(fComponent);
520 
521 		char name[B_FILE_NAME_LENGTH * 2];
522 		if ((attachment == NULL) || (attachment->FileName(name) < B_OK))
523 			strcpy(name, "unnamed");
524 
525 		BMimeType type;
526 		if (fComponent->MIMEType(&type) == B_OK)
527 			sprintf(name + strlen(name), ", Type: %s", type.Type());
528 
529 		owner->DrawString(name);
530 
531 		BRect iconRect(0, 0, B_MINI_ICON - 1, B_MINI_ICON - 1);
532 
533 		BBitmap bitmap(iconRect, B_COLOR_8_BIT);
534 		if (GetTrackerIcon(type, &bitmap, B_MINI_ICON) == B_NO_ERROR) {
535 			BRect rect(r.left + 4, r.top + 1, r.left + 4 + 15, r.top + 1 + 15);
536 			owner->SetDrawingMode(B_OP_OVER);
537 			owner->DrawBitmap(&bitmap, iconRect, rect);
538 			owner->SetDrawingMode(B_OP_COPY);
539 		} else {
540 			// ToDo: find some nicer image for this :-)
541 			owner->SetHighColor(150, 150, 150);
542 			owner->FillEllipse(BRect(r.left + 8, r.top + 4, r.left + 16, r.top + 13));
543 		}
544 		return;
545 	}
546 
547 	BFile file(&fRef, O_RDONLY);
548 	BEntry entry(&fRef);
549 	BPath path;
550 	if (entry.GetPath(&path) == B_OK && file.InitCheck() == B_OK) {
551 		owner->DrawString(path.Path());
552 
553 		BNodeInfo info(&file);
554 		BRect sr(0, 0, B_MINI_ICON - 1, B_MINI_ICON - 1);
555 
556 		BBitmap bitmap(sr, B_COLOR_8_BIT);
557 		if (info.GetTrackerIcon(&bitmap, B_MINI_ICON) == B_NO_ERROR) {
558 			BRect dr(r.left + 4, r.top + 1, r.left + 4 + 15, r.top + 1 + 15);
559 			owner->SetDrawingMode(B_OP_OVER);
560 			owner->DrawBitmap(&bitmap, sr, dr);
561 			owner->SetDrawingMode(B_OP_COPY);
562 		}
563 	} else
564 		owner->DrawString("<missing enclosure>");
565 }
566 
567