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