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:
parent
3da6df44df
commit
08c9600ab9
|
@ -35,6 +35,7 @@ HaikuSubInclude mandelbrot ;
|
|||
HaikuSubInclude mediaconverter ;
|
||||
HaikuSubInclude mediaplayer ;
|
||||
HaikuSubInclude midiplayer ;
|
||||
HaikuSubInclude musiccollection ;
|
||||
HaikuSubInclude networkstatus ;
|
||||
HaikuSubInclude networktime ;
|
||||
HaikuSubInclude overlayimage ;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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
|
|
@ -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 ] ;
|
|
@ -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"
|
||||
};
|
||||
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue