Add a simple query app to search all music files on all volumes. Entries are searched and displayed on the fly while typing the search string. The query searches for artist, album and title. After some smaller improvements it could be integrated into media player.

While I was missing such an application I also used it as a playground for eventual tracker improvements. At the moment it works this way: The query is read in a background thread where a list of entry_ref is filled. The entries are exchanged thread safe with the display view using two entry_ref lists which are swapped when the view handled all entries from one list... The view is responsible to display the entry_ref's and load all attributes. In a future directory view, the view would be responsible to load all additional attributes. For example, first fetch the sort column and then asynchronously the rest (as discussed on the mailing list).
 
- I found the following query issue: when displaying the whole collection the query uses a empty string, the problem is that empty strings are not handled in live queries. For example, when adding a new Media:Artist attribute to a file the file does not show up in the query. Running a none empty query, e.g. Media:Artist contains "test" it shows up. Thats a bug right?

- Only tested it with just ~2100 music file and the on the fly performance is very good. Displaying the complete music list is quite slow, though. This seems to be not a query problem but more a BOutlineView issue. Adding new items to the list seems to be expensive...

- At the moment a new query is started each time you typing a char. A faster solution would be to start just one query in the beginning and then just filter this list. Since BOutlineView seems to be the bottleneck I kept it this way for now. Furthermore, it is a nice performance test for queries.



git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@41493 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Clemens Zeidler 2011-05-14 07:08:26 +00:00
parent 3da6df44df
commit 08c9600ab9
11 changed files with 1255 additions and 0 deletions

View File

@ -35,6 +35,7 @@ HaikuSubInclude mandelbrot ;
HaikuSubInclude mediaconverter ;
HaikuSubInclude mediaplayer ;
HaikuSubInclude midiplayer ;
HaikuSubInclude musiccollection ;
HaikuSubInclude networkstatus ;
HaikuSubInclude networktime ;
HaikuSubInclude overlayimage ;

View File

