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