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