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