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