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