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