1 /* 2 * Copyright 2006, Haiku. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Stephan Aßmus <superstippi@gmx.de> 7 */ 8 9 #include "PathListView.h" 10 11 #include <stdio.h> 12 13 #include <Application.h> 14 #include <ListItem.h> 15 #include <Message.h> 16 #include <Mime.h> 17 #include <Window.h> 18 19 #include "AddPathsCommand.h" 20 #include "CommandStack.h" 21 #include "Observer.h" 22 #include "RemovePathsCommand.h" 23 #include "Shape.h" 24 #include "ShapeContainer.h" 25 #include "Selection.h" 26 #include "UnassignPathCommand.h" 27 #include "VectorPath.h" 28 29 static const float kMarkWidth = 14.0; 30 static const float kBorderOffset = 3.0; 31 static const float kTextOffset = 4.0; 32 33 class PathListItem : public SimpleItem, 34 public Observer { 35 public: 36 PathListItem(VectorPath* p, 37 PathListView* listView, 38 bool markEnabled) 39 : SimpleItem(""), 40 path(NULL), 41 fListView(listView), 42 fMarkEnabled(markEnabled), 43 fMarked(false) 44 { 45 SetPath(p); 46 } 47 48 virtual ~PathListItem() 49 { 50 SetPath(NULL); 51 } 52 53 // SimpleItem interface 54 virtual void Draw(BView* owner, BRect itemFrame, uint32 flags) 55 { 56 SimpleItem::DrawBackground(owner, itemFrame, flags); 57 58 // text 59 owner->SetHighColor(0, 0, 0, 255); 60 font_height fh; 61 owner->GetFontHeight(&fh); 62 BString truncatedString(Text()); 63 owner->TruncateString(&truncatedString, B_TRUNCATE_MIDDLE, 64 itemFrame.Width() 65 - kBorderOffset 66 - kMarkWidth 67 - kTextOffset 68 - kBorderOffset); 69 float height = itemFrame.Height(); 70 float textHeight = fh.ascent + fh.descent; 71 BPoint pos; 72 pos.x = itemFrame.left 73 + kBorderOffset + kMarkWidth + kTextOffset; 74 pos.y = itemFrame.top 75 + ceilf((height - textHeight) / 2.0 + fh.ascent); 76 owner->DrawString(truncatedString.String(), pos); 77 78 if (!fMarkEnabled) 79 return; 80 81 // mark 82 BRect markRect = itemFrame; 83 markRect.left += kBorderOffset; 84 markRect.right = markRect.left + kMarkWidth; 85 markRect.top = (markRect.top + markRect.bottom - kMarkWidth) / 2.0; 86 markRect.bottom = markRect.top + kMarkWidth; 87 owner->SetHighColor(tint_color(owner->LowColor(), B_DARKEN_1_TINT)); 88 owner->StrokeRect(markRect); 89 markRect.InsetBy(1, 1); 90 owner->SetHighColor(tint_color(owner->LowColor(), 1.04)); 91 owner->FillRect(markRect); 92 if (fMarked) { 93 markRect.InsetBy(2, 2); 94 owner->SetHighColor(tint_color(owner->LowColor(), 95 B_DARKEN_4_TINT)); 96 owner->SetPenSize(2); 97 owner->StrokeLine(markRect.LeftTop(), markRect.RightBottom()); 98 owner->StrokeLine(markRect.LeftBottom(), markRect.RightTop()); 99 owner->SetPenSize(1); 100 } 101 } 102 103 // Observer interface 104 virtual void ObjectChanged(const Observable* object) 105 { 106 UpdateText(); 107 } 108 109 // PathListItem 110 void SetPath(VectorPath* p) 111 { 112 if (p == path) 113 return; 114 115 if (path) { 116 path->RemoveObserver(this); 117 path->Release(); 118 } 119 120 path = p; 121 122 if (path) { 123 path->Acquire(); 124 path->AddObserver(this); 125 UpdateText(); 126 } 127 } 128 void UpdateText() 129 { 130 SetText(path->Name()); 131 Invalidate(); 132 } 133 134 void SetMarkEnabled(bool enabled) 135 { 136 if (fMarkEnabled == enabled) 137 return; 138 fMarkEnabled = enabled; 139 Invalidate(); 140 } 141 void SetMarked(bool marked) 142 { 143 if (fMarked == marked) 144 return; 145 fMarked = marked; 146 Invalidate(); 147 } 148 149 void Invalidate() 150 { 151 // :-/ 152 if (fListView->LockLooper()) { 153 fListView->InvalidateItem( 154 fListView->IndexOf(this)); 155 fListView->UnlockLooper(); 156 } 157 } 158 159 VectorPath* path; 160 private: 161 PathListView* fListView; 162 bool fMarkEnabled; 163 bool fMarked; 164 }; 165 166 167 class ShapePathListener : public PathContainerListener, 168 public ShapeContainerListener { 169 public: 170 ShapePathListener(PathListView* listView) 171 : fListView(listView), 172 fShape(NULL) 173 { 174 } 175 virtual ~ShapePathListener() 176 { 177 SetShape(NULL); 178 } 179 180 // PathContainerListener interface 181 virtual void PathAdded(VectorPath* path) 182 { 183 fListView->_SetPathMarked(path, true); 184 } 185 virtual void PathRemoved(VectorPath* path) 186 { 187 fListView->_SetPathMarked(path, false); 188 } 189 190 // ShapeContainerListener interface 191 virtual void ShapeAdded(Shape* shape, int32 index) {} 192 virtual void ShapeRemoved(Shape* shape) 193 { 194 fListView->SetCurrentShape(NULL); 195 } 196 197 // ShapePathListener 198 void SetShape(Shape* shape) 199 { 200 if (fShape == shape) 201 return; 202 203 if (fShape) 204 fShape->Paths()->RemoveListener(this); 205 206 fShape = shape; 207 208 if (fShape) 209 fShape->Paths()->AddListener(this); 210 } 211 212 Shape* CurrentShape() const 213 { 214 return fShape; 215 } 216 217 private: 218 PathListView* fListView; 219 Shape* fShape; 220 }; 221 222 // #pragma mark - 223 224 // constructor 225 PathListView::PathListView(BRect frame, 226 const char* name, 227 BMessage* message, BHandler* target) 228 : SimpleListView(frame, name, 229 NULL, B_SINGLE_SELECTION_LIST), 230 fMessage(message), 231 fPathContainer(NULL), 232 fShapeContainer(NULL), 233 fSelection(NULL), 234 fCommandStack(NULL), 235 236 fCurrentShape(NULL), 237 fShapePathListener(new ShapePathListener(this)) 238 { 239 SetTarget(target); 240 } 241 242 // destructor 243 PathListView::~PathListView() 244 { 245 _MakeEmpty(); 246 delete fMessage; 247 248 if (fPathContainer) 249 fPathContainer->RemoveListener(this); 250 251 if (fShapeContainer) 252 fShapeContainer->RemoveListener(fShapePathListener); 253 254 delete fShapePathListener; 255 } 256 257 // SelectionChanged 258 void 259 PathListView::SelectionChanged() 260 { 261 // NOTE: single selection list 262 263 PathListItem* item 264 = dynamic_cast<PathListItem*>(ItemAt(CurrentSelection(0))); 265 if (fMessage) { 266 BMessage message(*fMessage); 267 message.AddPointer("path", item ? (void*)item->path : NULL); 268 Invoke(&message); 269 } 270 271 // modify global Selection 272 if (!fSelection) 273 return; 274 275 if (item) 276 fSelection->Select(item->path); 277 else 278 fSelection->DeselectAll(); 279 } 280 281 // MouseDown 282 void 283 PathListView::MouseDown(BPoint where) 284 { 285 if (!fCurrentShape) { 286 SimpleListView::MouseDown(where); 287 return; 288 } 289 290 bool handled = false; 291 int32 index = IndexOf(where); 292 PathListItem* item = dynamic_cast<PathListItem*>(ItemAt(index)); 293 if (item) { 294 BRect itemFrame(ItemFrame(index)); 295 itemFrame.right = itemFrame.left 296 + kBorderOffset + kMarkWidth 297 + kTextOffset / 2.0; 298 VectorPath* path = item->path; 299 if (itemFrame.Contains(where) && fCommandStack) { 300 // add or remove the path to the shape 301 ::Command* command; 302 if (fCurrentShape->Paths()->HasPath(path)) { 303 command = new UnassignPathCommand( 304 fCurrentShape, path); 305 } else { 306 VectorPath* paths[1]; 307 paths[0] = path; 308 command = new AddPathsCommand( 309 fCurrentShape->Paths(), 310 paths, 1, false, 311 fCurrentShape->Paths()->CountPaths()); 312 } 313 fCommandStack->Perform(command); 314 handled = true; 315 } 316 } 317 318 if (!handled) 319 SimpleListView::MouseDown(where); 320 } 321 322 // MessageReceived 323 void 324 PathListView::MessageReceived(BMessage* message) 325 { 326 SimpleListView::MessageReceived(message); 327 } 328 329 // MakeDragMessage 330 void 331 PathListView::MakeDragMessage(BMessage* message) const 332 { 333 } 334 335 // AcceptDragMessage 336 bool 337 PathListView::AcceptDragMessage(const BMessage* message) const 338 { 339 return false; 340 } 341 342 // SetDropTargetRect 343 void 344 PathListView::SetDropTargetRect(const BMessage* message, BPoint where) 345 { 346 } 347 348 // MoveItems 349 void 350 PathListView::MoveItems(BList& items, int32 toIndex) 351 { 352 } 353 354 // CopyItems 355 void 356 PathListView::CopyItems(BList& items, int32 toIndex) 357 { 358 // TODO: allow to copy path 359 } 360 361 // RemoveItemList 362 void 363 PathListView::RemoveItemList(BList& items) 364 { 365 if (!fCommandStack || !fPathContainer) 366 return; 367 368 int32 count = items.CountItems(); 369 VectorPath* paths[count]; 370 for (int32 i = 0; i < count; i++) { 371 PathListItem* item = dynamic_cast<PathListItem*>( 372 (SimpleItem*)items.ItemAtFast(i)); 373 if (item) 374 paths[i] = item->path; 375 else 376 paths[i] = NULL; 377 } 378 379 RemovePathsCommand* command 380 = new (nothrow) RemovePathsCommand(fPathContainer, 381 paths, count); 382 fCommandStack->Perform(command); 383 } 384 385 // CloneItem 386 BListItem* 387 PathListView::CloneItem(int32 index) const 388 { 389 if (PathListItem* item = dynamic_cast<PathListItem*>(ItemAt(index))) { 390 return new PathListItem(item->path, 391 const_cast<PathListView*>(this), 392 fCurrentShape != NULL); 393 } 394 return NULL; 395 } 396 397 // #pragma mark - 398 399 // PathAdded 400 void 401 PathListView::PathAdded(VectorPath* path) 402 { 403 // NOTE: we are in the thread that messed with the 404 // ShapeContainer, so no need to lock the 405 // container, when this is changed to asynchronous 406 // notifications, then it would need to be read-locked! 407 if (!LockLooper()) 408 return; 409 410 // NOTE: shapes are always added at the end 411 // of the list, so the sorting is synced... 412 if (_AddPath(path)) 413 Select(CountItems() - 1); 414 415 UnlockLooper(); 416 } 417 418 // PathRemoved 419 void 420 PathListView::PathRemoved(VectorPath* path) 421 { 422 // NOTE: we are in the thread that messed with the 423 // ShapeContainer, so no need to lock the 424 // container, when this is changed to asynchronous 425 // notifications, then it would need to be read-locked! 426 if (!LockLooper()) 427 return; 428 429 // NOTE: we're only interested in VectorPath objects 430 _RemovePath(path); 431 432 UnlockLooper(); 433 } 434 435 // #pragma mark - 436 437 // SetPathContainer 438 void 439 PathListView::SetPathContainer(PathContainer* container) 440 { 441 if (fPathContainer == container) 442 return; 443 444 // detach from old container 445 if (fPathContainer) 446 fPathContainer->RemoveListener(this); 447 448 _MakeEmpty(); 449 450 fPathContainer = container; 451 452 if (!fPathContainer) 453 return; 454 455 fPathContainer->AddListener(this); 456 457 // sync 458 // if (!fPathContainer->ReadLock()) 459 // return; 460 461 int32 count = fPathContainer->CountPaths(); 462 for (int32 i = 0; i < count; i++) 463 _AddPath(fPathContainer->PathAtFast(i)); 464 465 // fPathContainer->ReadUnlock(); 466 } 467 468 // SetShapeContainer 469 void 470 PathListView::SetShapeContainer(ShapeContainer* container) 471 { 472 if (fShapeContainer == container) 473 return; 474 475 // detach from old container 476 if (fShapeContainer) 477 fShapeContainer->RemoveListener(fShapePathListener); 478 479 fShapeContainer = container; 480 481 if (fShapeContainer) 482 fShapeContainer->AddListener(fShapePathListener); 483 } 484 485 // SetSelection 486 void 487 PathListView::SetSelection(Selection* selection) 488 { 489 fSelection = selection; 490 } 491 492 // SetCommandStack 493 void 494 PathListView::SetCommandStack(CommandStack* stack) 495 { 496 fCommandStack = stack; 497 } 498 499 // SetCurrentShape 500 void 501 PathListView::SetCurrentShape(Shape* shape) 502 { 503 if (fCurrentShape == shape) 504 return; 505 506 fCurrentShape = shape; 507 fShapePathListener->SetShape(shape); 508 509 _UpdateMarks(); 510 } 511 512 // #pragma mark - 513 514 // _AddPath 515 bool 516 PathListView::_AddPath(VectorPath* path) 517 { 518 if (path) 519 return AddItem(new PathListItem(path, this, fCurrentShape != NULL)); 520 return false; 521 } 522 523 // _RemovePath 524 bool 525 PathListView::_RemovePath(VectorPath* path) 526 { 527 PathListItem* item = _ItemForPath(path); 528 if (item && RemoveItem(item)) { 529 delete item; 530 return true; 531 } 532 return false; 533 } 534 535 // _ItemForPath 536 PathListItem* 537 PathListView::_ItemForPath(VectorPath* path) const 538 { 539 for (int32 i = 0; 540 PathListItem* item = dynamic_cast<PathListItem*>(ItemAt(i)); 541 i++) { 542 if (item->path == path) 543 return item; 544 } 545 return NULL; 546 } 547 548 // #pragma mark - 549 550 // _UpdateMarks 551 void 552 PathListView::_UpdateMarks() 553 { 554 int32 count = CountItems(); 555 if (fCurrentShape) { 556 // enable display of marks and mark items whoes 557 // path is contained in fCurrentShape 558 for (int32 i = 0; i < count; i++) { 559 PathListItem* item = dynamic_cast<PathListItem*>(ItemAt(i)); 560 if (!item) 561 continue; 562 item->SetMarkEnabled(true); 563 item->SetMarked(fCurrentShape->Paths()->HasPath(item->path)); 564 } 565 } else { 566 // disable display of marks 567 for (int32 i = 0; i < count; i++) { 568 PathListItem* item = dynamic_cast<PathListItem*>(ItemAt(i)); 569 if (!item) 570 continue; 571 item->SetMarkEnabled(false); 572 } 573 } 574 575 Invalidate(); 576 } 577 578 // _SetPathMarked 579 void 580 PathListView::_SetPathMarked(VectorPath* path, bool marked) 581 { 582 if (PathListItem* item = _ItemForPath(path)) { 583 item->SetMarked(marked); 584 } 585 } 586