@ -0,0 +1,310 @@
/*
* Copyright 2011, Haiku, Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Clemens Zeidler <haiku@clemens-zeidler.de>
*/
#include "FileMonitor.h"
#include <Looper.h>
#include <Messenger.h>
#include <NodeMonitor.h>
FileMonitor::FileMonitor(EntryViewInterface* listener)
:
fListener(listener),
fCurrentReadList(NULL),
fCurrentReadIndex(0)
{
}
FileMonitor::~FileMonitor()
{
Reset();
}
void
FileMonitor::SetReadThread(ReadThread* readThread)
{
fReadThread = readThread;
}
void
FileMonitor::Reset()
{
fWatchedFileList.clear();
stop_watching(this);
BMessenger messenger(this);
messenger.SendMessage(kMsgCleared);
if (fCurrentReadList != NULL)
fCurrentReadIndex = fCurrentReadList->size();
}
void
FileMonitor::MessageReceived(BMessage* msg)
{
switch (msg->what) {
case kMsgAddRefs:
{
if (fCurrentReadList == NULL)
fCurrentReadList = fReadThread->ReadRefList();
uint32 terminate = fCurrentReadIndex + 50;
for (; fCurrentReadIndex < terminate; fCurrentReadIndex++) {
if (fCurrentReadIndex >= fCurrentReadList->size()) {
fCurrentReadList = NULL;
fCurrentReadIndex = 0;
fReadThread->ReadDone();
break;
}
entry_ref& entry = (*fCurrentReadList)[fCurrentReadIndex];
node_ref nodeRef;
BNode node(&entry);
if (node.GetNodeRef(&nodeRef) != B_OK)
continue;
EntryCreated(entry.name, entry.directory, entry.device,
nodeRef.node);
}
if (fCurrentReadList)
Looper()->PostMessage(kMsgAddRefs, this);
break;
}
case kMsgCleared:
fListener->EntriesCleared();
break;
default:
NodeMonitorHandler::MessageReceived(msg);
}
}
void
FileMonitor::EntryCreated(const char *name, ino_t directory, dev_t device,
ino_t node)
{
WatchedFile file;
NodeMonitorHandler::make_node_ref(device, node, &file.node);
if (fWatchedFileList.find(file.node) != fWatchedFileList.end())
return;
NodeMonitorHandler::make_entry_ref(device, directory, name, &file.entry);
fWatchedFileList[file.node] = file;
watch_node(&file.node, B_WATCH_NAME | B_WATCH_STAT | B_WATCH_ATTR, this);
fListener->EntryCreated(&fWatchedFileList[file.node]);
}
void
FileMonitor::EntryRemoved(const char *name, ino_t directory, dev_t device,
ino_t node)
{
WatchedFile* file = _FindFile(device, node);
if (file == NULL)
return;
fListener->EntryRemoved(file);
fWatchedFileList.erase(file->node);
}
void
FileMonitor::EntryMoved(const char *name, const char *fromName,
ino_t fromDirectory, ino_t toDirectory, dev_t device, ino_t node,
dev_t nodeDevice)
{
WatchedFile* file = _FindFile(device, node);
if (file == NULL)
return;
NodeMonitorHandler::make_entry_ref(device, toDirectory, name, &file->entry);
NodeMonitorHandler::make_node_ref(device, node, &file->node);
fListener->EntryMoved(file);
}
void
FileMonitor::StatChanged(ino_t node, dev_t device, int32 statFields)
{
WatchedFile* file = _FindFile(device, node);
if (file == NULL)
return;
fListener->StatChanged(file);
}
void
FileMonitor::AttrChanged(ino_t node, dev_t device)
{
WatchedFile* file = _FindFile(device, node);
if (file == NULL)
return;
fListener->AttrChanged(file);
}
WatchedFile*
FileMonitor::_FindFile(dev_t device, ino_t node)
{
node_ref nodeRef;
NodeMonitorHandler::make_node_ref(device, node, &nodeRef);
WatchedFileList::iterator it = fWatchedFileList.find(nodeRef);
if (it == fWatchedFileList.end())
return NULL;
return &it->second;
}
int32
ReadThreadFunction(void *data)
{
ReadThread* that = (ReadThread*)data;
return that->Process();
}
ReadThread::ReadThread(FileMonitor* target)
:
fTarget(target),
fReading(false),
fStopped(false),
fThreadId(-1),
fNReaded(0),
fRunning(false)
{
fWriteRefList = &fRefList1;
fReadRefList = &fRefList2;
}
status_t
ReadThread::Run()
{
if (fThreadId >= 0)
return B_ERROR;
fStopped = false;
fThreadId = spawn_thread(ReadThreadFunction, "file reader", B_LOW_PRIORITY,
this);
fRunning = true;
status_t status = resume_thread(fThreadId);
if (status != B_OK)
fRunning = false;
return status;
}
bool
ReadThread::Running()
{
return fRunning;
}
status_t
ReadThread::Wait()
{
status_t exitValue;
return wait_for_thread(fThreadId, &exitValue);
}
void
ReadThread::Stop()
{
fStopped = true;
}
bool
ReadThread::Stopped()
{
return fStopped;
}
RefList*
ReadThread::ReadRefList()
{
return fReadRefList;
}
void
ReadThread::ReadDone()
{
fReadRefList->clear();
// and release the list
fReading = false;
if (!fRunning && fWriteRefList->size() != 0) {
BMessenger messenger(fTarget);
_PublishEntrys(messenger);
}
}
int32
ReadThread::Process()
{
BMessenger messenger(fTarget);
entry_ref entry;
while (ReadNextEntry(entry)) {
if (Stopped()) {
fWriteRefList->clear();
break;
}
fWriteRefList->push_back(entry);
fNReaded++;
if (fNReaded >= 50)
_PublishEntrys(messenger);
}
fRunning = false;
_PublishEntrys(messenger);
fThreadId = -1;
return B_OK;
}
void
ReadThread::_SwapLists()
{
RefList* lastReadList = fReadRefList;
fReadRefList = fWriteRefList;
fWriteRefList = lastReadList;
}
void
ReadThread::_PublishEntrys(BMessenger& messenger)
{
if (fReading || Stopped())
return;
_SwapLists();
fReading = true;
fNReaded = 0;
messenger.SendMessage(kMsgAddRefs);
}

View File

