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