1 /* 2 ** Copyright 2004, Axel Dörfler, axeld@pinc-software.de. All rights reserved. 3 ** Distributed under the terms of the OpenBeOS 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 enum find_mode { 26 kAsciiMode, 27 kHexMode 28 }; 29 30 static const uint32 kMsgFindMode = 'FMde'; 31 static const uint32 kMsgStartFind = 'SFnd'; 32 33 34 class FindTextView : public BTextView { 35 public: 36 FindTextView(BRect frame, const char *name, BRect textRect, uint32 resizeMask); 37 38 virtual void MakeFocus(bool state); 39 virtual void TargetedByScrollView(BScrollView *view); 40 41 find_mode Mode() const { return fMode; } 42 status_t SetMode(find_mode mode); 43 44 void SetData(BMessage &message); 45 void GetData(BMessage &message); 46 47 virtual void KeyDown(const char *bytes, int32 numBytes); 48 49 protected: 50 virtual void InsertText(const char *text, int32 length, 51 int32 offset, const text_run_array *runs); 52 53 private: 54 void HexReformat(int32 oldCursor, int32 &newCursor); 55 status_t GetHexFromData(const uint8 *in, size_t inSize, char **_hex, size_t *_hexSize); 56 status_t GetDataFromHex(const char *text, size_t textLength, uint8 **_data, size_t *_dataSize); 57 58 BScrollView *fScrollView; 59 find_mode fMode; 60 }; 61 62 63 //--------------- 64 65 66 FindTextView::FindTextView(BRect frame, const char *name, BRect textRect, 67 uint32 resizeMask) 68 : BTextView(frame, name, textRect, resizeMask), 69 fScrollView(NULL), 70 fMode(kAsciiMode) 71 { 72 } 73 74 75 void 76 FindTextView::MakeFocus(bool state) 77 { 78 BTextView::MakeFocus(state); 79 80 if (fScrollView != NULL) 81 fScrollView->SetBorderHighlighted(state); 82 } 83 84 85 void 86 FindTextView::TargetedByScrollView(BScrollView *view) 87 { 88 BTextView::TargetedByScrollView(view); 89 fScrollView = view; 90 } 91 92 93 void 94 FindTextView::HexReformat(int32 oldCursor, int32 &newCursor) 95 { 96 const char *text = Text(); 97 int32 textLength = TextLength(); 98 char *insert = (char *)malloc(textLength * 2); 99 if (insert == NULL) 100 return; 101 102 newCursor = TextLength(); 103 int32 out = 0; 104 for (int32 i = 0; i < textLength; i++) { 105 if (i == oldCursor) { 106 // this is the end of the inserted text 107 newCursor = out; 108 } 109 110 char c = text[i]; 111 if (c >= 'A' && c <= 'F') 112 c += 'a' - 'A'; 113 if ((c >= 'a' && c <= 'f') || (c >= '0' && c <= '9')) 114 insert[out++] = c; 115 116 if ((out % 48) == 47) 117 insert[out++] = '\n'; 118 else if ((out % 3) == 2) 119 insert[out++] = ' '; 120 } 121 insert[out] = '\0'; 122 123 DeleteText(0, textLength); 124 125 // InsertText() does not work here, as we need the text 126 // to be reformatted as well (newlines, breaks, whatever). 127 // IOW the BTextView class is not very nicely done. 128 // BTextView::InsertText(insert, out, 0, NULL); 129 fMode = kAsciiMode; 130 Insert(0, insert, out); 131 fMode = kHexMode; 132 133 free(insert); 134 } 135 136 137 void 138 FindTextView::InsertText(const char *text, int32 length, int32 offset, 139 const text_run_array *runs) 140 { 141 if (fMode == kHexMode) { 142 if (offset > TextLength()) 143 offset = TextLength(); 144 145 BTextView::InsertText(text, length, offset, runs); 146 // lets add anything, and then start to filter out 147 // (since we have to reformat the whole text) 148 149 int32 start, end; 150 GetSelection(&start, &end); 151 152 int32 cursor; 153 HexReformat(offset, cursor); 154 155 if (length == 1 && start == offset) 156 Select(cursor + 1, cursor + 1); 157 } else 158 BTextView::InsertText(text, length, offset, runs); 159 } 160 161 162 void 163 FindTextView::KeyDown(const char *bytes, int32 numBytes) 164 { 165 if (fMode == kHexMode) { 166 // filter out invalid (for hex mode) characters 167 if (numBytes > 1) 168 return; 169 170 switch (bytes[0]) { 171 case B_RIGHT_ARROW: 172 case B_LEFT_ARROW: 173 case B_UP_ARROW: 174 case B_DOWN_ARROW: 175 case B_HOME: 176 case B_END: 177 case B_PAGE_UP: 178 case B_PAGE_DOWN: 179 break; 180 181 case B_BACKSPACE: 182 { 183 int32 start, end; 184 GetSelection(&start, &end); 185 186 if (bytes[0] == B_BACKSPACE) { 187 start--; 188 if (start < 0) 189 return; 190 } 191 192 if (Text()[start] == ' ') 193 BTextView::KeyDown(bytes, numBytes); 194 195 BTextView::KeyDown(bytes, numBytes); 196 197 GetSelection(&start, &end); 198 HexReformat(start, start); 199 Select(start, start); 200 return; 201 } 202 203 case B_DELETE: 204 { 205 int32 start, end; 206 GetSelection(&start, &end); 207 208 if (Text()[start] == ' ') 209 BTextView::KeyDown(bytes, numBytes); 210 211 BTextView::KeyDown(bytes, numBytes); 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 status_t 238 FindTextView::GetHexFromData(const uint8 *in, size_t inSize, char **_hex, size_t *_hexSize) 239 { 240 char *hex = (char *)malloc(inSize * 3 + 1); 241 if (hex == NULL) 242 return B_NO_MEMORY; 243 244 char *out = hex; 245 for (uint32 i = 0; i < inSize; i++) { 246 out += sprintf(out, "%02x", *(unsigned char *)(in + i)); 247 } 248 out[0] = '\0'; 249 250 *_hex = hex; 251 *_hexSize = out + 1 - hex; 252 return B_OK; 253 } 254 255 256 status_t 257 FindTextView::GetDataFromHex(const char *text, size_t textLength, uint8 **_data, size_t *_dataSize) 258 { 259 uint8 *data = (uint8 *)malloc(textLength); 260 if (data == NULL) 261 return B_NO_MEMORY; 262 263 size_t dataSize = 0; 264 uint8 hiByte = 0; 265 bool odd = false; 266 for (uint32 i = 0; i < textLength; i++) { 267 char c = text[i]; 268 int32 number; 269 if (c >= 'A' && c <= 'F') 270 number = c + 10 - 'A'; 271 else if (c >= 'a' && c <= 'f') 272 number = c + 10 - 'a'; 273 else if (c >= '0' && c <= '9') 274 number = c - '0'; 275 else 276 continue; 277 278 if (!odd) 279 hiByte = (number << 4) & 0xf0; 280 else 281 data[dataSize++] = hiByte | (number & 0x0f); 282 283 odd = !odd; 284 } 285 if (odd) 286 data[dataSize++] = hiByte; 287 288 *_data = data; 289 *_dataSize = dataSize; 290 return B_OK; 291 } 292 293 294 status_t 295 FindTextView::SetMode(find_mode mode) 296 { 297 if (fMode == mode) 298 return B_OK; 299 300 if (mode == kHexMode) { 301 // convert text to hex mode 302 303 char *hex; 304 size_t hexSize; 305 if (GetHexFromData((const uint8 *)Text(), TextLength(), &hex, &hexSize) < B_OK) 306 return B_NO_MEMORY; 307 308 fMode = mode; 309 310 SetText(hex, hexSize); 311 free(hex); 312 } else { 313 // convert hex to ascii 314 315 uint8 *data; 316 size_t dataSize; 317 if (GetDataFromHex(Text(), TextLength(), &data, &dataSize) < B_OK) 318 return B_NO_MEMORY; 319 320 fMode = mode; 321 322 SetText((const char *)data, dataSize); 323 free(data); 324 } 325 326 return B_OK; 327 } 328 329 330 void 331 FindTextView::SetData(BMessage &message) 332 { 333 const uint8 *data; 334 ssize_t dataSize; 335 if (message.FindData("data", B_RAW_TYPE, (const void **)&data, &dataSize) != B_OK) 336 return; 337 338 if (fMode == kHexMode) { 339 char *hex; 340 size_t hexSize; 341 if (GetHexFromData(data, dataSize, &hex, &hexSize) < B_OK) 342 return; 343 344 SetText(hex, hexSize); 345 free(hex); 346 } else 347 SetText((char *)data, dataSize); 348 } 349 350 351 void 352 FindTextView::GetData(BMessage &message) 353 { 354 if (fMode == kHexMode) { 355 // convert hex-text to real data 356 uint8 *data; 357 size_t dataSize; 358 if (GetDataFromHex(Text(), TextLength(), &data, &dataSize) != B_OK) 359 return; 360 361 message.AddData("data", B_RAW_TYPE, data, dataSize); 362 free(data); 363 } else 364 message.AddData("data", B_RAW_TYPE, Text(), TextLength()); 365 } 366 367 368 // #pragma mark - 369 370 371 FindWindow::FindWindow(BRect rect, BMessage &previous, BMessenger &target) 372 : BWindow(rect, "Find", B_TITLED_WINDOW, B_ASYNCHRONOUS_CONTROLS), 373 fTarget(target) 374 { 375 BView *view = new BView(Bounds(), "main", B_FOLLOW_ALL, 0); 376 view->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 377 AddChild(view); 378 379 int8 mode = kAsciiMode; 380 previous.FindInt8("find_mode", &mode); 381 382 // add the top widgets 383 384 fMenu = new BPopUpMenu("mode"); 385 BMessage *message; 386 BMenuItem *item; 387 fMenu->AddItem(item = new BMenuItem("Text", message = new BMessage(kMsgFindMode))); 388 message->AddInt8("mode", kAsciiMode); 389 if (mode == kAsciiMode) 390 item->SetMarked(true); 391 fMenu->AddItem(item = new BMenuItem("Hexadecimal", message = new BMessage(kMsgFindMode))); 392 message->AddInt8("mode", kHexMode); 393 if (mode == kHexMode) 394 item->SetMarked(true); 395 396 BRect rect = Bounds().InsetByCopy(5, 5); 397 BMenuField *menuField = new BMenuField(rect, B_EMPTY_STRING, 398 "Mode:", fMenu, B_FOLLOW_LEFT | B_FOLLOW_TOP); 399 menuField->SetDivider(menuField->StringWidth(menuField->Label()) + 8); 400 menuField->ResizeToPreferred(); 401 view->AddChild(menuField); 402 403 // add the bottom widgets 404 405 BButton *button = new BButton(rect, B_EMPTY_STRING, "Find", new BMessage(kMsgStartFind), 406 B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM); 407 button->MakeDefault(true); 408 button->ResizeToPreferred(); 409 button->MoveTo(rect.right - button->Bounds().Width(), 410 rect.bottom - button->Bounds().Height()); 411 view->AddChild(button); 412 413 fCaseCheckBox = new BCheckBox(rect, B_EMPTY_STRING, "Case sensitive", 414 NULL, B_FOLLOW_LEFT | B_FOLLOW_BOTTOM); 415 fCaseCheckBox->ResizeToPreferred(); 416 fCaseCheckBox->MoveTo(5, button->Frame().top); 417 bool caseSensitive; 418 if (previous.FindBool("case_sensitive", &caseSensitive) != B_OK) 419 caseSensitive = true; 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 be_app_messenger.SendMessage(kMsgFindWindowClosed); 498 return true; 499 } 500 501 502 void 503 FindWindow::SetTarget(BMessenger &target) 504 { 505 fTarget = target; 506 } 507 508