@ -0,0 +1,144 @@
/*
* Copyright 2011, Haiku, Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Clemens Zeidler <haiku@clemens-zeidler.de>
*/
#ifndef FILE_MONITOR_H
#define FILE_MONITOR_H
#include <map>
#include <vector>
#include <Entry.h>
#include <Node.h>
#include "NodeMonitorHandler.h"
struct WatchedFile {
entry_ref entry;
node_ref node;
/*! Don't use it as the primary cookie storage. To be set in EntryCreated
in EntryViewInterface. */
void* cookie;
};
class NodeRefComp {
public:
bool
operator()(const node_ref& a, const node_ref& b)
{
return a.node < b.node;
}
};
typedef std::map<node_ref, WatchedFile, NodeRefComp> WatchedFileList;
class EntryViewInterface {
public:
virtual ~EntryViewInterface() {};
virtual void EntryCreated(WatchedFile* file) {};
virtual void EntryRemoved(WatchedFile* file) {};
virtual void EntryMoved(WatchedFile* file) {};
virtual void StatChanged(WatchedFile* file) {};
virtual void AttrChanged(WatchedFile* file) {};
virtual void EntriesCleared() {};
};
const uint32 kMsgAddRefs = '&adr';
const uint32 kMsgCleared = '&clr';
typedef std::vector<entry_ref> RefList;
class ReadThread;
class FileMonitor : public NodeMonitorHandler {
public:
FileMonitor(EntryViewInterface* listener);
~FileMonitor();
void SetReadThread(ReadThread* readThread);
void Reset();
virtual void MessageReceived(BMessage* message);
virtual void EntryCreated(const char *name, ino_t directory,
dev_t device, ino_t node);
virtual void EntryRemoved(const char *name, ino_t directory,
dev_t device, ino_t node);
virtual void EntryMoved(const char *name,
const char *fromName, ino_t fromDirectory,
ino_t toDirectory, dev_t device,
ino_t node, dev_t nodeDevice);
virtual void StatChanged(ino_t node, dev_t device,
int32 statFields);
virtual void AttrChanged(ino_t node, dev_t device);
private:
WatchedFile* _FindFile(dev_t device, ino_t node);
EntryViewInterface* fListener;
WatchedFileList fWatchedFileList;
ReadThread* fReadThread;
RefList* fCurrentReadList;
uint32 fCurrentReadIndex;
};
class ReadThread {
public:
ReadThread(FileMonitor* target);
virtual ~ReadThread() {}
status_t Run();
bool Running();
status_t Wait();
void Stop();
bool Stopped();
RefList* ReadRefList();
void ReadDone();
protected:
virtual bool ReadNextEntry(entry_ref& entry) = 0;
int32 Process();
friend int32 ReadThreadFunction(void *data);
BHandler* fTarget;
RefList fRefList1;
RefList fRefList2;
RefList* fWriteRefList;
RefList* fReadRefList;
bool fReading;
private:
void _SwapLists();
inline void _PublishEntrys(BMessenger& messenger);
bool fStopped;
thread_id fThreadId;
int16 fNReaded;
bool fRunning;
};
#endif // FILE_MONITOR_H

View File

@ -0,0 +1,29 @@
SubDir HAIKU_TOP src apps musiccollection ;
UsePrivateHeaders storage shared ;
UseLibraryHeaders lp_solve linprog alm ;
Application MusicCollection :
FileMonitor.cpp
main.cpp
MusicCollectionWindow.cpp
QueryMonitor.cpp
NodeMonitorHandler.cpp
:
be liblpsolve55.so libalm.so $(HAIKU_LOCALE_LIBS) $(TARGET_LIBSUPC++)
$(TARGET_LIBSTDC++) libshared.a
:
MusicCollection.rdef
;
DoCatalogs MusicCollection :
x-vnd.MusicCollection
:
main.cpp
;
SEARCH on [ FGristFiles NodeMonitorHandler.cpp ]
+= [ FDirName $(SUBDIR) $(DOTDOT) $(DOTDOT) kits storage ] ;

View File

