xref: /haiku/src/apps/musiccollection/MusicCollectionWindow.cpp (revision 4a55cc230cf7566cadcbb23b1928eefff8aea9a2)
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