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