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