1 /* 2 * Copyright 2007, Haiku. All rights reserved. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Stephan Aßmus <superstippi@gmx.de> 7 */ 8 #include "PlaylistListView.h" 9 10 #include <new> 11 #include <stdio.h> 12 13 #include <Autolock.h> 14 #include <Message.h> 15 #include <ScrollBar.h> 16 #include <ScrollView.h> 17 #include <Window.h> 18 19 #include "CommandStack.h" 20 #include "Controller.h" 21 #include "ControllerObserver.h" 22 #include "CopyPLItemsCommand.h" 23 #include "ImportPLItemsCommand.h" 24 #include "ListViews.h" 25 #include "MovePLItemsCommand.h" 26 #include "PlaybackState.h" 27 #include "Playlist.h" 28 #include "PlaylistObserver.h" 29 #include "RemovePLItemsCommand.h" 30 31 using std::nothrow; 32 33 34 enum { 35 DISPLAY_NAME = 0, 36 DISPLAY_PATH = 1 37 }; 38 39 40 static float 41 playback_mark_size(const font_height& fh) 42 { 43 return ceilf(fh.ascent * 0.7); 44 } 45 46 47 static float 48 text_offset(const font_height& fh) 49 { 50 return ceilf(fh.ascent * 0.8); 51 } 52 53 54 class PlaylistItem : public SimpleItem { 55 public: 56 PlaylistItem(const entry_ref& ref); 57 virtual ~PlaylistItem(); 58 59 void Draw(BView* owner, BRect frame, 60 const font_height& fh, 61 bool tintedLine, uint32 mode, 62 bool active, 63 uint32 playbackState); 64 65 private: 66 entry_ref fRef; 67 68 }; 69 70 71 PlaylistItem::PlaylistItem(const entry_ref& ref) 72 : SimpleItem(ref.name), 73 fRef(ref) 74 { 75 } 76 77 78 PlaylistItem::~PlaylistItem() 79 { 80 } 81 82 83 void 84 PlaylistItem::Draw(BView* owner, BRect frame, const font_height& fh, 85 bool tintedLine, uint32 mode, bool active, uint32 playbackState) 86 { 87 rgb_color color = (rgb_color){ 255, 255, 255, 255 }; 88 if (tintedLine) 89 color = tint_color(color, 1.04); 90 // background 91 if (IsSelected()) 92 color = tint_color(color, B_DARKEN_2_TINT); 93 owner->SetLowColor(color); 94 owner->FillRect(frame, B_SOLID_LOW); 95 // label 96 rgb_color black = (rgb_color){ 0, 0, 0, 255 }; 97 owner->SetHighColor(black); 98 const char* text = Text(); 99 switch (mode) { 100 case DISPLAY_NAME: 101 // TODO 102 break; 103 case DISPLAY_PATH: 104 // TODO 105 break; 106 default: 107 break; 108 } 109 110 float playbackMarkSize = playback_mark_size(fh); 111 float textOffset = text_offset(fh); 112 113 BString truncatedString(text); 114 owner->TruncateString(&truncatedString, B_TRUNCATE_MIDDLE, 115 frame.Width() - playbackMarkSize - textOffset); 116 owner->DrawString(truncatedString.String(), 117 BPoint(frame.left + playbackMarkSize + textOffset, 118 floorf(frame.top + frame.bottom + fh.ascent) / 2 - 1)); 119 120 // playmark 121 if (active) { 122 rgb_color green = (rgb_color){ 0, 255, 0, 255 }; 123 if (playbackState != PLAYBACK_STATE_PLAYING) 124 green = tint_color(color, B_DARKEN_1_TINT); 125 126 BRect r(0, 0, playbackMarkSize, playbackMarkSize); 127 r.OffsetTo(frame.left + 4, 128 ceilf((frame.top + frame.bottom) / 2) - 5); 129 130 BPoint arrow[3]; 131 arrow[0] = r.LeftTop(); 132 arrow[1] = r.LeftBottom(); 133 arrow[2].x = r.right; 134 arrow[2].y = (r.top + r.bottom) / 2; 135 #ifdef __HAIKU__ 136 owner->SetPenSize(2); 137 owner->StrokePolygon(arrow, 3); 138 owner->SetPenSize(1); 139 #else 140 rgb_color lightGreen = tint_color(green, B_LIGHTEN_2_TINT); 141 rgb_color darkGreen = tint_color(green, B_DARKEN_2_TINT); 142 owner->BeginLineArray( 6 ); 143 // black outline 144 owner->AddLine(arrow[0], arrow[1], black); 145 owner->AddLine(BPoint(arrow[1].x + 1.0, arrow[1].y - 1.0), 146 arrow[2], black); 147 owner->AddLine(arrow[0], arrow[2], black); 148 // inset arrow 149 arrow[0].x += 1.0; 150 arrow[0].y += 2.0; 151 arrow[1].x += 1.0; 152 arrow[1].y -= 2.0; 153 arrow[2].x -= 2.0; 154 // highlights and shadow 155 owner->AddLine(arrow[1], arrow[2], darkGreen); 156 owner->AddLine(arrow[0], arrow[2], lightGreen); 157 owner->AddLine(arrow[0], arrow[1], lightGreen); 158 owner->EndLineArray(); 159 // fill green 160 arrow[0].x += 1.0; 161 arrow[0].y += 1.0; 162 arrow[1].x += 1.0; 163 arrow[1].y -= 1.0; 164 arrow[2].x -= 2.0; 165 #endif // __HAIKU__ 166 owner->SetLowColor(owner->HighColor()); 167 owner->SetHighColor(green); 168 owner->FillPolygon(arrow, 3); 169 } 170 } 171 172 173 // #pragma mark - 174 175 176 PlaylistListView::PlaylistListView(BRect frame, Playlist* playlist, 177 Controller* controller, CommandStack* stack) 178 : SimpleListView(frame, "playlist listview", NULL) 179 180 , fPlaylist(playlist) 181 , fPlaylistObserver(new PlaylistObserver(this)) 182 183 , fController(controller) 184 , fControllerObserver(new ControllerObserver(this, 185 OBSERVE_PLAYBACK_STATE_CHANGES)) 186 187 , fCommandStack(stack) 188 189 , fCurrentPlaylistIndex(-1) 190 , fPlaybackState(PLAYBACK_STATE_STOPPED) 191 192 , fLastClickedItem(NULL) 193 { 194 fPlaylist->AddListener(fPlaylistObserver); 195 fController->AddListener(fControllerObserver); 196 197 #ifdef __HAIKU__ 198 SetFlags(Flags() | B_SUBPIXEL_PRECISE); 199 #endif 200 } 201 202 203 PlaylistListView::~PlaylistListView() 204 { 205 fPlaylist->RemoveListener(fPlaylistObserver); 206 delete fPlaylistObserver; 207 fController->RemoveListener(fControllerObserver); 208 delete fControllerObserver; 209 } 210 211 212 void 213 PlaylistListView::AttachedToWindow() 214 { 215 _FullSync(); 216 SimpleListView::AttachedToWindow(); 217 218 GetFontHeight(&fFontHeight); 219 MakeFocus(true); 220 } 221 222 223 void 224 PlaylistListView::MessageReceived(BMessage* message) 225 { 226 switch (message->what) { 227 // PlaylistObserver messages 228 case MSG_PLAYLIST_REF_ADDED: { 229 entry_ref ref; 230 int32 index; 231 if (message->FindRef("refs", &ref) == B_OK 232 && message->FindInt32("index", &index) == B_OK) 233 _AddItem(ref, index); 234 break; 235 } 236 case MSG_PLAYLIST_REF_REMOVED: { 237 int32 index; 238 if (message->FindInt32("index", &index) == B_OK) 239 _RemoveItem(index); 240 break; 241 } 242 case MSG_PLAYLIST_REFS_SORTED: 243 _FullSync(); 244 break; 245 case MSG_PLAYLIST_CURRENT_REF_CHANGED: { 246 int32 index; 247 if (message->FindInt32("index", &index) == B_OK) 248 _SetCurrentPlaylistIndex(index); 249 break; 250 } 251 252 // ControllerObserver messages 253 case MSG_CONTROLLER_PLAYBACK_STATE_CHANGED: { 254 uint32 state; 255 if (message->FindInt32("state", (int32*)&state) == B_OK) 256 _SetPlaybackState(state); 257 break; 258 } 259 260 case B_SIMPLE_DATA: 261 if (message->HasRef("refs")) 262 RefsReceived(message, fDropIndex); 263 else if (message->HasPointer("list")) 264 SimpleListView::MessageReceived(message); 265 break; 266 case B_REFS_RECEIVED: 267 RefsReceived(message, fDropIndex); 268 break; 269 270 default: 271 SimpleListView::MessageReceived(message); 272 break; 273 } 274 } 275 276 277 void 278 PlaylistListView::MouseDown(BPoint where) 279 { 280 if (!IsFocus()) 281 MakeFocus(true); 282 283 int32 clicks; 284 if (Window()->CurrentMessage()->FindInt32("clicks", &clicks) < B_OK) 285 clicks = 1; 286 287 bool handled = false; 288 289 float playbackMarkSize = playback_mark_size(fFontHeight); 290 float textOffset = text_offset(fFontHeight); 291 292 for (int32 i = 0; 293 PlaylistItem* item = dynamic_cast<PlaylistItem*>(ItemAt(i)); i++) { 294 BRect r = ItemFrame(i); 295 if (r.Contains(where)) { 296 if (clicks == 2) { 297 // only do something if user clicked the same item twice 298 if (fLastClickedItem == item) { 299 BAutolock _(fPlaylist); 300 fPlaylist->SetCurrentRefIndex(i); 301 handled = true; 302 } 303 } else { 304 // remember last clicked item 305 fLastClickedItem = item; 306 if (i == fCurrentPlaylistIndex) { 307 r.right = r.left + playbackMarkSize + textOffset; 308 if (r.Contains (where)) { 309 fController->TogglePlaying(); 310 handled = true; 311 } 312 } 313 } 314 break; 315 } 316 } 317 318 if (!handled) 319 SimpleListView::MouseDown(where); 320 } 321 322 323 void 324 PlaylistListView::KeyDown(const char* bytes, int32 numBytes) 325 { 326 if (numBytes < 1) 327 return; 328 329 if ((bytes[0] == B_BACKSPACE) || (bytes[0] == B_DELETE)) 330 RemoveSelected(); 331 332 DragSortableListView::KeyDown(bytes, numBytes); 333 } 334 335 336 void 337 PlaylistListView::MoveItems(BList& indices, int32 toIndex) 338 { 339 fCommandStack->Perform(new (nothrow) MovePLItemsCommand(fPlaylist, 340 (int32*)indices.Items(), indices.CountItems(), toIndex)); 341 } 342 343 344 void 345 PlaylistListView::CopyItems(BList& indices, int32 toIndex) 346 { 347 fCommandStack->Perform(new (nothrow) CopyPLItemsCommand(fPlaylist, 348 (int32*)indices.Items(), indices.CountItems(), toIndex)); 349 } 350 351 352 void 353 PlaylistListView::RemoveItemList(BList& indices) 354 { 355 fCommandStack->Perform(new (nothrow) RemovePLItemsCommand(fPlaylist, 356 (int32*)indices.Items(), indices.CountItems())); 357 } 358 359 360 void 361 PlaylistListView::DrawListItem(BView* owner, int32 index, BRect frame) const 362 { 363 if (PlaylistItem* item = dynamic_cast<PlaylistItem*>(ItemAt(index))) { 364 item->Draw(owner, frame, fFontHeight, index % 2, 365 DISPLAY_NAME, index == fCurrentPlaylistIndex, fPlaybackState); 366 } 367 } 368 369 370 void 371 PlaylistListView::RefsReceived(BMessage* message, int32 appendIndex) 372 { 373 fCommandStack->Perform(new (nothrow) ImportPLItemsCommand(fPlaylist, 374 message, appendIndex)); 375 } 376 377 378 // #pragma mark - 379 380 381 void 382 PlaylistListView::_FullSync() 383 { 384 if (!fPlaylist->Lock()) 385 return; 386 387 // TODO: detach scrollbar, and this will be quick... 388 // BScrollBar* scrollBar = ScrollBar(B_VERTICAL); 389 // SetScrollBar(); 390 // 391 MakeEmpty(); 392 393 int32 count = fPlaylist->CountItems(); 394 for (int32 i = 0; i < count; i++) { 395 entry_ref ref; 396 if (fPlaylist->GetRefAt(i, &ref) == B_OK) 397 _AddItem(ref, i); 398 } 399 400 _SetCurrentPlaylistIndex(fPlaylist->CurrentRefIndex()); 401 _SetPlaybackState(fController->PlaybackState()); 402 403 fPlaylist->Unlock(); 404 } 405 406 407 void 408 PlaylistListView::_AddItem(const entry_ref& ref, int32 index) 409 { 410 PlaylistItem* item = new (nothrow) PlaylistItem(ref); 411 if (item) 412 AddItem(item, index); 413 } 414 415 416 void 417 PlaylistListView::_RemoveItem(int32 index) 418 { 419 delete RemoveItem(index); 420 } 421 422 423 void 424 PlaylistListView::_SetCurrentPlaylistIndex(int32 index) 425 { 426 if (fCurrentPlaylistIndex == index) 427 return; 428 429 InvalidateItem(fCurrentPlaylistIndex); 430 fCurrentPlaylistIndex = index; 431 InvalidateItem(fCurrentPlaylistIndex); 432 } 433 434 435 void 436 PlaylistListView::_SetPlaybackState(uint32 state) 437 { 438 if (fPlaybackState == state) 439 return; 440 441 fPlaybackState = state; 442 InvalidateItem(fCurrentPlaylistIndex); 443 } 444 445 446