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