xref: /haiku/src/apps/diskprobe/FindWindow.cpp (revision 3634f142352af2428aed187781fc9d75075e9140)
1 /*
2  * Copyright 2004-2023, 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.
5  */
6 
7 
8 #include "FindWindow.h"
9 
10 #include <stdlib.h>
11 #include <stdio.h>
12 #include <string.h>
13 
14 #include <algorithm>
15 
16 #include <Application.h>
17 #include <Autolock.h>
18 #include <AutoLocker.h>
19 #include <Beep.h>
20 #include <Button.h>
21 #include <Catalog.h>
22 #include <CheckBox.h>
23 #include <Clipboard.h>
24 #include <LayoutBuilder.h>
25 #include <Locale.h>
26 #include <MenuField.h>
27 #include <MenuItem.h>
28 #include <Mime.h>
29 #include <PopUpMenu.h>
30 #include <ScrollView.h>
31 #include <TextView.h>
32 
33 #include "DataView.h"
34 #include "DiskProbe.h"
35 
36 
37 #undef B_TRANSLATION_CONTEXT
38 #define B_TRANSLATION_CONTEXT "FindWindow"
39 
40 static const uint32 kMsgFindMode = 'FMde';
41 static const uint32 kMsgStartFind = 'SFnd';
42 
43 
44 class FindTextView : public BTextView {
45 public:
46 							FindTextView(const char* name);
47 
48 	virtual void			MakeFocus(bool state);
49 	virtual void			TargetedByScrollView(BScrollView* view);
50 
51 			find_mode		Mode() const { return fMode; }
52 			status_t		SetMode(find_mode mode);
53 
54 			void			SetData(BMessage& message);
55 			void			GetData(BMessage& message);
56 
57 	virtual void			KeyDown(const char* bytes, int32 numBytes);
58 
59 	virtual bool			AcceptsPaste(BClipboard* clipboard);
60 	virtual void			Copy(BClipboard* clipboard);
61 	virtual void			Cut(BClipboard* clipboard);
62 	virtual void			Paste(BClipboard* clipboard);
63 
64 protected:
65 	virtual	void			InsertText(const char* text, int32 length,
66 								int32 offset, const text_run_array* runs);
67 
68 private:
69 			void			_HexReformat(int32 oldCursor, int32& newCursor);
70 			status_t		_GetHexFromData(const uint8* in, size_t inSize,
71 								char** _hex, size_t* _hexSize);
72 			status_t		_GetDataFromHex(const char* text, size_t textLength,
73 								uint8** _data, size_t* _dataSize);
74 
75 			BScrollView*	fScrollView;
76 			find_mode		fMode;
77 };
78 
79 
80 FindTextView::FindTextView(const char* name)
81 	:
82 	BTextView(name),
83 	fScrollView(NULL),
84 	fMode(kAsciiMode)
85 {
86 }
87 
88 
89 void
90 FindTextView::MakeFocus(bool state)
91 {
92 	BTextView::MakeFocus(state);
93 
94 	if (fScrollView != NULL)
95 		fScrollView->SetBorderHighlighted(state);
96 }
97 
98 
99 void
100 FindTextView::TargetedByScrollView(BScrollView* view)
101 {
102 	BTextView::TargetedByScrollView(view);
103 	fScrollView = view;
104 }
105 
106 
107 void
108 FindTextView::_HexReformat(int32 oldCursor, int32& newCursor)
109 {
110 	const char* text = Text();
111 	int32 textLength = TextLength();
112 	char* insert = (char*)malloc(textLength * 2);
113 	if (insert == NULL)
114 		return;
115 
116 	newCursor = TextLength();
117 	int32 out = 0;
118 	for (int32 i = 0; i < textLength; i++) {
119 		if (i == oldCursor) {
120 			// this is the end of the inserted text
121 			newCursor = out;
122 		}
123 
124 		char c = text[i];
125 		if (c >= 'A' && c <= 'F')
126 			c += 'a' - 'A';
127 		if ((c >= 'a' && c <= 'f') || (c >= '0' && c <= '9'))
128 			insert[out++] = c;
129 
130 		if ((out % 48) == 47)
131 			insert[out++] = '\n';
132 		else if ((out % 3) == 2)
133 			insert[out++] = ' ';
134 	}
135 	insert[out] = '\0';
136 
137 	DeleteText(0, textLength);
138 
139 	// InsertText() does not work here, as we need the text
140 	// to be reformatted as well (newlines, breaks, whatever).
141 	// IOW the BTextView class is not very nicely done.
142 	//	BTextView::InsertText(insert, out, 0, NULL);
143 	fMode = kAsciiMode;
144 	Insert(0, insert, out);
145 	fMode = kHexMode;
146 
147 	free(insert);
148 }
149 
150 
151 void
152 FindTextView::InsertText(const char* text, int32 length, int32 offset,
153 	const text_run_array* runs)
154 {
155 	if (fMode == kHexMode) {
156 		if (offset > TextLength())
157 			offset = TextLength();
158 
159 		BTextView::InsertText(text, length, offset, runs);
160 			// lets add anything, and then start to filter out
161 			// (since we have to reformat the whole text)
162 
163 		int32 start, end;
164 		GetSelection(&start, &end);
165 
166 		int32 cursor;
167 		_HexReformat(offset, cursor);
168 
169 		if (length == 1 && start == offset)
170 			Select(cursor + 1, cursor + 1);
171 	} else
172 		BTextView::InsertText(text, length, offset, runs);
173 }
174 
175 
176 void
177 FindTextView::KeyDown(const char* bytes, int32 numBytes)
178 {
179 	if (fMode == kHexMode) {
180 		// filter out invalid (for hex mode) characters
181 		if (numBytes > 1)
182 			return;
183 
184 		switch (bytes[0]) {
185 			case B_RIGHT_ARROW:
186 			case B_LEFT_ARROW:
187 			case B_UP_ARROW:
188 			case B_DOWN_ARROW:
189 			case B_HOME:
190 			case B_END:
191 			case B_PAGE_UP:
192 			case B_PAGE_DOWN:
193 				break;
194 
195 			case B_BACKSPACE:
196 			case B_DELETE:
197 			{
198 				int32 start, end;
199 				GetSelection(&start, &end);
200 
201 				if (bytes[0] == B_BACKSPACE && --start < 0) {
202 					if (end == 0)
203 						return;
204 					start = 0;
205 				}
206 
207 				if (ByteAt(start) == ' ')
208 					BTextView::KeyDown(bytes, numBytes);
209 
210 				BTextView::KeyDown(bytes, numBytes);
211 
212 				if (bytes[0] == B_BACKSPACE)
213 					GetSelection(&start, &end);
214 
215 				_HexReformat(start, start);
216 				Select(start, start);
217 				return;
218 			}
219 
220 			default:
221 			{
222 				if (!strchr("0123456789abcdefABCDEF", bytes[0]))
223 					return;
224 
225 				// the original KeyDown() has severe cursor setting
226 				// problems with our InsertText().
227 
228 				int32 start, end;
229 				GetSelection(&start, &end);
230 				InsertText(bytes, 1, start, NULL);
231 				return;
232 			}
233 		}
234 	}
235 	BTextView::KeyDown(bytes, numBytes);
236 }
237 
238 
239 bool
240 FindTextView::AcceptsPaste(BClipboard* clipboard)
241 {
242 	if (clipboard == NULL)
243 		return false;
244 
245 	AutoLocker<BClipboard> _(clipboard);
246 
247 	BMessage* clip = clipboard->Data();
248 	if (clip == NULL)
249 		return false;
250 
251 	if (clip->HasData(B_FILE_MIME_TYPE, B_MIME_TYPE)
252 		|| clip->HasData("text/plain", B_MIME_TYPE))
253 		return true;
254 
255 	return BTextView::AcceptsPaste(clipboard);
256 }
257 
258 
259 void
260 FindTextView::Copy(BClipboard* clipboard)
261 {
262 	if (fMode != kHexMode) {
263 		BTextView::Copy(clipboard);
264 		return;
265 	}
266 
267 	int32 start, end;
268 	GetSelection(&start, &end);
269 
270 	if (clipboard == NULL || start == end)
271 		return;
272 
273 	AutoLocker<BClipboard> _(clipboard);
274 
275 	BMessage* clip = clipboard->Data();
276 	if (clip == NULL)
277 		return;
278 
279 	// convert hex-text to real data
280 	uint8* data;
281 	size_t dataSize;
282 	if (_GetDataFromHex(Text() + start, end - start, &data, &dataSize)
283 			!= B_OK)
284 		return;
285 
286 	clip->AddData(B_FILE_MIME_TYPE, B_MIME_TYPE, data, dataSize);
287 
288 	if (is_valid_utf8(data, dataSize))
289 		clip->AddData("text/plain", B_MIME_TYPE, data, dataSize);
290 
291 	free(data);
292 }
293 
294 
295 void
296 FindTextView::Cut(BClipboard* clipboard)
297 {
298 	if (fMode != kHexMode) {
299 		BTextView::Cut(clipboard);
300 		return;
301 	}
302 
303 	int32 start, end;
304 	GetSelection(&start, &end);
305 	if (clipboard == NULL || start == end)
306 		return;
307 
308 	AutoLocker<BClipboard> _(clipboard);
309 
310 	BMessage* clip = clipboard->Data();
311 	if (clip == NULL)
312 		return;
313 
314 	Copy(clipboard);
315 	Clear();
316 }
317 
318 
319 void
320 FindTextView::Paste(BClipboard* clipboard)
321 {
322 	if (clipboard == NULL)
323 		return;
324 
325 	AutoLocker<BClipboard> _(clipboard);
326 
327 	BMessage* clip = clipboard->Data();
328 	if (clip == NULL)
329 		return;
330 
331 	const uint8* data;
332 	ssize_t dataSize;
333 	if (clip->FindData(B_FILE_MIME_TYPE, B_MIME_TYPE, (const void**)&data,
334 			&dataSize) == B_OK) {
335 		if (fMode == kHexMode) {
336 			char* hex;
337 			size_t hexSize;
338 			if (_GetHexFromData(data, dataSize, &hex, &hexSize) < B_OK)
339 				return;
340 
341 			Insert(hex, hexSize);
342 			free(hex);
343 		} else
344 			Insert((char*)data, dataSize);
345 		return;
346 	}
347 
348 	BTextView::Paste(clipboard);
349 }
350 
351 
352 status_t
353 FindTextView::_GetHexFromData(const uint8* in, size_t inSize, char** _hex,
354 	size_t* _hexSize)
355 {
356 	char* hex = (char*)malloc(inSize * 3 + 1);
357 	if (hex == NULL)
358 		return B_NO_MEMORY;
359 
360 	char* out = hex;
361 	for (uint32 i = 0; i < inSize; i++) {
362 		out += sprintf(out, "%02x", *(unsigned char*)(in + i));
363 	}
364 	out[0] = '\0';
365 
366 	*_hex = hex;
367 	*_hexSize = out + 1 - hex;
368 	return B_OK;
369 }
370 
371 
372 status_t
373 FindTextView::_GetDataFromHex(const char* text, size_t textLength, uint8** _data,
374 	size_t* _dataSize)
375 {
376 	uint8* data = (uint8*)malloc(textLength);
377 	if (data == NULL)
378 		return B_NO_MEMORY;
379 
380 	size_t dataSize = 0;
381 	uint8 hiByte = 0;
382 	bool odd = false;
383 	for (uint32 i = 0; i < textLength; i++) {
384 		char c = text[i];
385 		int32 number;
386 		if (c >= 'A' && c <= 'F')
387 			number = c + 10 - 'A';
388 		else if (c >= 'a' && c <= 'f')
389 			number = c + 10 - 'a';
390 		else if (c >= '0' && c <= '9')
391 			number = c - '0';
392 		else
393 			continue;
394 
395 		if (!odd)
396 			hiByte = (number << 4) & 0xf0;
397 		else
398 			data[dataSize++] = hiByte | (number & 0x0f);
399 
400 		odd = !odd;
401 	}
402 	if (odd)
403 		data[dataSize++] = hiByte;
404 
405 	*_data = data;
406 	*_dataSize = dataSize;
407 	return B_OK;
408 }
409 
410 
411 status_t
412 FindTextView::SetMode(find_mode mode)
413 {
414 	if (fMode == mode)
415 		return B_OK;
416 
417 	if (mode == kHexMode) {
418 		// convert text to hex mode
419 
420 		char* hex;
421 		size_t hexSize;
422 		if (_GetHexFromData((const uint8*)Text(), TextLength(), &hex, &hexSize)
423 				< B_OK)
424 			return B_NO_MEMORY;
425 
426 		fMode = mode;
427 
428 		SetText(hex, hexSize);
429 		free(hex);
430 	} else {
431 		// convert hex to ascii
432 
433 		uint8* data;
434 		size_t dataSize;
435 		if (_GetDataFromHex(Text(), TextLength(), &data, &dataSize) < B_OK)
436 			return B_NO_MEMORY;
437 
438 		fMode = mode;
439 
440 		SetText((const char*)data, dataSize);
441 		free(data);
442 	}
443 
444 	return B_OK;
445 }
446 
447 
448 void
449 FindTextView::SetData(BMessage& message)
450 {
451 	const uint8* data;
452 	ssize_t dataSize;
453 	if (message.FindData("data", B_RAW_TYPE,
454 			(const void**)&data, &dataSize) != B_OK)
455 		return;
456 
457 	if (fMode == kHexMode) {
458 		char* hex;
459 		size_t hexSize;
460 		if (_GetHexFromData(data, dataSize, &hex, &hexSize) < B_OK)
461 			return;
462 
463 		SetText(hex, hexSize);
464 		free(hex);
465 	} else
466 		SetText((char*)data, dataSize);
467 }
468 
469 
470 void
471 FindTextView::GetData(BMessage& message)
472 {
473 	if (fMode == kHexMode) {
474 		// convert hex-text to real data
475 		uint8* data;
476 		size_t dataSize;
477 		if (_GetDataFromHex(Text(), TextLength(), &data, &dataSize) != B_OK)
478 			return;
479 
480 		message.AddData("data", B_RAW_TYPE, data, dataSize);
481 		free(data);
482 	} else
483 		message.AddData("data", B_RAW_TYPE, Text(), TextLength());
484 }
485 
486 
487 //	#pragma mark -
488 
489 
490 FindWindow::FindWindow(BRect _rect, BMessage& previous, BMessenger& target,
491 		const BMessage* settings)
492 	:
493 	BWindow(_rect, B_TRANSLATE("Find"), B_TITLED_WINDOW,
494 		B_ASYNCHRONOUS_CONTROLS | B_CLOSE_ON_ESCAPE | B_AUTO_UPDATE_SIZE_LIMITS),
495 	fTarget(target)
496 {
497 	int8 mode = kAsciiMode;
498 	if (previous.FindInt8("find_mode", &mode) != B_OK && settings != NULL)
499 		settings->FindInt8("find_mode", &mode);
500 
501 	fMenu = new BPopUpMenu("mode");
502 	BMessage* message;
503 	BMenuItem* item;
504 	fMenu->AddItem(item = new BMenuItem(B_TRANSLATE("Text"),
505 		message = new BMessage(kMsgFindMode)));
506 	message->AddInt8("mode", kAsciiMode);
507 	if (mode == kAsciiMode)
508 		item->SetMarked(true);
509 	fMenu->AddItem(item = new BMenuItem(B_TRANSLATE_COMMENT("Hexadecimal",
510 		"A menu item, as short as possible, noun is recommended if it is "
511 		"shorter than adjective."), message = new BMessage(kMsgFindMode)));
512 	message->AddInt8("mode", kHexMode);
513 	if (mode == kHexMode)
514 		item->SetMarked(true);
515 
516 	BMenuField* menuField = new BMenuField(B_TRANSLATE("Mode:"), fMenu);
517 
518 	fTextView = new FindTextView(B_EMPTY_STRING);
519 	fTextView->SetWordWrap(true);
520 	fTextView->SetMode((find_mode)mode);
521 	fTextView->SetData(previous);
522 
523 	BScrollView* scrollView = new BScrollView("scroller", fTextView,
524 		B_WILL_DRAW | B_FRAME_EVENTS, false, false);
525 
526 	BButton* button = new BButton(B_TRANSLATE("Find"),
527 		new BMessage(kMsgStartFind));
528 	button->MakeDefault(true);
529 
530 	fCaseCheckBox = new BCheckBox(B_TRANSLATE("Case sensitive"), NULL);
531 	bool caseSensitive;
532 	if (previous.FindBool("case_sensitive", &caseSensitive) != B_OK
533 		&& (settings == NULL
534 			|| settings->FindBool("case_sensitive", &caseSensitive) != B_OK)) {
535 		caseSensitive = true;
536 	}
537 	fCaseCheckBox->SetValue(caseSensitive);
538 
539 	BLayoutBuilder::Group<>(this, B_VERTICAL)
540 		.SetInsets(B_USE_WINDOW_SPACING)
541 		.AddGroup(B_HORIZONTAL)
542 			.Add(menuField->CreateLabelLayoutItem())
543 			.Add(menuField->CreateMenuBarLayoutItem())
544 			.AddGlue()
545 			.End()
546 		.Add(scrollView)
547 		.AddGroup(B_HORIZONTAL)
548 			.Add(fCaseCheckBox)
549 			.AddGlue()
550 			.Add(button);
551 
552 	ResizeTo(std::max(Bounds().Width() / 2, 300.f),
553 		button->Frame().Height() * 3 + 30);
554 }
555 
556 
557 FindWindow::~FindWindow()
558 {
559 }
560 
561 
562 void
563 FindWindow::WindowActivated(bool active)
564 {
565 	fTextView->MakeFocus(active);
566 }
567 
568 
569 void
570 FindWindow::MessageReceived(BMessage* message)
571 {
572 	switch (message->what) {
573 		case kMsgFindMode:
574 		{
575 			int8 mode;
576 			if (message->FindInt8("mode", &mode) != B_OK)
577 				break;
578 
579 			if (fTextView->SetMode((find_mode)mode) != B_OK) {
580 				// activate other item
581 				fMenu->ItemAt(mode == kAsciiMode ? 1 : 0)->SetMarked(true);
582 				beep();
583 			}
584 			fTextView->MakeFocus(true);
585 			break;
586 		}
587 
588 		case kMsgStartFind:
589 		{
590 			BMessage find(kMsgFind);
591 			fTextView->GetData(find);
592 			find.AddBool("case_sensitive", fCaseCheckBox->Value() != 0);
593 			find.AddInt8("find_mode", fTextView->Mode());
594 			fTarget.SendMessage(&find);
595 
596 			PostMessage(B_QUIT_REQUESTED);
597 			break;
598 		}
599 
600 		default:
601 			BWindow::MessageReceived(message);
602 	}
603 }
604 
605 
606 bool
607 FindWindow::QuitRequested()
608 {
609 	// update the application's settings
610 	BMessage update(kMsgSettingsChanged);
611 	update.AddBool("case_sensitive", fCaseCheckBox->Value() != 0);
612 	update.AddInt8("find_mode", fTextView->Mode());
613 	be_app_messenger.SendMessage(&update);
614 
615 	be_app_messenger.SendMessage(kMsgFindWindowClosed);
616 	return true;
617 }
618 
619 
620 void
621 FindWindow::Show()
622 {
623 	fTextView->SelectAll();
624 	BWindow::Show();
625 }
626 
627 
628 void
629 FindWindow::SetTarget(BMessenger& target)
630 {
631 	fTarget = target;
632 }
633 
634