1 /* 2 * Copyright 2011, Haiku, Inc. All rights reserved. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Clemens Zeidler <haiku@clemens-zeidler.de> 7 */ 8 9 #include "MusicCollectionWindow.h" 10 11 #include <Application.h> 12 #include <ControlLook.h> 13 #include <ScrollView.h> 14 #include <VolumeRoster.h> 15 16 #include <NaturalCompare.h> 17 18 #include "ALMLayout.h" 19 20 21 static int 22 StringItemComp(const BListItem* first, const BListItem* second) 23 { 24 BStringItem* firstItem = (BStringItem*)first; 25 BStringItem* secondItem = (BStringItem*)second; 26 return BPrivate::NaturalCompare(firstItem->Text(), secondItem->Text()); 27 } 28 29 30 template <class ListItem = FileListItem> 31 class ListViewListener : public EntryViewInterface { 32 public: 33 ListViewListener(BOutlineListView* list, BStringView* countView) 34 : 35 fListView(list), 36 fCountView(countView), 37 fItemCount(0) 38 { 39 40 } 41 42 43 void 44 SetQueryString(const char* string) 45 { 46 fQueryString = string; 47 } 48 49 50 void 51 EntryCreated(WatchedFile* file) 52 { 53 //ListItem* item1 = new ListItem(file->entry.name, file); 54 //fListView->AddItem(item1); 55 56 fItemCount++; 57 BString count("Count: "); 58 count << fItemCount; 59 fCountView->SetText(count); 60 61 const ssize_t bufferSize = 256; 62 char buffer[bufferSize]; 63 BNode node(&file->entry); 64 65 ssize_t readBytes; 66 readBytes = node.ReadAttr("Audio:Artist", B_STRING_TYPE, 0, buffer, 67 bufferSize); 68 if (readBytes < 0) 69 readBytes = 0; 70 if (readBytes >= bufferSize) 71 readBytes = bufferSize - 1; 72 buffer[readBytes] = '\0'; 73 74 BString artist = (strcmp(buffer, "") == 0) ? "Unknown" : buffer; 75 ListItem* artistItem = _AddSuperItem(artist, fArtistList, NULL); 76 77 readBytes = node.ReadAttr("Audio:Album", B_STRING_TYPE, 0, buffer, 78 bufferSize); 79 if (readBytes < 0) 80 readBytes = 0; 81 buffer[readBytes] = '\0'; 82 BString album = (strcmp(buffer, "") == 0) ? "Unknown" : buffer; 83 ListItem* albumItem = _AddSuperItem(album, fAlbumList, artistItem); 84 85 readBytes = node.ReadAttr("Media:Title", B_STRING_TYPE, 0, buffer, 86 bufferSize); 87 if (readBytes < 0) 88 readBytes = 0; 89 buffer[readBytes] = '\0'; 90 BString title= (strcmp(buffer, "") == 0) ? file->entry.name 91 : buffer; 92 93 ListItem* item = new ListItem(title, file); 94 file->cookie = item; 95 fListView->AddUnder(item, albumItem); 96 fListView->SortItemsUnder(albumItem, true, StringItemComp); 97 98 if (fQueryString == "") 99 return; 100 if (title.IFindFirst(fQueryString) >= 0) { 101 fListView->Expand(artistItem); 102 fListView->Expand(albumItem); 103 } else if (album.IFindFirst(fQueryString) >= 0) { 104 fListView->Expand(artistItem); 105 } 106 }; 107 108 109 void 110 EntryRemoved(WatchedFile* file) 111 { 112 ListItem* item = (ListItem*)file->cookie; 113 ListItem* album = (ListItem*)fListView->Superitem(item); 114 fListView->RemoveItem(item); 115 if (album != NULL && fListView->CountItemsUnder(album, true) == 0) { 116 ListItem* artist = (ListItem*)fListView->Superitem(album); 117 fListView->RemoveItem(album); 118 if (artist != NULL && fListView->CountItemsUnder(artist, true) == 0) 119 fListView->RemoveItem(artist); 120 } 121 }; 122 123 void 124 EntryMoved(WatchedFile* file) 125 { 126 AttrChanged(file); 127 }; 128 129 130 void 131 AttrChanged(WatchedFile* file) 132 { 133 EntryRemoved(file); 134 EntryCreated(file); 135 } 136 137 138 void 139 EntriesCleared() 140 { 141 for (int32 i = 0; i < fListView->FullListCountItems(); i++) 142 delete fListView->FullListItemAt(i); 143 fListView->MakeEmpty(); 144 145 fArtistList.MakeEmpty(); 146 fAlbumList.MakeEmpty(); 147 148 printf("prev count %i\n", (int)fItemCount); 149 fItemCount = 0; 150 fCountView->SetText("Count: 0"); 151 } 152 153 154 private: 155 ListItem* 156 _AddSuperItem(const char* name, BObjectList<ListItem>& list, 157 ListItem* under) 158 { 159 ListItem* item = _FindStringItem(list, name, under); 160 if (item != NULL) 161 return item; 162 163 item = new ListItem(name); 164 fListView->AddUnder(item, under); 165 fListView->SortItemsUnder(under, true, StringItemComp); 166 list.AddItem(item); 167 168 fListView->Collapse(item); 169 170 return item; 171 } 172 173 ListItem* 174 _FindStringItem(BObjectList<ListItem>& list, const char* text, 175 ListItem* parent) 176 { 177 for (int32 i = 0; i < list.CountItems(); i++) { 178 ListItem* item = list.ItemAt(i); 179 ListItem* superItem = (ListItem*)fListView->Superitem(item); 180 if (parent != NULL && parent != superItem) 181 continue; 182 if (strcmp(item->Text(), text) == 0) 183 return item; 184 } 185 return NULL; 186 } 187 188 BOutlineListView* fListView; 189 BStringView* fCountView; 190 191 BObjectList<ListItem> fArtistList; 192 BObjectList<ListItem> fAlbumList; 193 194 BString fQueryString; 195 int32 fItemCount; 196 }; 197 198 199 const uint32 kMsgQueryInput = '&qin'; 200 const uint32 kMsgItemInvoked = '&iin'; 201 202 203 MusicCollectionWindow::MusicCollectionWindow(BRect frame, const char* title) 204 : 205 BWindow(frame, title, B_DOCUMENT_WINDOW, B_AVOID_FRONT) 206 { 207 BView* rootView = new BView(Bounds(), NULL, B_FOLLOW_ALL, B_WILL_DRAW); 208 AddChild(rootView); 209 rootView->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 210 211 fQueryField = new BTextControl("Search: ", "", NULL); 212 fQueryField->SetExplicitAlignment(BAlignment(B_ALIGN_HORIZONTAL_CENTER, 213 B_ALIGN_USE_FULL_HEIGHT)); 214 fQueryField->SetModificationMessage(new BMessage(kMsgQueryInput)); 215 216 fCountView = new BStringView("Count View", "Count:"); 217 218 fFileListView = new MusicFileListView("File List View"); 219 fFileListView->SetInvocationMessage(new BMessage(kMsgItemInvoked)); 220 BScrollView* scrollView = new BScrollView("list scroll", fFileListView, 0, 221 true, true, B_PLAIN_BORDER); 222 223 float spacing = be_control_look->DefaultItemSpacing() / 2; 224 BALMLayout* layout = new BALMLayout(spacing); 225 layout->SetInset(spacing); 226 rootView->SetLayout(layout); 227 228 layout->AddView(fQueryField, layout->Left(), layout->Top()); 229 layout->AddViewToRight(fCountView, layout->Right()); 230 layout->AddView(scrollView, layout->Left(), 231 layout->AreaFor(fQueryField)->Bottom(), layout->Right(), 232 layout->Bottom()); 233 234 Area* area = layout->AreaFor(scrollView); 235 area->SetLeftInset(0); 236 area->SetRightInset(0); 237 area->SetBottomInset(0); 238 239 BSize min = layout->MinSize(); 240 BSize max = layout->MaxSize(); 241 SetSizeLimits(min.Width(), max.Width(), min.Height(), max.Height()); 242 243 fEntryViewInterface = new ListViewListener<FileListItem>(fFileListView, 244 fCountView); 245 fQueryHandler = new QueryHandler(fEntryViewInterface); 246 AddHandler(fQueryHandler); 247 fQueryReader = new QueryReader(fQueryHandler); 248 fQueryHandler->SetReadThread(fQueryReader); 249 250 // start initial query 251 PostMessage(kMsgQueryInput); 252 } 253 254 255 MusicCollectionWindow::~MusicCollectionWindow() 256 { 257 delete fQueryReader; 258 delete fQueryHandler; 259 delete fEntryViewInterface; 260 } 261 262 263 bool 264 MusicCollectionWindow::QuitRequested() 265 { 266 be_app->PostMessage(B_QUIT_REQUESTED); 267 return true; 268 } 269 270 271 void 272 MusicCollectionWindow::MessageReceived(BMessage* message) 273 { 274 switch (message->what) { 275 case kMsgQueryInput: 276 _StartNewQuery(); 277 break; 278 279 case kMsgItemInvoked: 280 fFileListView->Launch(message); 281 break; 282 283 default: 284 BWindow::MessageReceived(message); 285 } 286 } 287 288 289 void 290 CaseInsensitiveString(BString &instring, BString &outstring) 291 { 292 outstring = ""; 293 int i = 0; 294 while (instring[i]) 295 { 296 if (instring[i] >= 65 && instring[i] <= 90) // capital letters 297 { 298 int ch = instring[i] + 32; 299 outstring += "["; 300 outstring += ch; 301 outstring += instring[i]; 302 outstring += "]"; 303 } else if (instring[i] >= 97 && instring[i] <= 122) 304 { 305 int ch = instring[i]-32; 306 outstring += "["; 307 outstring += instring[i]; 308 outstring += ch; 309 outstring += "]"; 310 } else 311 outstring += instring[i]; 312 i++; 313 } 314 } 315 316 317 void 318 MusicCollectionWindow::_StartNewQuery() 319 { 320 fQueryReader->Reset(); 321 fQueryHandler->Reset(); 322 323 BString orgString = fQueryField->Text(); 324 ((ListViewListener<FileListItem>*)fEntryViewInterface)->SetQueryString( 325 orgString); 326 327 BVolume volume; 328 //BVolumeRoster().GetBootVolume(&volume); 329 BVolumeRoster roster; 330 while (roster.GetNextVolume(&volume) == B_OK) { 331 if (!volume.KnowsQuery()) 332 continue; 333 BQuery* query = _CreateQuery(orgString); 334 query->SetVolume(&volume); 335 fQueryReader->AddQuery(query); 336 } 337 338 fQueryReader->Run(); 339 } 340 341 342 BQuery* 343 MusicCollectionWindow::_CreateQuery(BString& orgString) 344 { 345 BQuery* query = new BQuery; 346 347 BString queryString; 348 CaseInsensitiveString(orgString, queryString); 349 350 query->PushAttr("Media:Title"); 351 query->PushString(queryString); 352 query->PushOp(B_CONTAINS); 353 354 query->PushAttr("Audio:Album"); 355 query->PushString(queryString); 356 query->PushOp(B_CONTAINS); 357 query->PushOp(B_OR); 358 359 query->PushAttr("Audio:Artist"); 360 query->PushString(queryString); 361 query->PushOp(B_CONTAINS); 362 query->PushOp(B_OR); 363 364 if (queryString == "") { 365 query->PushAttr("BEOS:TYPE"); 366 query->PushString("audio/"); 367 query->PushOp(B_BEGINS_WITH); 368 query->PushOp(B_OR); 369 } 370 371 query->PushAttr("BEOS:TYPE"); 372 query->PushString("audio/"); 373 query->PushOp(B_BEGINS_WITH); 374 375 query->PushAttr("name"); 376 query->PushString(queryString); 377 query->PushOp(B_CONTAINS); 378 query->PushOp(B_AND); 379 query->PushOp(B_OR); 380 381 return query; 382 } 383