@ -0,0 +1,47 @@
resource app_signature "application/x-vnd.Haiku-MusicCollection";
resource app_name_catalog_entry "x-vnd.Haiku-MusicCollection:System name:MusicCollection";
resource app_version {
major = 1,
middle = 0,
minor = 0,
variety = B_APPV_BETA,
internal = 0,
short_info = "MusicCollection",
long_info = "MusicCollection ©2011 Haiku, Inc."
};
resource vector_icon {
$"6E6369660D05000401740200060238FD543D29B3BD87203955A34A81F24A9838"
$"0075E9FFFF46D3EC0200060238E9203CA6E8BD037C394EBB4B224D4A495A0013"
$"9FB8FF048AA202000602BA5D113612D8B936AEBDA9B54AFE784B9845000FA6C2"
$"FF0B778A0200060234E5DA38ADBFBB9EB437EA864AFEE34B2C5E000499B4FF22"
$"BAD5020106033E2C11B82BB93719683D19E648342D448EA500FFEEACB3FFE789"
$"FFDABC5203C1844303FFFDCE03FFE26D02000602B504C13BBEB1BD9B53B6E83E"
$"4A1A204B5E8900FBFFC0FF46D3EC02000602B5818638F95EBD4FAAB9E7414A06"
$"ED4A930F00FBFFC0FFF8FF8903FF5A00130606B60626345131572B54375A405E"
$"4E50310606FE0F40604A604A60C45760C66D5DC553CC0B555D5B565F57CC59C6"
$"94584C5C4A5649504A5348504A0605B60126345131572B54375A405E3B0A1629"
$"52264C26472A522C52263F263328BAC52E5330542E363538335635573C3A403B"
$"404438583A59404E40553B5B0A04405E44544536403B0A0444544E504E314536"
$"0A03405E44544E500612EEEEEEBA0E2832242F2431242D2A2C2A2626282C2530"
$"27372233213B233B263F243D2341254029462844274829492B50284DB563C6BB"
$"B6974D2C4E2E4A32C4A8B973C2DEBAD84535403A32373939B897BBB60608EEDC"
$"332A2D2F2D2B2D3231313434313337353834323533333130302E30B978302C06"
$"04EE3E2C462E452C47304432432E452F412D0606FB0F3325BBD4B3BB2F293B28"
$"3A2ABCE3B6AB3D2C432A432A402ABEC6B676BEC6B67642233C273B233C270605"
$"B7032A272E22B647B6AB2E2930B5B1B87B2B322F312B2B2C31322C0608BBBB35"
$"2C382ABA5FB87138303832373139333B333E363837C12DBB653D323A313A323A"
$"303C2E06033B482E432DC4E4B8F8492C4C2A512A482A02022356B5E3C82AB273"
$"C8DF285A235B2A5A0202305E2F5FBA01CB122E5DB924CABD2D5E0606FA0B265C"
$"2C5D2F602F603360355E355F355C2D5C2D5C2E5A2A590204323F3740B82D3E2A"
$"422A3F2A45324AB822C30E384C3C473C4B3C430A0B2D422E4531463247364837"
$"4839453645344431443042120A010101000A0001001001178400040A02010200"
$"0A0A0103081FFF0A0201111001178400040A0B0111000A0C0112000A03010400"
$"0A040105000A050106000A0001071815FF01178403040A000107180015011786"
$"03040A060107000A07020809000A08040B0C0A0D0815FF0A0101100815FF0A00"
$"020E0F1815FF01178400040A09020E0F0815FF"
};

View File

