2 * Copyright 2004-2009, Axel Dörfler, axeld@pinc-software.de.
3 * Copyright 2009, Philippe St-Pierre, stpere@gmail.com
4 * Distributed under the terms of the MIT License.
8 #include "FindWindow.h"
14 #include <Application.h>
16 #include <AutoLocker.h>
21 #include <Clipboard.h>
23 #include <MenuField.h>
26 #include <PopUpMenu.h>
27 #include <ScrollView.h>
31 #include "DiskProbe.h"
34 #undef B_TRANSLATION_CONTEXT
35 #define B_TRANSLATION_CONTEXT "FindWindow"
37 static const uint32 kMsgFindMode
= 'FMde';
38 static const uint32 kMsgStartFind
= 'SFnd';
41 class FindTextView
: public BTextView
{
43 FindTextView(BRect frame
, const char* name
,
44 BRect textRect
, uint32 resizeMask
);
46 virtual void MakeFocus(bool state
);
47 virtual void TargetedByScrollView(BScrollView
* view
);
49 find_mode
Mode() const { return fMode
; }
50 status_t
SetMode(find_mode mode
);
52 void SetData(BMessage
& message
);
53 void GetData(BMessage
& message
);
55 virtual void KeyDown(const char* bytes
, int32 numBytes
);
57 virtual bool AcceptsPaste(BClipboard
* clipboard
);
58 virtual void Copy(BClipboard
* clipboard
);
59 virtual void Cut(BClipboard
* clipboard
);
60 virtual void Paste(BClipboard
* clipboard
);
63 virtual void InsertText(const char* text
, int32 length
,
64 int32 offset
, const text_run_array
* runs
);
67 void _HexReformat(int32 oldCursor
, int32
& newCursor
);
68 status_t
_GetHexFromData(const uint8
* in
, size_t inSize
,
69 char** _hex
, size_t* _hexSize
);
70 status_t
_GetDataFromHex(const char* text
, size_t textLength
,
71 uint8
** _data
, size_t* _dataSize
);
73 BScrollView
* fScrollView
;
78 FindTextView::FindTextView(BRect frame
, const char* name
, BRect textRect
,
80 : BTextView(frame
, name
, textRect
, resizeMask
),
88 FindTextView::MakeFocus(bool state
)
90 BTextView::MakeFocus(state
);
92 if (fScrollView
!= NULL
)
93 fScrollView
->SetBorderHighlighted(state
);
98 FindTextView::TargetedByScrollView(BScrollView
* view
)
100 BTextView::TargetedByScrollView(view
);
106 FindTextView::_HexReformat(int32 oldCursor
, int32
& newCursor
)
108 const char* text
= Text();
109 int32 textLength
= TextLength();
110 char* insert
= (char*)malloc(textLength
* 2);
114 newCursor
= TextLength();
116 for (int32 i
= 0; i
< textLength
; i
++) {
117 if (i
== oldCursor
) {
118 // this is the end of the inserted text
123 if (c
>= 'A' && c
<= 'F')
125 if ((c
>= 'a' && c
<= 'f') || (c
>= '0' && c
<= '9'))
128 if ((out
% 48) == 47)
129 insert
[out
++] = '\n';
130 else if ((out
% 3) == 2)
135 DeleteText(0, textLength
);
137 // InsertText() does not work here, as we need the text
138 // to be reformatted as well (newlines, breaks, whatever).
139 // IOW the BTextView class is not very nicely done.
140 // BTextView::InsertText(insert, out, 0, NULL);
142 Insert(0, insert
, out
);
150 FindTextView::InsertText(const char* text
, int32 length
, int32 offset
,
151 const text_run_array
* runs
)
153 if (fMode
== kHexMode
) {
154 if (offset
> TextLength())
155 offset
= TextLength();
157 BTextView::InsertText(text
, length
, offset
, runs
);
158 // lets add anything, and then start to filter out
159 // (since we have to reformat the whole text)
162 GetSelection(&start
, &end
);
165 _HexReformat(offset
, cursor
);
167 if (length
== 1 && start
== offset
)
168 Select(cursor
+ 1, cursor
+ 1);
170 BTextView::InsertText(text
, length
, offset
, runs
);
175 FindTextView::KeyDown(const char* bytes
, int32 numBytes
)
177 if (fMode
== kHexMode
) {
178 // filter out invalid (for hex mode) characters
197 GetSelection(&start
, &end
);
199 if (bytes
[0] == B_BACKSPACE
&& --start
< 0) {
205 if (ByteAt(start
) == ' ')
206 BTextView::KeyDown(bytes
, numBytes
);
208 BTextView::KeyDown(bytes
, numBytes
);
210 if (bytes
[0] == B_BACKSPACE
)
211 GetSelection(&start
, &end
);
213 _HexReformat(start
, start
);
214 Select(start
, start
);
220 if (!strchr("0123456789abcdefABCDEF", bytes
[0]))
223 // the original KeyDown() has severe cursor setting
224 // problems with our InsertText().
227 GetSelection(&start
, &end
);
228 InsertText(bytes
, 1, start
, NULL
);
233 BTextView::KeyDown(bytes
, numBytes
);
238 FindTextView::AcceptsPaste(BClipboard
* clipboard
)
240 if (clipboard
== NULL
)
243 AutoLocker
<BClipboard
> _(clipboard
);
245 BMessage
* clip
= clipboard
->Data();
249 if (clip
->HasData(B_FILE_MIME_TYPE
, B_MIME_TYPE
)
250 || clip
->HasData("text/plain", B_MIME_TYPE
))
253 return BTextView::AcceptsPaste(clipboard
);
258 FindTextView::Copy(BClipboard
* clipboard
)
260 if (fMode
!= kHexMode
) {
261 BTextView::Copy(clipboard
);
266 GetSelection(&start
, &end
);
268 if (clipboard
== NULL
|| start
== end
)
271 AutoLocker
<BClipboard
> _(clipboard
);
273 BMessage
* clip
= clipboard
->Data();
277 // convert hex-text to real data
280 if (_GetDataFromHex(Text() + start
, end
- start
, &data
, &dataSize
)
284 clip
->AddData(B_FILE_MIME_TYPE
, B_MIME_TYPE
, data
, dataSize
);
286 if (is_valid_utf8(data
, dataSize
))
287 clip
->AddData("text/plain", B_MIME_TYPE
, data
, dataSize
);
294 FindTextView::Cut(BClipboard
* clipboard
)
296 if (fMode
!= kHexMode
) {
297 BTextView::Cut(clipboard
);
302 GetSelection(&start
, &end
);
303 if (clipboard
== NULL
|| start
== end
)
306 AutoLocker
<BClipboard
> _(clipboard
);
308 BMessage
* clip
= clipboard
->Data();
318 FindTextView::Paste(BClipboard
* clipboard
)
320 if (clipboard
== NULL
)
323 AutoLocker
<BClipboard
> _(clipboard
);
325 BMessage
* clip
= clipboard
->Data();
331 if (clip
->FindData(B_FILE_MIME_TYPE
, B_MIME_TYPE
, (const void**)&data
,
332 &dataSize
) == B_OK
) {
333 if (fMode
== kHexMode
) {
336 if (_GetHexFromData(data
, dataSize
, &hex
, &hexSize
) < B_OK
)
339 Insert(hex
, hexSize
);
342 Insert((char*)data
, dataSize
);
346 BTextView::Paste(clipboard
);
351 FindTextView::_GetHexFromData(const uint8
* in
, size_t inSize
, char** _hex
,
354 char* hex
= (char*)malloc(inSize
* 3 + 1);
359 for (uint32 i
= 0; i
< inSize
; i
++) {
360 out
+= sprintf(out
, "%02x", *(unsigned char*)(in
+ i
));
365 *_hexSize
= out
+ 1 - hex
;
371 FindTextView::_GetDataFromHex(const char* text
, size_t textLength
, uint8
** _data
,
374 uint8
* data
= (uint8
*)malloc(textLength
);
381 for (uint32 i
= 0; i
< textLength
; i
++) {
384 if (c
>= 'A' && c
<= 'F')
385 number
= c
+ 10 - 'A';
386 else if (c
>= 'a' && c
<= 'f')
387 number
= c
+ 10 - 'a';
388 else if (c
>= '0' && c
<= '9')
394 hiByte
= (number
<< 4) & 0xf0;
396 data
[dataSize
++] = hiByte
| (number
& 0x0f);
401 data
[dataSize
++] = hiByte
;
404 *_dataSize
= dataSize
;
410 FindTextView::SetMode(find_mode mode
)
415 if (mode
== kHexMode
) {
416 // convert text to hex mode
420 if (_GetHexFromData((const uint8
*)Text(), TextLength(), &hex
, &hexSize
)
426 SetText(hex
, hexSize
);
429 // convert hex to ascii
433 if (_GetDataFromHex(Text(), TextLength(), &data
, &dataSize
) < B_OK
)
438 SetText((const char*)data
, dataSize
);
447 FindTextView::SetData(BMessage
& message
)
451 if (message
.FindData("data", B_RAW_TYPE
,
452 (const void**)&data
, &dataSize
) != B_OK
)
455 if (fMode
== kHexMode
) {
458 if (_GetHexFromData(data
, dataSize
, &hex
, &hexSize
) < B_OK
)
461 SetText(hex
, hexSize
);
464 SetText((char*)data
, dataSize
);
469 FindTextView::GetData(BMessage
& message
)
471 if (fMode
== kHexMode
) {
472 // convert hex-text to real data
475 if (_GetDataFromHex(Text(), TextLength(), &data
, &dataSize
) != B_OK
)
478 message
.AddData("data", B_RAW_TYPE
, data
, dataSize
);
481 message
.AddData("data", B_RAW_TYPE
, Text(), TextLength());
488 FindWindow::FindWindow(BRect _rect
, BMessage
& previous
, BMessenger
& target
,
489 const BMessage
* settings
)
490 : BWindow(_rect
, B_TRANSLATE("Find"), B_TITLED_WINDOW
,
491 B_ASYNCHRONOUS_CONTROLS
| B_CLOSE_ON_ESCAPE
),
494 BView
* view
= new BView(Bounds(), "main", B_FOLLOW_ALL
, 0);
495 view
->SetViewUIColor(B_PANEL_BACKGROUND_COLOR
);
498 int8 mode
= kAsciiMode
;
499 if (previous
.FindInt8("find_mode", &mode
) != B_OK
&& settings
!= NULL
)
500 settings
->FindInt8("find_mode", &mode
);
502 // add the top widgets
504 fMenu
= new BPopUpMenu("mode");
507 fMenu
->AddItem(item
= new BMenuItem(B_TRANSLATE("Text"),
508 message
= new BMessage(kMsgFindMode
)));
509 message
->AddInt8("mode", kAsciiMode
);
510 if (mode
== kAsciiMode
)
511 item
->SetMarked(true);
512 fMenu
->AddItem(item
= new BMenuItem(B_TRANSLATE_COMMENT("Hexadecimal",
513 "A menu item, as short as possible, noun is recommended if it is "
514 "shorter than adjective."), message
= new BMessage(kMsgFindMode
)));
515 message
->AddInt8("mode", kHexMode
);
516 if (mode
== kHexMode
)
517 item
->SetMarked(true);
519 BRect rect
= Bounds().InsetByCopy(5, 5);
520 BMenuField
* menuField
= new BMenuField(rect
, B_EMPTY_STRING
,
521 B_TRANSLATE("Mode:"), fMenu
, B_FOLLOW_LEFT
| B_FOLLOW_TOP
);
522 menuField
->SetDivider(menuField
->StringWidth(menuField
->Label()) + 8);
523 menuField
->ResizeToPreferred();
524 view
->AddChild(menuField
);
526 // add the bottom widgets
528 BButton
* button
= new BButton(rect
, B_EMPTY_STRING
, B_TRANSLATE("Find"),
529 new BMessage(kMsgStartFind
), B_FOLLOW_RIGHT
| B_FOLLOW_BOTTOM
);
530 button
->MakeDefault(true);
531 button
->ResizeToPreferred();
532 button
->MoveTo(rect
.right
- button
->Bounds().Width(),
533 rect
.bottom
- button
->Bounds().Height());
534 view
->AddChild(button
);
536 fCaseCheckBox
= new BCheckBox(rect
, B_EMPTY_STRING
, B_TRANSLATE("Case sensitive"),
537 NULL
, B_FOLLOW_LEFT
| B_FOLLOW_BOTTOM
);
538 fCaseCheckBox
->ResizeToPreferred();
539 fCaseCheckBox
->MoveTo(5, button
->Frame().top
);
541 if (previous
.FindBool("case_sensitive", &caseSensitive
) != B_OK
) {
543 || settings
->FindBool("case_sensitive", &caseSensitive
) != B_OK
)
544 caseSensitive
= true;
546 fCaseCheckBox
->SetValue(caseSensitive
);
547 view
->AddChild(fCaseCheckBox
);
549 // and now those inbetween
551 rect
.top
= menuField
->Frame().bottom
+ 5;
552 rect
.bottom
= fCaseCheckBox
->Frame().top
- 8;
554 fTextView
= new FindTextView(rect
, B_EMPTY_STRING
,
555 rect
.OffsetToCopy(B_ORIGIN
).InsetByCopy(3, 3), B_FOLLOW_ALL
);
556 fTextView
->SetWordWrap(true);
557 fTextView
->SetMode((find_mode
)mode
);
558 fTextView
->SetData(previous
);
560 BScrollView
* scrollView
= new BScrollView("scroller", fTextView
,
561 B_FOLLOW_ALL
, B_WILL_DRAW
, false, false);
562 view
->AddChild(scrollView
);
564 ResizeTo(290, button
->Frame().Height() * 3 + 30);
566 SetSizeLimits(fCaseCheckBox
->Bounds().Width() + button
->Bounds().Width()
567 + 20, 32768, button
->Frame().Height() * 3 + 10, 32768);
571 FindWindow::~FindWindow()
577 FindWindow::WindowActivated(bool active
)
579 fTextView
->MakeFocus(active
);
584 FindWindow::MessageReceived(BMessage
* message
)
586 switch (message
->what
) {
590 if (message
->FindInt8("mode", &mode
) != B_OK
)
593 if (fTextView
->SetMode((find_mode
)mode
) != B_OK
) {
594 // activate other item
595 fMenu
->ItemAt(mode
== kAsciiMode
? 1 : 0)->SetMarked(true);
598 fTextView
->MakeFocus(true);
604 BMessage
find(kMsgFind
);
605 fTextView
->GetData(find
);
606 find
.AddBool("case_sensitive", fCaseCheckBox
->Value() != 0);
607 find
.AddInt8("find_mode", fTextView
->Mode());
608 fTarget
.SendMessage(&find
);
610 PostMessage(B_QUIT_REQUESTED
);
615 BWindow::MessageReceived(message
);
621 FindWindow::QuitRequested()
623 // update the application's settings
624 BMessage
update(kMsgSettingsChanged
);
625 update
.AddBool("case_sensitive", fCaseCheckBox
->Value() != 0);
626 update
.AddInt8("find_mode", fTextView
->Mode());
627 be_app_messenger
.SendMessage(&update
);
629 be_app_messenger
.SendMessage(kMsgFindWindowClosed
);
637 fTextView
->SelectAll();
643 FindWindow::SetTarget(BMessenger
& target
)