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