From 8db249194cd850b2122f3d76f101cbcc26fd28ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Axel=20D=C3=B6rfler?= Date: Sat, 28 Feb 2004 04:10:48 +0000 Subject: [PATCH] Implemented find functionality. The find panel is still missing though, it just searches for "test". The find itself is implemented in DataEditor::Find() which sets the editor in read-only mode and searches for the specified data. It will update a progress monitor, it will find data no matter if it's on a view size break, and it is interruptible at any time. The DataEditor change methods now respect the read-only mode, and return B_NOT_ALLOWED in this case. Renamed the UpdateLooper class to EditorLooper class, since it now also runs the find action. The ProbeView class now maintains the "Find Again" menu item; it's target message always contains the item that last searched for. The HeaderView will now show a "Stop" button while the search operation is in progress; it also acts as the Find() progress monitor. git-svn-id: file:///srv/svn/repos/haiku/trunk/current@6789 a95241bf-73f2-0310-859d-f6bbb57e9c96 --- src/apps/diskprobe/DataEditor.cpp | 150 ++++++++++++++++++++++ src/apps/diskprobe/DataEditor.h | 6 + src/apps/diskprobe/ProbeView.cpp | 198 ++++++++++++++++++++++++++---- src/apps/diskprobe/ProbeView.h | 8 +- 4 files changed, 338 insertions(+), 24 deletions(-) diff --git a/src/apps/diskprobe/DataEditor.cpp b/src/apps/diskprobe/DataEditor.cpp index 7a8225729b..dabff4f341 100644 --- a/src/apps/diskprobe/DataEditor.cpp +++ b/src/apps/diskprobe/DataEditor.cpp @@ -383,6 +383,8 @@ DataEditor::SetTo(entry_ref &ref, const char *attribute) status_t DataEditor::SetTo(BEntry &entry, const char *attribute) { + fSize = 0; + struct stat stat; stat.st_mode = 0; @@ -510,6 +512,9 @@ DataEditor::AddChange(DataChange *change) status_t DataEditor::Replace(off_t offset, const uint8 *data, size_t length) { + if (IsReadOnly()) + return B_NOT_ALLOWED; + BAutolock locker(this); if (offset >= fSize) @@ -533,6 +538,9 @@ DataEditor::Replace(off_t offset, const uint8 *data, size_t length) status_t DataEditor::Remove(off_t offset, off_t length) { + if (IsReadOnly()) + return B_NOT_ALLOWED; + BAutolock locker(this); // not yet implemented @@ -545,6 +553,9 @@ DataEditor::Remove(off_t offset, off_t length) status_t DataEditor::Insert(off_t offset, const uint8 *text, size_t length) { + if (IsReadOnly()) + return B_NOT_ALLOWED; + BAutolock locker(this); // not yet implemented @@ -926,6 +937,21 @@ DataEditor::UpdateIfNeeded(bool *_updated = NULL) } +status_t +DataEditor::ForceUpdate() +{ + status_t status = B_OK; + + if (fView == NULL) + status = SetViewOffset(fViewOffset); + + if (status == B_OK) + status = Update(); + + return status; +} + + status_t DataEditor::GetViewBuffer(const uint8 **_buffer) { @@ -941,6 +967,130 @@ DataEditor::GetViewBuffer(const uint8 **_buffer) } +off_t +DataEditor::Find(off_t startPosition, const uint8 *data, size_t dataSize, + bool cyclic, BMessenger progressMonitor, volatile bool *stop) +{ + if (data == NULL || dataSize == 0) + return B_BAD_VALUE; + + if (startPosition < 0) + startPosition = 0; + + BAutolock locker(this); + + bool savedIsReadOnly = fIsReadOnly; + fIsReadOnly = true; + off_t savedOffset = fViewOffset; + off_t position = (startPosition / fRealViewSize) * fRealViewSize; + size_t firstByte = startPosition % fRealViewSize; + size_t matchLastOffset = 0; + off_t foundAt = B_ENTRY_NOT_FOUND; + bool noStop = false; + if (stop == NULL) + stop = &noStop; + + // start progress monitor + { + BMessage progress(kMsgDataEditorFindProgress); + progress.AddBool("running", true); + progressMonitor.SendMessage(&progress); + } + + bigtime_t lastReport = 0; + + off_t blocks = fSize; + if (!cyclic) + blocks -= position; + blocks = (blocks + fRealViewSize - 1) / fRealViewSize; + + for (; blocks-- > 0 && !*stop; position += fRealViewSize) { + if (position > fSize) + position = 0; + + SetViewOffset(position, false); + if (fNeedsUpdate) + Update(); + + bigtime_t current = system_time(); + if (lastReport + 500000LL < current) { + // report the progress two times a second + BMessage progress(kMsgDataEditorFindProgress); + progress.AddInt64("position", position); + progressMonitor.SendMessage(&progress); + + lastReport = current; + } + + // search for data in current block + + if (matchLastOffset != 0) { + // we had a partial match in the previous block, let's + // check if it is a whole match + if (!memcmp(fView, data + matchLastOffset, dataSize - matchLastOffset)) { + matchLastOffset = 0; + break; + } + + foundAt = B_ENTRY_NOT_FOUND; + matchLastOffset = 0; + } + + for (size_t i = firstByte; i < fRealViewSize; i++) { + if (position + i + dataSize > fSize) + break; + + if (fView[i] == data[0]) { + // one byte matches, compare the rest + size_t size = dataSize - 1; + size_t offset = i + 1; + + if (offset + size > fRealViewSize) + size = fRealViewSize - offset; + + if (size == 0 || !memcmp(fView + offset, data + 1, size)) { + foundAt = position + i; + + if (size != dataSize - 1) { + // only a partial match, we have to check the start + // of the next block + matchLastOffset = size + 1; + } + break; + } + } + } + + if (foundAt >= 0 && matchLastOffset == 0) + break; + + firstByte = 0; + } + + fIsReadOnly = savedIsReadOnly; + + if (foundAt >= 0 && matchLastOffset != 0) + foundAt = B_ENTRY_NOT_FOUND; + + // stop progress monitor + { + BMessage progress(kMsgDataEditorFindProgress); + progress.AddBool("running", false); + progress.AddInt64("position", foundAt >= 0 ? foundAt : savedOffset); + progressMonitor.SendMessage(&progress); + } + + SetViewOffset(savedOffset, false); + // this is to ensure that we're sending an update when + // we're set to the found data offset + + if (foundAt < 0 && *stop) + return B_INTERRUPTED; + + return foundAt; +} + + void DataEditor::SendNotices(DataChange *change) { diff --git a/src/apps/diskprobe/DataEditor.h b/src/apps/diskprobe/DataEditor.h index eca3888b17..8e96263c4e 100644 --- a/src/apps/diskprobe/DataEditor.h +++ b/src/apps/diskprobe/DataEditor.h @@ -65,6 +65,7 @@ class DataEditor : public BLocker { size_t BlockSize() const { return fBlockSize; } status_t UpdateIfNeeded(bool *_updated = NULL); + status_t ForceUpdate(); status_t GetViewBuffer(const uint8 **_buffer); status_t StartWatching(BMessenger target); @@ -72,6 +73,9 @@ class DataEditor : public BLocker { void StopWatching(BMessenger target); void StopWatching(BHandler *handler, BLooper *looper = NULL); + off_t Find(off_t startPosition, const uint8 *data, size_t dataSize, bool cyclic, + BMessenger progressMessenger, volatile bool *stop = NULL); + BFile &File() { return fFile; } const entry_ref &Ref() const { return fRef; } @@ -113,4 +117,6 @@ static const uint32 kMsgDataEditorStateChange = 'deSC'; static const uint32 kMsgDataEditorUpdate = 'deUp'; static const uint32 kMsgDataEditorParameterChange = 'dePC'; +static const uint32 kMsgDataEditorFindProgress = 'deFP'; + #endif /* DATA_EDITOR_H */ diff --git a/src/apps/diskprobe/ProbeView.cpp b/src/apps/diskprobe/ProbeView.cpp index 858f9f48b4..26ffb07b41 100644 --- a/src/apps/diskprobe/ProbeView.cpp +++ b/src/apps/diskprobe/ProbeView.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -34,6 +35,7 @@ #include #include #include +#include #include #include @@ -53,6 +55,10 @@ static const uint32 kMsgAddBookmark = 'bmrk'; static const uint32 kMsgPrint = 'prnt'; static const uint32 kMsgPageSetup = 'pgsp'; +static const uint32 kMsgFind = 'find'; +static const uint32 kMsgFindWindow = 'fndw'; +static const uint32 kMsgStopFind = 'sfnd'; + class IconView : public BView { public: @@ -112,7 +118,8 @@ class HeaderView : public BView, public BInvoker { base_type Base() const { return fBase; } void SetBase(base_type); - + + off_t CursorOffset() const { return fOffset; } off_t Position() const { return fPosition; } uint32 BlockSize() const { return fBlockSize; } void SetTo(off_t position, uint32 blockSize); @@ -140,6 +147,7 @@ class HeaderView : public BView, public BInvoker { BStringView *fFileOffsetView; PositionSlider *fPositionSlider; IconView *fIconView; + BButton *fStopButton; }; @@ -155,16 +163,21 @@ class TypeMenuItem : public BMenuItem { }; -class UpdateLooper : public BLooper { +class EditorLooper : public BLooper { public: - UpdateLooper(const char *name, DataEditor &editor, BMessenger messenger); - virtual ~UpdateLooper(); + EditorLooper(const char *name, DataEditor &editor, BMessenger messenger); + virtual ~EditorLooper(); virtual void MessageReceived(BMessage *message); + bool FindIsRunning() const { return !fQuitFind; } + void Find(off_t startAt, const uint8 *data, size_t dataSize, BMessenger progressMonitor); + void QuitFind(); + private: - DataEditor &fEditor; - BMessenger fMessenger; + DataEditor &fEditor; + BMessenger fMessenger; + volatile bool fQuitFind; }; @@ -412,12 +425,20 @@ HeaderView::HeaderView(BRect frame, const entry_ref *ref, DataEditor &editor) fIconView = new IconView(BRect(10, 10, 41, 41), ref, editor.IsDevice()); AddChild(fIconView); + BRect rect = Bounds(); + fStopButton = new BButton(BRect(0, 0, 20, 20), B_EMPTY_STRING, "Stop", + new BMessage(kMsgStopFind)); + fStopButton->ResizeToPreferred(); + fStopButton->MoveTo(rect.right - 5 - fStopButton->Bounds().Width(), 5); + fStopButton->Hide(); + AddChild(fStopButton); + BFont boldFont = *be_bold_font; boldFont.SetSize(10.0); BFont plainFont = *be_plain_font; plainFont.SetSize(10.0); - BStringView *stringView = new BStringView(BRect(50, 6, frame.right, 20), + BStringView *stringView = new BStringView(BRect(50, 6, rect.right, 20), B_EMPTY_STRING, editor.IsAttribute() ? "Attribute: " : editor.IsDevice() ? "Device: " : "File: "); stringView->SetFont(&boldFont); stringView->ResizeToPreferred(); @@ -430,9 +451,9 @@ HeaderView::HeaderView(BRect frame, const entry_ref *ref, DataEditor &editor) string.Prepend(fAttribute); string.Append(")"); } - BRect rect = stringView->Frame(); + rect = stringView->Frame(); rect.left = rect.right; - rect.right = frame.right; + rect.right = fStopButton->Frame().left - 1; fPathView = new BStringView(rect, B_EMPTY_STRING, string.String()); fPathView->SetFont(&plainFont); AddChild(fPathView); @@ -543,6 +564,7 @@ HeaderView::AttachedToWindow() { SetTarget(Window()); + fStopButton->SetTarget(Parent()); fPositionControl->SetTarget(this); fPositionSlider->SetTarget(this); } @@ -698,6 +720,30 @@ HeaderView::MessageReceived(BMessage *message) break; } + case kMsgDataEditorFindProgress: + { + bool state; + if (message->FindBool("running", &state) == B_OK && fFileSize > fBlockSize) { + fPositionSlider->SetEnabled(!state); + if (state) + fStopButton->Show(); + else + fStopButton->Hide(); + } + + off_t position; + if (message->FindInt64("position", &position) != B_OK) + break; + + fPosition = (position / fBlockSize) * fBlockSize; + // round to block size + + // update views + UpdatePositionViews(); + fPositionSlider->SetPosition(fPosition); + break; + } + case kMsgPositionUpdate: { fLastPosition = fPosition; @@ -815,25 +861,28 @@ TypeMenuItem::DrawContent() * That way, simple offset changes will not stop the main * looper from operating. Therefore, all offset updates * for the editor will go through this looper. + * + * Also, it will run the find action in the editor. */ -UpdateLooper::UpdateLooper(const char *name, DataEditor &editor, BMessenger target) +EditorLooper::EditorLooper(const char *name, DataEditor &editor, BMessenger target) : BLooper(name), fEditor(editor), - fMessenger(target) + fMessenger(target), + fQuitFind(true) { fEditor.StartWatching(this); } -UpdateLooper::~UpdateLooper() +EditorLooper::~EditorLooper() { fEditor.StopWatching(this); } void -UpdateLooper::MessageReceived(BMessage *message) +EditorLooper::MessageReceived(BMessage *message) { switch (message->what) { case kMsgPositionUpdate: @@ -874,6 +923,20 @@ UpdateLooper::MessageReceived(BMessage *message) break; } + case kMsgFind: + { + BMessenger progressMonitor; + message->FindMessenger("progress_monitor", &progressMonitor); + + off_t startAt = 0; + message->FindInt64("start", &startAt); + + ssize_t dataSize; + const uint8 *data; + if (message->FindData("data", B_RAW_TYPE, (const void **)&data, &dataSize) == B_OK) + Find(startAt, data, dataSize, progressMonitor); + } + default: BLooper::MessageReceived(message); break; @@ -881,12 +944,52 @@ UpdateLooper::MessageReceived(BMessage *message) } +void +EditorLooper::Find(off_t startAt, const uint8 *data, size_t dataSize, BMessenger progressMonitor) +{ + fQuitFind = false; + + BAutolock locker(fEditor); + + bigtime_t startTime = system_time(); + + off_t foundAt = fEditor.Find(startAt, data, dataSize, true, progressMonitor, &fQuitFind); + if (foundAt >= B_OK) { + fEditor.SetViewOffset(foundAt); + + // select the part in our target + BMessage message(kMsgSetSelection); + message.AddInt64("start", foundAt - fEditor.ViewOffset()); + message.AddInt64("end", foundAt + dataSize - 1 - fEditor.ViewOffset()); + fMessenger.SendMessage(&message); + } else if (foundAt == B_ENTRY_NOT_FOUND) { + if (system_time() > startTime + 8000000LL) { + // If the user had to wait more than 8 seconds for the result, + // we are trying to please him with a requester... + (new BAlert("DiskProbe request", + "Could not find pattern", "Ok", NULL, NULL, + B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(NULL); + } else + beep(); + } +} + + +void +EditorLooper::QuitFind() +{ + fQuitFind = true; + // this will cleanly stop the find process +} + + // #pragma mark - ProbeView::ProbeView(BRect rect, entry_ref *ref, const char *attribute, const BMessage *settings) : BView(rect, "probeView", B_FOLLOW_ALL, B_WILL_DRAW), - fPrintSettings(NULL) + fPrintSettings(NULL), + fLastSearch(NULL) { fEditor.SetTo(*ref, attribute); @@ -945,9 +1048,11 @@ ProbeView::UpdateSizeLimits() void ProbeView::DetachedFromWindow() { - if (fUpdateLooper->Lock()) - fUpdateLooper->Quit(); - fUpdateLooper = NULL; + fEditorLooper->QuitFind(); + + if (fEditorLooper->Lock()) + fEditorLooper->Quit(); + fEditorLooper = NULL; fEditor.StopWatching(this); fDataView->StopWatching(fHeaderView, kDataViewCursorPosition); @@ -1037,8 +1142,8 @@ ProbeView::AddPrintMenuItems(BMenu *menu, int32 index) void ProbeView::AttachedToWindow() { - fUpdateLooper = new UpdateLooper(fEditor.Ref().name, fEditor, BMessenger(fDataView)); - fUpdateLooper->Run(); + fEditorLooper = new EditorLooper(fEditor.Ref().name, fEditor, BMessenger(fDataView)); + fEditorLooper->Run(); fEditor.StartWatching(this); fDataView->StartWatching(fHeaderView, kDataViewCursorPosition); @@ -1086,8 +1191,12 @@ ProbeView::AttachedToWindow() menu->AddItem(item = new BMenuItem("Select All", new BMessage(B_SELECT_ALL), 'A', B_COMMAND_KEY)); item->SetTarget(fDataView); menu->AddSeparatorItem(); - menu->AddItem(new BMenuItem("Find" B_UTF8_ELLIPSIS, NULL, 'F', B_COMMAND_KEY)); - menu->AddItem(new BMenuItem("Find Again", NULL, 'G', B_COMMAND_KEY)); + menu->AddItem(item = new BMenuItem("Find" B_UTF8_ELLIPSIS, new BMessage(kMsgFindWindow), 'F', B_COMMAND_KEY)); + item->SetTarget(this); + menu->AddItem(fFindAgainMenuItem = new BMenuItem("Find Again", new BMessage(kMsgFind), + 'G', B_COMMAND_KEY)); + fFindAgainMenuItem->SetEnabled(false); + fFindAgainMenuItem->SetTarget(this); bar->AddItem(menu); // "Block" menu @@ -1216,7 +1325,7 @@ ProbeView::AttachedToWindow() void ProbeView::AllAttached() { - fHeaderView->SetTarget(fUpdateLooper); + fHeaderView->SetTarget(fEditorLooper); } @@ -1439,6 +1548,8 @@ ProbeView::Save() bool ProbeView::QuitRequested() { + fEditorLooper->QuitFind(); + if (!fEditor.IsModified()) return true; @@ -1548,6 +1659,49 @@ ProbeView::MessageReceived(BMessage *message) PageSetup(); break; + case kMsgFindWindow: + { + fEditorLooper->QuitFind(); + + // ToDo: open find window + message->AddData("data", B_RAW_TYPE, "test", 4); + message->what = kMsgFind; + //break; + } + + case kMsgFind: + { + const uint8 *data; + ssize_t size; + if (message->FindData("data", B_RAW_TYPE, (const void **)&data, &size) != B_OK) { + // search again for last pattern + BMessage *itemMessage = fFindAgainMenuItem->Message(); + if (itemMessage == NULL + || itemMessage->FindData("data", B_RAW_TYPE, (const void **)&data, &size) != B_OK) { + // this shouldn't ever happen, but well... + beep(); + break; + } + } else { + // remember the search pattern + fFindAgainMenuItem->SetMessage(new BMessage(*message)); + fFindAgainMenuItem->SetEnabled(true); + } + + int32 start, end; + fDataView->GetSelection(start, end); + + BMessage find(*message); + find.AddInt64("start", fHeaderView->Position() + start + 1); + find.AddMessenger("progress_monitor", BMessenger(fHeaderView)); + fEditorLooper->PostMessage(&find); + break; + } + + case kMsgStopFind: + fEditorLooper->QuitFind(); + break; + case B_NODE_MONITOR: { switch (message->FindInt32("opcode")) { diff --git a/src/apps/diskprobe/ProbeView.h b/src/apps/diskprobe/ProbeView.h index f742f2cea7..3760232f64 100644 --- a/src/apps/diskprobe/ProbeView.h +++ b/src/apps/diskprobe/ProbeView.h @@ -18,7 +18,7 @@ class BMenuItem; class HeaderView; class DataView; -class UpdateLooper; +class EditorLooper; class ProbeView : public BView { @@ -52,7 +52,7 @@ class ProbeView : public BView { status_t Save(); DataEditor fEditor; - UpdateLooper *fUpdateLooper; + EditorLooper *fEditorLooper; HeaderView *fHeaderView; DataView *fDataView; BScrollView *fScrollView; @@ -62,6 +62,10 @@ class ProbeView : public BView { BMenuItem *fSaveMenuItem; BMessage *fPrintSettings; BMenu *fBookmarkMenu; + + BMenuItem *fFindAgainMenuItem; + const uint8 *fLastSearch; + size_t fLastSearchSize; }; #endif /* PROBE_VIEW_H */