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