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