* Work in progress on implementing asynchronous image retrieval with a
read-ahead cache. Not yet done or used. * Added deleting images via delete key again. The only difference to before is that it will open the previous image if the last image in a folder was removed, and only close ShowImage if there is no image left. git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@39322 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
@@ -18,7 +18,11 @@
|
|||||||
|
|
||||||
#include "ImageFileNavigator.h"
|
#include "ImageFileNavigator.h"
|
||||||
|
|
||||||
|
#include <deque>
|
||||||
|
#include <map>
|
||||||
#include <new>
|
#include <new>
|
||||||
|
#include <set>
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
#include <Bitmap.h>
|
#include <Bitmap.h>
|
||||||
@@ -26,11 +30,14 @@
|
|||||||
#include <Directory.h>
|
#include <Directory.h>
|
||||||
#include <Entry.h>
|
#include <Entry.h>
|
||||||
#include <File.h>
|
#include <File.h>
|
||||||
|
#include <Locker.h>
|
||||||
#include <ObjectList.h>
|
#include <ObjectList.h>
|
||||||
#include <Path.h>
|
#include <Path.h>
|
||||||
#include <TranslatorRoster.h>
|
#include <TranslatorRoster.h>
|
||||||
|
|
||||||
|
#include <AutoDeleter.h>
|
||||||
#include <tracker_private.h>
|
#include <tracker_private.h>
|
||||||
|
#include <kernel/util/DoublyLinkedList.h>
|
||||||
|
|
||||||
#include "ProgressWindow.h"
|
#include "ProgressWindow.h"
|
||||||
#include "ShowImageConstants.h"
|
#include "ShowImageConstants.h"
|
||||||
@@ -183,11 +190,11 @@ TrackerNavigator::FindNextImage(const entry_ref& currentRef, entry_ref& ref,
|
|||||||
else
|
else
|
||||||
specifier.what = 'sprv';
|
specifier.what = 'sprv';
|
||||||
specifier.AddString("property", "Entry");
|
specifier.AddString("property", "Entry");
|
||||||
if (rewind)
|
if (rewind) {
|
||||||
// if rewinding, ask for the ref to the
|
// if rewinding, ask for the ref to the
|
||||||
// first item in the directory
|
// first item in the directory
|
||||||
specifier.AddInt32("data", 0);
|
specifier.AddInt32("data", 0);
|
||||||
else
|
} else
|
||||||
specifier.AddRef("data", &nextRef);
|
specifier.AddRef("data", &nextRef);
|
||||||
request.AddSpecifier(&specifier);
|
request.AddSpecifier(&specifier);
|
||||||
|
|
||||||
@@ -313,6 +320,249 @@ FolderNavigator::_CompareRefs(const entry_ref* refA, const entry_ref* refB)
|
|||||||
// #pragma mark -
|
// #pragma mark -
|
||||||
|
|
||||||
|
|
||||||
|
struct CacheEntry : DoublyLinkedListLinkImpl<CacheEntry> {
|
||||||
|
entry_ref ref;
|
||||||
|
int32 page;
|
||||||
|
int32 pageCount;
|
||||||
|
BBitmap* bitmap;
|
||||||
|
BString type;
|
||||||
|
BString mimeType;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct QueueEntry {
|
||||||
|
entry_ref ref;
|
||||||
|
int32 page;
|
||||||
|
std::set<BMessenger> listeners;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class ImageCache {
|
||||||
|
public:
|
||||||
|
ImageCache();
|
||||||
|
virtual ~ImageCache();
|
||||||
|
|
||||||
|
void RetrieveImage(const entry_ref& ref,
|
||||||
|
const BMessenger* target);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static status_t _QueueWorkerThread(void* self);
|
||||||
|
|
||||||
|
status_t _RetrieveImage(QueueEntry* entry,
|
||||||
|
BMessage& message);
|
||||||
|
void _NotifyListeners(QueueEntry* entry,
|
||||||
|
BMessage& message);
|
||||||
|
|
||||||
|
private:
|
||||||
|
typedef std::pair<entry_ref, int32> ImageSelector;
|
||||||
|
typedef std::map<ImageSelector, CacheEntry*> CacheMap;
|
||||||
|
typedef std::map<ImageSelector, QueueEntry*> QueueMap;
|
||||||
|
typedef std::deque<QueueEntry*> QueueDeque;
|
||||||
|
typedef DoublyLinkedList<CacheEntry> CacheList;
|
||||||
|
|
||||||
|
BLocker fCacheLocker;
|
||||||
|
CacheMap fCacheMap;
|
||||||
|
CacheList fCacheEntriesByAge;
|
||||||
|
BLocker fQueueLocker;
|
||||||
|
QueueMap fQueueMap;
|
||||||
|
QueueDeque fQueue;
|
||||||
|
vint32 fThreadCount;
|
||||||
|
int32 fMaxThreadCount;
|
||||||
|
uint64 fBytes;
|
||||||
|
uint64 fMaxBytes;
|
||||||
|
size_t fMaxEntries;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
ImageCache::ImageCache()
|
||||||
|
:
|
||||||
|
fCacheLocker("image cache"),
|
||||||
|
fQueueLocker("image queue"),
|
||||||
|
fThreadCount(0),
|
||||||
|
fBytes(0)
|
||||||
|
{
|
||||||
|
system_info info;
|
||||||
|
get_system_info(&info);
|
||||||
|
|
||||||
|
fMaxThreadCount = (info.cpu_count + 1) / 2;
|
||||||
|
fMaxBytes = info.max_pages * B_PAGE_SIZE / 8;
|
||||||
|
fMaxEntries = 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ImageCache::~ImageCache()
|
||||||
|
{
|
||||||
|
// TODO: delete CacheEntries, and QueueEntries
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
ImageCache::RetrieveImage(const entry_ref& ref, const BMessenger* target)
|
||||||
|
{
|
||||||
|
// TODO!
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*static*/ status_t
|
||||||
|
ImageCache::_QueueWorkerThread(void* _self)
|
||||||
|
{
|
||||||
|
ImageCache* self = (ImageCache*)_self;
|
||||||
|
|
||||||
|
// get next queue entry
|
||||||
|
while (true) {
|
||||||
|
self->fQueueLocker.Lock();
|
||||||
|
if (self->fQueue.empty()) {
|
||||||
|
self->fQueueLocker.Unlock();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
QueueEntry* entry = *self->fQueue.begin();
|
||||||
|
self->fQueue.pop_front();
|
||||||
|
self->fQueueLocker.Unlock();
|
||||||
|
|
||||||
|
if (entry == NULL)
|
||||||
|
break;
|
||||||
|
|
||||||
|
BMessage notification(kMsgImageLoaded);
|
||||||
|
status_t status = self->_RetrieveImage(entry, notification);
|
||||||
|
if (status != B_OK)
|
||||||
|
notification.AddInt32("error", status);
|
||||||
|
|
||||||
|
self->fQueueLocker.Lock();
|
||||||
|
self->fQueueMap.erase(std::make_pair(entry->ref, entry->page));
|
||||||
|
self->fQueueLocker.Unlock();
|
||||||
|
|
||||||
|
self->_NotifyListeners(entry, notification);
|
||||||
|
delete entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
atomic_add(&self->fThreadCount, -1);
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
status_t
|
||||||
|
ImageCache::_RetrieveImage(QueueEntry* queueEntry, BMessage& message)
|
||||||
|
{
|
||||||
|
CacheEntry* entry = new(std::nothrow) CacheEntry();
|
||||||
|
if (entry == NULL)
|
||||||
|
return B_NO_MEMORY;
|
||||||
|
|
||||||
|
ObjectDeleter<CacheEntry> deleter(entry);
|
||||||
|
|
||||||
|
BTranslatorRoster* roster = BTranslatorRoster::Default();
|
||||||
|
if (roster == NULL)
|
||||||
|
return B_ERROR;
|
||||||
|
|
||||||
|
if (!entry_ref_is_file(queueEntry->ref))
|
||||||
|
return B_IS_A_DIRECTORY;
|
||||||
|
|
||||||
|
BFile file;
|
||||||
|
status_t status = file.SetTo(&queueEntry->ref, B_READ_ONLY);
|
||||||
|
if (status != B_OK)
|
||||||
|
return status;
|
||||||
|
|
||||||
|
translator_info info;
|
||||||
|
memset(&info, 0, sizeof(translator_info));
|
||||||
|
BMessage ioExtension;
|
||||||
|
|
||||||
|
if (queueEntry->page != 0
|
||||||
|
&& ioExtension.AddInt32("/documentIndex", queueEntry->page) != B_OK)
|
||||||
|
return B_NO_MEMORY;
|
||||||
|
|
||||||
|
// TODO: rethink this!
|
||||||
|
#if 0
|
||||||
|
if (fProgressWindow != NULL) {
|
||||||
|
BMessage progress(kMsgProgressStatusUpdate);
|
||||||
|
if (ioExtension.AddMessenger("/progressMonitor",
|
||||||
|
fProgressWindow) == B_OK
|
||||||
|
&& ioExtension.AddMessage("/progressMessage", &progress) == B_OK)
|
||||||
|
fProgressWindow->Start();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Translate image data and create a new ShowImage window
|
||||||
|
|
||||||
|
BBitmapStream outstream;
|
||||||
|
|
||||||
|
status = roster->Identify(&file, &ioExtension, &info, 0, NULL,
|
||||||
|
B_TRANSLATOR_BITMAP);
|
||||||
|
if (status == B_OK) {
|
||||||
|
status = roster->Translate(&file, &info, &ioExtension, &outstream,
|
||||||
|
B_TRANSLATOR_BITMAP);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
if (fProgressWindow != NULL)
|
||||||
|
fProgressWindow->Stop();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (status != B_OK)
|
||||||
|
return status;
|
||||||
|
|
||||||
|
BBitmap* bitmap;
|
||||||
|
if (outstream.DetachBitmap(&bitmap) != B_OK)
|
||||||
|
return B_ERROR;
|
||||||
|
|
||||||
|
entry->ref = queueEntry->ref;
|
||||||
|
entry->page = queueEntry->page;
|
||||||
|
entry->bitmap = bitmap;
|
||||||
|
entry->type = info.name;
|
||||||
|
entry->mimeType = info.MIME;
|
||||||
|
|
||||||
|
// get the number of documents (pages) if it has been supplied
|
||||||
|
int32 documentCount = 0;
|
||||||
|
if (ioExtension.FindInt32("/documentCount", &documentCount) == B_OK
|
||||||
|
&& documentCount > 0)
|
||||||
|
entry->pageCount = documentCount;
|
||||||
|
else
|
||||||
|
entry->pageCount = 1;
|
||||||
|
|
||||||
|
message.AddString("type", info.name);
|
||||||
|
message.AddString("mime", info.MIME);
|
||||||
|
message.AddRef("ref", &entry->ref);
|
||||||
|
message.AddInt32("page", entry->page);
|
||||||
|
message.AddPointer("bitmap", (void*)bitmap);
|
||||||
|
|
||||||
|
deleter.Detach();
|
||||||
|
|
||||||
|
fCacheLocker.Lock();
|
||||||
|
|
||||||
|
fCacheMap.insert(std::make_pair(
|
||||||
|
std::make_pair(entry->ref, entry->page), entry));
|
||||||
|
fCacheEntriesByAge.Add(entry);
|
||||||
|
|
||||||
|
fBytes += bitmap->BitsLength();
|
||||||
|
|
||||||
|
while (fBytes > fMaxBytes || fCacheMap.size() > fMaxEntries) {
|
||||||
|
if (fCacheMap.size() <= 2)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Remove the oldest entry
|
||||||
|
entry = fCacheEntriesByAge.RemoveHead();
|
||||||
|
fBytes -= entry->bitmap->BitsLength();
|
||||||
|
fCacheMap.erase(std::make_pair(entry->ref, entry->page));
|
||||||
|
delete entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
fCacheLocker.Unlock();
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
ImageCache::_NotifyListeners(QueueEntry* entry, BMessage& message)
|
||||||
|
{
|
||||||
|
std::set<BMessenger>::iterator iterator = entry->listeners.begin();
|
||||||
|
for (; iterator != entry->listeners.end(); iterator++) {
|
||||||
|
iterator->SendMessage(&message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// #pragma mark -
|
||||||
|
|
||||||
|
|
||||||
ImageFileNavigator::ImageFileNavigator(const BMessenger& target,
|
ImageFileNavigator::ImageFileNavigator(const BMessenger& target,
|
||||||
const entry_ref& ref, const BMessenger& trackerMessenger)
|
const entry_ref& ref, const BMessenger& trackerMessenger)
|
||||||
:
|
:
|
||||||
@@ -534,24 +784,33 @@ ImageFileNavigator::HasPreviousFile()
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void
|
/*! Moves the current file into the trash.
|
||||||
ImageFileNavigator::DeleteFile()
|
Returns true if a new file is being loaded, false if not.
|
||||||
|
*/
|
||||||
|
bool
|
||||||
|
ImageFileNavigator::MoveFileToTrash()
|
||||||
{
|
{
|
||||||
|
entry_ref nextRef;
|
||||||
|
if (!fNavigator->FindNextImage(fCurrentRef, nextRef, true, false)
|
||||||
|
&& !fNavigator->FindNextImage(fCurrentRef, nextRef, false, false))
|
||||||
|
nextRef.device = -1;
|
||||||
|
|
||||||
// Move image to Trash
|
// Move image to Trash
|
||||||
BMessage trash(BPrivate::kMoveToTrash);
|
BMessage trash(BPrivate::kMoveToTrash);
|
||||||
trash.AddRef("refs", &fCurrentRef);
|
trash.AddRef("refs", &fCurrentRef);
|
||||||
|
|
||||||
// TODO!
|
|
||||||
#if 0
|
|
||||||
// We create our own messenger because the member fTrackerMessenger
|
// We create our own messenger because the member fTrackerMessenger
|
||||||
// could be invalid
|
// could be invalid
|
||||||
BMessenger tracker(kTrackerSignature);
|
BMessenger tracker(kTrackerSignature);
|
||||||
if (tracker.SendMessage(&trash) == B_OK && !NextFile()) {
|
if (tracker.SendMessage(&trash) != B_OK)
|
||||||
// This is the last (or only file) in this directory,
|
return true;
|
||||||
// close the window
|
|
||||||
_SendMessageToWindow(B_QUIT_REQUESTED);
|
if (nextRef.device != -1 && LoadImage(nextRef) == B_OK) {
|
||||||
|
fNavigator->UpdateSelection(nextRef);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ public:
|
|||||||
bool HasNextFile();
|
bool HasNextFile();
|
||||||
bool HasPreviousFile();
|
bool HasPreviousFile();
|
||||||
|
|
||||||
void DeleteFile();
|
bool MoveFileToTrash();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
status_t _LoadNextImage(bool next, bool rewind);
|
status_t _LoadNextImage(bool next, bool rewind);
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ enum {
|
|||||||
MSG_GOTO_PAGE = 'mGTP',
|
MSG_GOTO_PAGE = 'mGTP',
|
||||||
MSG_FILE_NEXT = 'mFLN',
|
MSG_FILE_NEXT = 'mFLN',
|
||||||
MSG_FILE_PREV = 'mFLP',
|
MSG_FILE_PREV = 'mFLP',
|
||||||
|
kMsgDeleteCurrentFile = 'mDcF',
|
||||||
MSG_SHRINK_TO_WINDOW = 'mSTW',
|
MSG_SHRINK_TO_WINDOW = 'mSTW',
|
||||||
MSG_STRETCH_TO_WINDOW = 'mZTW',
|
MSG_STRETCH_TO_WINDOW = 'mZTW',
|
||||||
MSG_ROTATE_90 = 'mR90',
|
MSG_ROTATE_90 = 'mR90',
|
||||||
|
|||||||
@@ -1331,11 +1331,8 @@ ShowImageView::KeyDown(const char* bytes, int32 numBytes)
|
|||||||
ClearSelection();
|
ClearSelection();
|
||||||
break;
|
break;
|
||||||
case B_DELETE:
|
case B_DELETE:
|
||||||
{
|
_SendMessageToWindow(kMsgDeleteCurrentFile);
|
||||||
// TODO!
|
|
||||||
//fNavigator.DeleteFile();
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
case '+':
|
case '+':
|
||||||
case '=':
|
case '=':
|
||||||
ZoomIn();
|
ZoomIn();
|
||||||
|
|||||||
@@ -709,6 +709,11 @@ ShowImageWindow::MessageReceived(BMessage* message)
|
|||||||
fNavigator.NextFile();
|
fNavigator.NextFile();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case kMsgDeleteCurrentFile:
|
||||||
|
if (!fNavigator.MoveFileToTrash())
|
||||||
|
PostMessage(B_QUIT_REQUESTED);
|
||||||
|
break;
|
||||||
|
|
||||||
case MSG_ROTATE_90:
|
case MSG_ROTATE_90:
|
||||||
fImageView->Rotate(90);
|
fImageView->Rotate(90);
|
||||||
break;
|
break;
|
||||||
|
|||||||
Reference in New Issue
Block a user