@ -0,0 +1,382 @@
/*
* Copyright 2011, Haiku, Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Clemens Zeidler <haiku@clemens-zeidler.de>
*/
#include "MusicCollectionWindow.h"
#include <Application.h>
#include <ControlLook.h>
#include <ScrollView.h>
#include <VolumeRoster.h>
#include <NaturalCompare.h>
#include "ALMLayout.h"
static int
StringItemComp(const BListItem* first, const BListItem* second)
{
BStringItem* firstItem = (BStringItem*)first;
BStringItem* secondItem = (BStringItem*)second;
return BPrivate::NaturalCompare(firstItem->Text(), secondItem->Text());
}
template <class ListItem = FileListItem>
class ListViewListener : public EntryViewInterface {
public:
ListViewListener(BOutlineListView* list, BStringView* countView)
:
fListView(list),
fCountView(countView),
fItemCount(0)
{
}
void
SetQueryString(const char* string)
{
fQueryString = string;
}
void
EntryCreated(WatchedFile* file)
{
//ListItem* item1 = new ListItem(file->entry.name, file);
//fListView->AddItem(item1);
fItemCount++;
BString count("Count: ");
count << fItemCount;
fCountView->SetText(count);
const ssize_t bufferSize = 256;
char buffer[bufferSize];
BNode node(&file->entry);
ssize_t readBytes;
readBytes = node.ReadAttr("Audio:Artist", B_STRING_TYPE, 0, buffer,
bufferSize);
if (readBytes < 0)
readBytes = 0;
if (readBytes >= bufferSize)
readBytes = bufferSize - 1;
buffer[readBytes] = '\0';
BString artist = (strcmp(buffer, "") == 0) ? "Unknown" : buffer;
ListItem* artistItem = _AddSuperItem(artist, fArtistList, NULL);
readBytes = node.ReadAttr("Audio:Album", B_STRING_TYPE, 0, buffer,
bufferSize);
if (readBytes < 0)
readBytes = 0;
buffer[readBytes] = '\0';
BString album = (strcmp(buffer, "") == 0) ? "Unknown" : buffer;
ListItem* albumItem = _AddSuperItem(album, fAlbumList, artistItem);
readBytes = node.ReadAttr("Media:Title", B_STRING_TYPE, 0, buffer,
bufferSize);
if (readBytes < 0)
readBytes = 0;
buffer[readBytes] = '\0';
BString title= (strcmp(buffer, "") == 0) ? file->entry.name
: buffer;
ListItem* item = new ListItem(title, file);
file->cookie = item;
fListView->AddUnder(item, albumItem);
fListView->SortItemsUnder(albumItem, true, StringItemComp);
if (fQueryString == "")
return;
if (title.IFindFirst(fQueryString) >= 0) {
fListView->Expand(artistItem);
fListView->Expand(albumItem);
} else if (album.IFindFirst(fQueryString) >= 0) {
fListView->Expand(artistItem);
}
};
void
EntryRemoved(WatchedFile* file)
{
ListItem* item = (ListItem*)file->cookie;
ListItem* album = (ListItem*)fListView->Superitem(item);
fListView->RemoveItem(item);
if (album != NULL && fListView->CountItemsUnder(album, true) == 0) {
ListItem* artist = (ListItem*)fListView->Superitem(album);
fListView->RemoveItem(album);
if (artist != NULL && fListView->CountItemsUnder(artist, true) == 0)
fListView->RemoveItem(artist);
}
};
void
EntryMoved(WatchedFile* file)
{
AttrChanged(file);
};
void
AttrChanged(WatchedFile* file)
{
EntryRemoved(file);
EntryCreated(file);
}
void
EntriesCleared()
{
for (int32 i = 0; i < fListView->FullListCountItems(); i++)
delete fListView->FullListItemAt(i);
fListView->MakeEmpty();
fArtistList.MakeEmpty();
fAlbumList.MakeEmpty();
printf("prev count %i\n", (int)fItemCount);
fItemCount = 0;
fCountView->SetText("Count: 0");
}
private:
ListItem*
_AddSuperItem(const char* name, BObjectList<ListItem>& list,
ListItem* under)
{
ListItem* item = _FindStringItem(list, name, under);
if (item != NULL)
return item;
item = new ListItem(name);
fListView->AddUnder(item, under);
fListView->SortItemsUnder(under, true, StringItemComp);
list.AddItem(item);
fListView->Collapse(item);
return item;
}
ListItem*
_FindStringItem(BObjectList<ListItem>& list, const char* text,
ListItem* parent)
{
for (int32 i = 0; i < list.CountItems(); i++) {
ListItem* item = list.ItemAt(i);
ListItem* superItem = (ListItem*)fListView->Superitem(item);
if (parent != NULL && parent != superItem)
continue;
if (strcmp(item->Text(), text) == 0)
return item;
}
return NULL;
}
BOutlineListView* fListView;
BStringView* fCountView;
BObjectList<ListItem> fArtistList;
BObjectList<ListItem> fAlbumList;
BString fQueryString;
int32 fItemCount;
};
const uint32 kMsgQueryInput = '&qin';
const uint32 kMsgItemInvoked = '&iin';
MusicCollectionWindow::MusicCollectionWindow(BRect frame, const char* title)
:
BWindow(frame, title, B_DOCUMENT_WINDOW, B_AVOID_FRONT)
{
BView* rootView = new BView(Bounds(), NULL, B_FOLLOW_ALL, B_WILL_DRAW);
AddChild(rootView);
rootView->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
fQueryField = new BTextControl("Search: ", "", NULL);
fQueryField->SetExplicitAlignment(BAlignment(B_ALIGN_HORIZONTAL_CENTER,
B_ALIGN_USE_FULL_HEIGHT));
fQueryField->SetModificationMessage(new BMessage(kMsgQueryInput));
fCountView = new BStringView("Count View", "Count:");
fFileListView = new MusicFileListView("File List View");
fFileListView->SetInvocationMessage(new BMessage(kMsgItemInvoked));
BScrollView* scrollView = new BScrollView("list scroll", fFileListView, 0,
true, true, B_PLAIN_BORDER);
float spacing = be_control_look->DefaultItemSpacing() / 2;
BALMLayout* layout = new BALMLayout(spacing);
layout->SetInset(spacing);
rootView->SetLayout(layout);
layout->AddView(fQueryField, layout->Left(), layout->Top());
layout->AddViewToRight(fCountView, layout->Right());
layout->AddView(scrollView, layout->Left(),
layout->AreaFor(fQueryField)->Bottom(), layout->Right(),
layout->Bottom());
Area* area = layout->AreaFor(scrollView);
area->SetLeftInset(0);
area->SetRightInset(0);
area->SetBottomInset(0);
BSize min = layout->MinSize();
BSize max = layout->MaxSize();
SetSizeLimits(min.Width(), max.Width(), min.Height(), max.Height());
fEntryViewInterface = new ListViewListener<FileListItem>(fFileListView,
fCountView);
fQueryHandler = new QueryHandler(fEntryViewInterface);
AddHandler(fQueryHandler);
fQueryReader = new QueryReader(fQueryHandler);
fQueryHandler->SetReadThread(fQueryReader);
// start initial query
PostMessage(kMsgQueryInput);
}
MusicCollectionWindow::~MusicCollectionWindow()
{
delete fQueryReader;
delete fQueryHandler;
delete fEntryViewInterface;
}
bool
MusicCollectionWindow::QuitRequested()
{
be_app->PostMessage(B_QUIT_REQUESTED);
return true;
}
void
MusicCollectionWindow::MessageReceived(BMessage* message)
{
switch (message->what) {
case kMsgQueryInput:
_StartNewQuery();
break;
case kMsgItemInvoked:
fFileListView->Launch(message);
break;
default:
BWindow::MessageReceived(message);
}
}
void
CaseInsensitiveString(BString &instring, BString &outstring)
{
outstring = "";
int i = 0;
while (instring[i])
{
if (instring[i] >= 65 && instring[i] <= 90) // capital letters
{
int ch = instring[i] + 32;
outstring += "[";
outstring += ch;
outstring += instring[i];
outstring += "]";
} else if (instring[i] >= 97 && instring[i] <= 122)
{
int ch = instring[i]-32;
outstring += "[";
outstring += instring[i];
outstring += ch;
outstring += "]";
} else
outstring += instring[i];
i++;
}
}
void
MusicCollectionWindow::_StartNewQuery()
{
fQueryReader->Reset();
fQueryHandler->Reset();
BString orgString = fQueryField->Text();
((ListViewListener<FileListItem>*)fEntryViewInterface)->SetQueryString(
orgString);
BVolume volume;
//BVolumeRoster().GetBootVolume(&volume);
BVolumeRoster roster;
while (roster.GetNextVolume(&volume) == B_OK) {
if (!volume.KnowsQuery())
continue;
BQuery* query = _CreateQuery(orgString);
query->SetVolume(&volume);
fQueryReader->AddQuery(query);
}
fQueryReader->Run();
}
BQuery*
MusicCollectionWindow::_CreateQuery(BString& orgString)
{
BQuery* query = new BQuery;
BString queryString;
CaseInsensitiveString(orgString, queryString);
query->PushAttr("Media:Title");
query->PushString(queryString);
query->PushOp(B_CONTAINS);
query->PushAttr("Audio:Album");
query->PushString(queryString);
query->PushOp(B_CONTAINS);
query->PushOp(B_OR);
query->PushAttr("Audio:Artist");
query->PushString(queryString);
query->PushOp(B_CONTAINS);
query->PushOp(B_OR);
if (queryString == "") {
query->PushAttr("BEOS:TYPE");
query->PushString("audio/");
query->PushOp(B_BEGINS_WITH);
query->PushOp(B_OR);
}
query->PushAttr("BEOS:TYPE");
query->PushString("audio/");
query->PushOp(B_BEGINS_WITH);
query->PushAttr("name");
query->PushString(queryString);
query->PushOp(B_CONTAINS);
query->PushOp(B_AND);
query->PushOp(B_OR);
return query;
}

