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