xref: /haiku/src/apps/mail/Enclosures.cpp (revision e1c4049fed1047bdb957b0529e1921e97ef94770)
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 <ControlLook.h>
49 #include <Debug.h>
50 #include <LayoutBuilder.h>
51 #include <Locale.h>
52 #include <MenuItem.h>
53 #include <NodeMonitor.h>
54 #include <PopUpMenu.h>
55 #include <StringForSize.h>
56 #include <StringView.h>
57 
58 #include <MailAttachment.h>
59 #include <MailMessage.h>
60 
61 #include <stdio.h>
62 #include <stdlib.h>
63 #include <string.h>
64 
65 #include "MailApp.h"
66 #include "MailSupport.h"
67 #include "MailWindow.h"
68 #include "Messages.h"
69 
70 
71 #define B_TRANSLATION_CONTEXT "Mail"
72 
73 
74 static const float kPlainFontSizeScale = 0.9;
75 
76 
77 static void
78 recursive_attachment_search(TEnclosuresView* us, BMailContainer* mail,
79 	BMailComponent *body)
80 {
81 	if (mail == NULL)
82 		return;
83 
84 	for (int32 i = 0; i < mail->CountComponents(); i++) {
85 		BMailComponent *component = mail->GetComponent(i);
86 		if (component == body)
87 			continue;
88 
89 		if (component->ComponentType() == B_MAIL_MULTIPART_CONTAINER) {
90 			recursive_attachment_search(us,
91 				dynamic_cast<BMIMEMultipartMailContainer *>(component), body);
92 		}
93 
94 		us->fList->AddItem(new TListItem(component));
95 	}
96 }
97 
98 
99 //	#pragma mark -
100 
101 
102 TEnclosuresView::TEnclosuresView()
103 	:
104 	BView("m_enclosures", B_WILL_DRAW),
105 	fFocus(false)
106 {
107 	SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
108 
109 	BFont font(be_plain_font);
110 	font.SetSize(font.Size() * kPlainFontSizeScale);
111 	SetFont(&font);
112 
113 	fList = new TListView(this);
114 	fList->SetInvocationMessage(new BMessage(LIST_INVOKED));
115 
116 	BStringView* label = new BStringView("label", B_TRANSLATE("Attachments:"));
117 	BScrollView* scroll = new BScrollView("", fList, 0, false, true);
118 
119 	BLayoutBuilder::Group<>(this, B_HORIZONTAL)
120 		.SetInsets(B_USE_SMALL_INSETS, 0,
121 			scroll->ScrollBar(B_VERTICAL)->PreferredSize().width - 2, -2)
122 		.Add(label)
123 		.Add(scroll)
124 	.End();
125 }
126 
127 
128 TEnclosuresView::~TEnclosuresView()
129 {
130 	for (int32 index = fList->CountItems();index-- > 0;) {
131 		TListItem *item = static_cast<TListItem *>(fList->ItemAt(index));
132 		fList->RemoveItem(index);
133 
134 		if (item->Component() == NULL)
135 			watch_node(item->NodeRef(), B_STOP_WATCHING, this);
136 		delete item;
137 	}
138 }
139 
140 
141 void
142 TEnclosuresView::MessageReceived(BMessage *msg)
143 {
144 	switch (msg->what)
145 	{
146 		case LIST_INVOKED:
147 		{
148 			BListView *list;
149 			msg->FindPointer("source", (void **)&list);
150 			if (list) {
151 				TListItem *item = (TListItem *) (list->ItemAt(msg->FindInt32("index")));
152 				if (item) {
153 					BMessenger tracker("application/x-vnd.Be-TRAK");
154 					if (tracker.IsValid()) {
155 						BMessage message(B_REFS_RECEIVED);
156 						message.AddRef("refs", item->Ref());
157 
158 						tracker.SendMessage(&message);
159 					}
160 				}
161 			}
162 			break;
163 		}
164 
165 		case M_REMOVE:
166 		{
167 			int32 index;
168 			while ((index = fList->CurrentSelection()) >= 0) {
169 				TListItem *item = (TListItem *) fList->ItemAt(index);
170 				fList->RemoveItem(index);
171 
172 				if (item->Component()) {
173 					TMailWindow *window = dynamic_cast<TMailWindow *>(Window());
174 					if (window && window->Mail())
175 						window->Mail()->RemoveComponent(item->Component());
176 
177 					BAlert* alert = new BAlert("", B_TRANSLATE(
178 						"Removing attachments from a forwarded mail is not yet "
179 						"implemented!\nIt will not yet work correctly."),
180 						B_TRANSLATE("OK"));
181 					alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
182 					alert->Go();
183 				} else
184 					watch_node(item->NodeRef(), B_STOP_WATCHING, this);
185 				delete item;
186 			}
187 			break;
188 		}
189 
190 		case M_SELECT:
191 			fList->Select(0, fList->CountItems() - 1, true);
192 			break;
193 
194 		case B_SIMPLE_DATA:
195 		case B_REFS_RECEIVED:
196 		case REFS_RECEIVED:
197 			if (msg->HasRef("refs")) {
198 				bool badType = false;
199 
200 				int32 index = 0;
201 				entry_ref ref;
202 				while (msg->FindRef("refs", index++, &ref) == B_NO_ERROR) {
203 					BEntry entry(&ref, true);
204 					entry.GetRef(&ref);
205 					BFile file(&ref, O_RDONLY);
206 					if (file.InitCheck() == B_OK && file.IsFile()) {
207 						TListItem *item;
208 						bool exists = false;
209 						for (int32 loop = 0; loop < fList->CountItems(); loop++) {
210 							item = (TListItem *) fList->ItemAt(loop);
211 							if (ref == *(item->Ref())) {
212 								fList->Select(loop);
213 								fList->ScrollToSelection();
214 								exists = true;
215 								continue;
216 							}
217 						}
218 						if (exists == false) {
219 							fList->AddItem(item = new TListItem(&ref));
220 							fList->Select(fList->CountItems() - 1);
221 							fList->ScrollToSelection();
222 
223 							watch_node(item->NodeRef(), B_WATCH_NAME, this);
224 						}
225 					} else
226 						badType = true;
227 				}
228 				if (badType) {
229 					beep();
230 					BAlert* alert = new BAlert("",
231 						B_TRANSLATE("Only files can be added as attachments."),
232 						B_TRANSLATE("OK"));
233 					alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
234 					alert->Go();
235 				}
236 			}
237 			break;
238 
239 		case B_NODE_MONITOR:
240 		{
241 			int32 opcode;
242 			if (msg->FindInt32("opcode", &opcode) == B_NO_ERROR) {
243 				dev_t device;
244 				if (msg->FindInt32("device", &device) < B_OK)
245 					break;
246 				ino_t inode;
247 				if (msg->FindInt64("node", &inode) < B_OK)
248 					break;
249 
250 				for (int32 index = fList->CountItems();index-- > 0;) {
251 					TListItem *item = static_cast<TListItem *>(fList->ItemAt(index));
252 
253 					if (device == item->NodeRef()->device
254 						&& inode == item->NodeRef()->node)
255 					{
256 						if (opcode == B_ENTRY_REMOVED) {
257 							// don't hide the <missing attachment> item
258 
259 							//fList->RemoveItem(index);
260 							//
261 							//watch_node(item->NodeRef(), B_STOP_WATCHING, this);
262 							//delete item;
263 						} else if (opcode == B_ENTRY_MOVED) {
264 							item->Ref()->device = device;
265 							msg->FindInt64("to directory", &item->Ref()->directory);
266 
267 							const char *name;
268 							msg->FindString("name", &name);
269 							item->Ref()->set_name(name);
270 						}
271 
272 						fList->InvalidateItem(index);
273 						break;
274 					}
275 				}
276 			}
277 			break;
278 		}
279 
280 		default:
281 			BView::MessageReceived(msg);
282 			break;
283 	}
284 }
285 
286 
287 void
288 TEnclosuresView::Focus(bool focus)
289 {
290 	if (fFocus != focus) {
291 		fFocus = focus;
292 		Invalidate();
293 	}
294 }
295 
296 
297 void
298 TEnclosuresView::AddEnclosuresFromMail(BEmailMessage *mail)
299 {
300 	for (int32 i = 0; i < mail->CountComponents(); i++) {
301 		BMailComponent *component = mail->GetComponent(i);
302 		if (component == mail->Body())
303 			continue;
304 
305 		if (component->ComponentType() == B_MAIL_MULTIPART_CONTAINER) {
306 			recursive_attachment_search(this,
307 				dynamic_cast<BMIMEMultipartMailContainer *>(component),
308 				mail->Body());
309 		}
310 
311 		fList->AddItem(new TListItem(component));
312 	}
313 }
314 
315 
316 //	#pragma mark -
317 
318 
319 TListView::TListView(TEnclosuresView *view)
320 	:
321 	BListView("", B_MULTIPLE_SELECTION_LIST),
322 	fParent(view)
323 {
324 }
325 
326 
327 void
328 TListView::AttachedToWindow()
329 {
330 	BListView::AttachedToWindow();
331 
332 	BFont font(be_plain_font);
333 	font.SetSize(font.Size() * kPlainFontSizeScale);
334 	SetFont(&font);
335 }
336 
337 
338 BSize
339 TListView::MinSize()
340 {
341 	BSize size = BListView::MinSize();
342 	size.height = be_control_look->DefaultLabelSpacing() * 11.0f;
343 	return size;
344 }
345 
346 
347 void
348 TListView::MakeFocus(bool focus)
349 {
350 	BListView::MakeFocus(focus);
351 	fParent->Focus(focus);
352 }
353 
354 
355 void
356 TListView::MouseDown(BPoint point)
357 {
358 	int32 buttons;
359 	Looper()->CurrentMessage()->FindInt32("buttons", &buttons);
360 
361 	BListView::MouseDown(point);
362 
363 	if ((buttons & B_SECONDARY_MOUSE_BUTTON) != 0 && IndexOf(point) >= 0) {
364 		BPopUpMenu menu("enclosure", false, false);
365 		menu.SetFont(be_plain_font);
366 		menu.AddItem(new BMenuItem(B_TRANSLATE("Open attachment"),
367 			new BMessage(LIST_INVOKED)));
368 		menu.AddItem(new BMenuItem(B_TRANSLATE("Remove attachment"),
369 			new BMessage(M_REMOVE)));
370 
371 		BPoint menuStart = ConvertToScreen(point);
372 
373 		BMenuItem* item = menu.Go(menuStart);
374 		if (item != NULL) {
375 			if (item->Command() == LIST_INVOKED) {
376 				BMessage msg(LIST_INVOKED);
377 				msg.AddPointer("source",this);
378 				msg.AddInt32("index",IndexOf(point));
379 				Window()->PostMessage(&msg,fParent);
380 			} else {
381 				Select(IndexOf(point));
382 				Window()->PostMessage(item->Command(),fParent);
383 			}
384 		}
385 	}
386 }
387 
388 
389 void
390 TListView::KeyDown(const char *bytes, int32 numBytes)
391 {
392 	BListView::KeyDown(bytes,numBytes);
393 
394 	if (numBytes == 1 && *bytes == B_DELETE)
395 		Window()->PostMessage(M_REMOVE, fParent);
396 }
397 
398 
399 //	#pragma mark -
400 
401 
402 TListItem::TListItem(entry_ref *ref)
403 {
404 	fComponent = NULL;
405 	fRef = *ref;
406 
407 	BEntry entry(ref);
408 	entry.GetNodeRef(&fNodeRef);
409 }
410 
411 
412 TListItem::TListItem(BMailComponent *component)
413 	:
414 	fComponent(component)
415 {
416 }
417 
418 
419 void
420 TListItem::Update(BView* owner, const BFont* font)
421 {
422 	BListItem::Update(owner, font);
423 
424 	const float minimalHeight =
425 		be_control_look->ComposeIconSize(B_MINI_ICON).Height() +
426 		(be_control_look->DefaultLabelSpacing() / 3.0f);
427 	if (Height() < minimalHeight)
428 		SetHeight(minimalHeight);
429 }
430 
431 
432 void
433 TListItem::DrawItem(BView *owner, BRect frame, bool /* complete */)
434 {
435 	rgb_color kHighlight = ui_color(B_LIST_SELECTED_BACKGROUND_COLOR);
436 	rgb_color kHighlightText = ui_color(B_LIST_SELECTED_ITEM_TEXT_COLOR);
437 	rgb_color kText = ui_color(B_LIST_ITEM_TEXT_COLOR);
438 
439 	BRect r(frame);
440 
441 	if (IsSelected()) {
442 		owner->SetHighColor(kHighlight);
443 		owner->SetLowColor(kHighlight);
444 		owner->FillRect(r);
445 	}
446 
447 	const BRect iconRect(BPoint(0, 0), be_control_look->ComposeIconSize(B_MINI_ICON));
448 	BBitmap iconBitmap(iconRect, B_RGBA32);
449 	status_t iconStatus = B_NO_INIT;
450 	BString label;
451 
452 	if (fComponent) {
453 		// if it's already a mail component, we don't have an icon to
454 		// draw, and the entry_ref is invalid
455 		BMailAttachment *attachment = dynamic_cast<BMailAttachment *>(fComponent);
456 
457 		char name[B_FILE_NAME_LENGTH * 2];
458 		if ((attachment == NULL) || (attachment->FileName(name) < B_OK))
459 			strcpy(name, "unnamed");
460 
461 		BMimeType type;
462 		if (fComponent->MIMEType(&type) == B_OK)
463 			label.SetToFormat("%s, Type: %s", name, type.Type());
464 		else
465 			label = name;
466 
467 		iconStatus = type.GetIcon(&iconBitmap,
468 			(icon_size)(iconRect.IntegerWidth() + 1));
469 	} else {
470 		BFile file(&fRef, O_RDONLY);
471 		BEntry entry(&fRef);
472 		BPath path;
473 		if (entry.GetPath(&path) == B_OK && file.InitCheck() == B_OK) {
474 			off_t bytes;
475 			file.GetSize(&bytes);
476 			char size[B_PATH_NAME_LENGTH];
477 			string_for_size(bytes, size, sizeof(size));
478 			label << path.Path() << " (" << size << ")";
479 			BNodeInfo info(&file);
480 			iconStatus = info.GetTrackerIcon(&iconBitmap,
481 				(icon_size)(iconRect.IntegerWidth() + 1));
482 		} else
483 			label = "<missing attachment>";
484 	}
485 
486 	BRect iconFrame(frame);
487 	iconFrame.left += be_control_look->DefaultLabelSpacing() / 2;
488 	iconFrame.Set(iconFrame.left, iconFrame.top + 1,
489 		iconFrame.left + iconRect.Width(),
490 		iconFrame.top + iconRect.Height() + 1);
491 
492 	if (iconStatus == B_OK) {
493 		owner->SetDrawingMode(B_OP_ALPHA);
494 		owner->SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY);
495 		owner->DrawBitmap(&iconBitmap, iconFrame);
496 		owner->SetDrawingMode(B_OP_COPY);
497 	} else {
498 		// ToDo: find some nicer image for this :-)
499 		owner->SetHighColor(150, 150, 150);
500 		owner->FillEllipse(iconFrame);
501 	}
502 
503 	BFont font;
504 	owner->GetFont(&font);
505 	font_height finfo;
506 	font.GetHeight(&finfo);
507 	owner->MovePenTo(frame.left + (iconFrame.Width() * 1.5f),
508 		frame.top + ((frame.Height()
509 			- (finfo.ascent + finfo.descent + finfo.leading)) / 2)
510 		+ finfo.ascent);
511 
512 	owner->SetHighColor(IsSelected() ? kHighlightText : kText);
513 	owner->DrawString(label.String());
514 }
515