View File

@ -0,0 +1,42 @@
/*
* Copyright 2011, Haiku, Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Clemens Zeidler <haiku@clemens-zeidler.de>
*/
#ifndef MUSIC_COLLECTION_WINDOW_H
#define MUSIC_COLLECTION_WINDOW_H
#include <OutlineListView.h>
#include <StringView.h>
#include <TextControl.h>
#include <Window.h>
#include "MusicFileListView.h"
class MusicCollectionWindow : public BWindow {
public:
MusicCollectionWindow(BRect rect,
const char* name);
virtual ~MusicCollectionWindow();
virtual bool QuitRequested();
virtual void MessageReceived(BMessage* message);
private:
void _StartNewQuery();
BQuery* _CreateQuery(BString& queryString);
BTextControl* fQueryField;
BStringView* fCountView;
MusicFileListView* fFileListView;
EntryViewInterface* fEntryViewInterface;
QueryHandler* fQueryHandler;
QueryReader* fQueryReader;
};
#endif // MUSIC_COLLECTION_WINDOW_H

View File

@ -0,0 +1,130 @@
/*
* Copyright 2011, Haiku, Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Clemens Zeidler <haiku@clemens-zeidler.de>
*/
#ifndef MUSIC_FILE_LIST_VIEW_H
#define MUSIC_FILE_LIST_VIEW_H
#include <Bitmap.h>
#include <ListItem.h>
#include <OutlineListView.h>
#include <Roster.h>
#include "QueryMonitor.h"
class FileListItem : public BStringItem {
public:
FileListItem(const char* text, WatchedFile* file = NULL)
:
BStringItem(text),
fFile(file)
{
}
WatchedFile*
File()
{
return fFile;
}
private:
WatchedFile* fFile;
};
class MusicFileListView : public BOutlineListView {
public:
MusicFileListView(const char *name)
:
BOutlineListView(name)
{
}
bool
InitiateDrag(BPoint where, int32 index, bool wasSelected)
{
int32 itemIndex = IndexOf(where);
FileListItem* item = (FileListItem*)ItemAt(itemIndex);
if (item == NULL)
return false;
const char* text = item->Text();
BRect rect(0, 0, 200, 50);
BBitmap* bitmap = new BBitmap(rect, B_RGB32, true);
BView* bitmapView = new BView(rect, "bitmap", B_FOLLOW_NONE,
B_WILL_DRAW);
bitmap->Lock();
bitmap->AddChild(bitmapView);
bitmapView->SetLowColor(255, 255, 255, 0); // transparent
bitmapView->SetHighColor(0, 0, 0, 100);
bitmapView->SetDrawingMode(B_OP_COPY);
bitmapView->FillRect(bitmapView->Bounds(), B_SOLID_LOW);
bitmapView->SetDrawingMode(B_OP_OVER);
font_height height;
bitmapView->GetFontHeight(&height);
float fontHeight = height.ascent + height.descent;
BRect latchRect = LatchRect(BRect(0, 0, item->Width(), item->Height()),
item->OutlineLevel());
bitmapView->DrawString(text, BPoint(latchRect.Width(), fontHeight));
bitmapView->Sync();
bitmap->Unlock();
BMessage dragMessage(B_SIMPLE_DATA);
dragMessage.AddPoint("click_location", where);
_RecursiveAddRefs(dragMessage, item);
BRect itemFrame(ItemFrame(itemIndex));
BPoint pt(where.x + itemFrame.left, where.y - itemFrame.top);
DragMessage(&dragMessage, bitmap, B_OP_ALPHA, pt, this);
return true;
}
void
Launch(BMessage* message)
{
int32 index;
for (int32 i = 0; ; i++) {
if (message->FindInt32("index", i, &index) != B_OK)
break;
FileListItem* item = (FileListItem*)ItemAt(index);
BMessage refs(B_REFS_RECEIVED);
_RecursiveAddRefs(refs, item);
be_roster->Launch("application/x-vnd.Haiku-MediaPlayer", &refs);
}
};
private:
void
_RecursiveAddRefs(BMessage& message, FileListItem* item)
{
WatchedFile* file = item->File();
if (file != NULL) {
message.AddRef("refs", &(file->entry));
} else {
for (int32 i = 0; i < CountItemsUnder(item, true); i++) {
_RecursiveAddRefs(message, (FileListItem*)ItemUnderAt(
item, true, i));
}
}
}
};
#endif // MUSIC_FILE_LIST_VIEW_H

View File

@ -0,0 +1,90 @@
/*
* Copyright 2011, Haiku, Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Clemens Zeidler <haiku@clemens-zeidler.de>
*/
#include "QueryMonitor.h"
QueryHandler::QueryHandler(EntryViewInterface* listener)
:
FileMonitor(listener)
{
}
void
QueryHandler::MessageReceived(BMessage* message)
{
int32 opcode;
if (message->what == B_QUERY_UPDATE
&& message->FindInt32("opcode", &opcode) == B_OK) {
switch (opcode) {
case B_ENTRY_CREATED:
case B_ENTRY_REMOVED:
message->what = B_NODE_MONITOR;
break;
}
}
FileMonitor::MessageReceived(message);
}
QueryReader::QueryReader(QueryHandler* handler)
:
ReadThread(handler)
{
}
QueryReader::~QueryReader()
{
Reset();
}
bool
QueryReader::AddQuery(BQuery* query)
{
query->SetTarget(fTarget);
query->Fetch();
return fQueries.AddItem(query);
}
void
QueryReader::Reset()
{
Stop();
Wait();
for (int32 i = 0; i < fLiveQueries.CountItems(); i++)
delete fLiveQueries.ItemAt(i);
fLiveQueries.MakeEmpty();
for (int32 i = 0; i < fQueries.CountItems(); i++)
delete fQueries.ItemAt(i);
fQueries.MakeEmpty();
}
bool
QueryReader::ReadNextEntry(entry_ref& entry)
{
BQuery* query = fQueries.ItemAt(0);
if (query == NULL)
return false;
if (query->GetNextRef(&entry) != B_OK) {
fQueries.RemoveItemAt(0);
fLiveQueries.AddItem(query);
return ReadNextEntry(entry);
}
return true;
}

View File

@ -0,0 +1,49 @@
/*
* Copyright 2011, Haiku, Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Clemens Zeidler <haiku@clemens-zeidler.de>
*/
#ifndef QUERY_MONITOR_H
#define QUERY_MONITOR_H
#include <Query.h>
#include <ObjectList.h>
#include "FileMonitor.h"
/*! Handle live query messages, query reader messages, and node monitor messages
and dispatch them to a EntryViewInterface. */
class QueryHandler : public FileMonitor {
public:
QueryHandler(EntryViewInterface* listener);
void MessageReceived(BMessage* message);
};
typedef BObjectList<BQuery> BQueryList;
class QueryReader : public ReadThread {
public:
QueryReader(QueryHandler* handler);
~QueryReader();
bool AddQuery(BQuery* query);
void Reset();
protected:
bool ReadNextEntry(entry_ref& entry);
private:
BQueryList fQueries;
BQueryList fLiveQueries;
};
#endif // QUERY_MONITOR_H

View File

@ -0,0 +1,31 @@
/*
* Copyright 2011, Haiku, Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Clemens Zeidler <haiku@clemens-zeidler.de>
*/
#include <Application.h>
#include <Catalog.h>
#include "MusicCollectionWindow.h"
const char* kAppSignature = "application/x-vnd.MusicCollection";
int
main(int argc, char* argv[])
{
BApplication app(kAppSignature);
BWindow* window = new MusicCollectionWindow(BRect(100, 100, 350, 500),
B_TRANSLATE_SYSTEM_NAME("Music Collection"));
window->Show();
app.Run();
return 0;
}