1 /* 2 * Copyright 2004-2018, Axel Dörfler, axeld@pinc-software.de. 3 * All rights reserved. Distributed under the terms of the MIT license. 4 */ 5 6 7 #include "DiskProbe.h" 8 9 #include <stdio.h> 10 #include <string.h> 11 12 #include <AboutWindow.h> 13 #include <Alert.h> 14 #include <Application.h> 15 #include <Autolock.h> 16 #include <Catalog.h> 17 #include <Directory.h> 18 #include <Entry.h> 19 #include <FilePanel.h> 20 #include <FindDirectory.h> 21 #include <LayoutUtils.h> 22 #include <Locale.h> 23 #include <Path.h> 24 #include <Screen.h> 25 #include <TextView.h> 26 27 #include "DataEditor.h" 28 #include "DataView.h" 29 #include "FileWindow.h" 30 #include "AttributeWindow.h" 31 #include "OpenWindow.h" 32 #include "FindWindow.h" 33 34 35 #undef B_TRANSLATION_CONTEXT 36 #define B_TRANSLATION_CONTEXT "DiskProbe" 37 38 39 const char* kSignature = "application/x-vnd.Haiku-DiskProbe"; 40 41 static const uint32 kMsgDiskProbeSettings = 'DPst'; 42 static const uint32 kCascadeOffset = 20; 43 44 45 struct disk_probe_settings { 46 BRect window_frame; 47 int32 base_type; 48 int32 font_size; 49 int32 flags; 50 }; 51 52 53 enum disk_probe_flags { 54 kCaseSensitive = 0x01, 55 // this flag alone is R5 DiskProbe settings compatible 56 kHexFindMode = 0x02, 57 }; 58 59 60 class Settings { 61 public: 62 Settings(); 63 ~Settings(); 64 65 const BMessage& Message() const { return fMessage; } 66 void UpdateFrom(BMessage* message); 67 68 private: 69 status_t Open(BFile* file, int32 mode); 70 71 private: 72 BMessage fMessage; 73 bool fUpdated; 74 }; 75 76 77 class DiskProbe : public BApplication { 78 public: 79 DiskProbe(); 80 virtual ~DiskProbe(); 81 82 virtual void ReadyToRun(); 83 84 virtual void RefsReceived(BMessage* message); 85 virtual void ArgvReceived(int32 argc, char** argv); 86 virtual void MessageReceived(BMessage* message); 87 88 virtual bool QuitRequested(); 89 90 private: 91 status_t Probe(BEntry& entry, 92 const char* attribute = NULL); 93 94 private: 95 Settings fSettings; 96 BFilePanel* fFilePanel; 97 BWindow* fOpenWindow; 98 FindWindow* fFindWindow; 99 uint32 fWindowCount; 100 BRect fWindowFrame; 101 BMessenger fFindTarget; 102 }; 103 104 105 //----------------- 106 107 108 Settings::Settings() 109 : 110 fMessage(kMsgDiskProbeSettings), 111 fUpdated(false) 112 { 113 float fontSize = be_plain_font->Size(); 114 int32 windowWidth = DataView::WidthForFontSize(fontSize) + 20; 115 // TODO: make scrollbar width variable 116 117 BScreen screen; 118 fMessage.AddRect("window_frame", BLayoutUtils::AlignInFrame(screen.Frame(), 119 BSize(windowWidth, windowWidth), 120 BAlignment(B_ALIGN_HORIZONTAL_CENTER, B_ALIGN_VERTICAL_CENTER))); 121 fMessage.AddInt32("base_type", kHexBase); 122 fMessage.AddFloat("font_size", fontSize); 123 fMessage.AddBool("case_sensitive", true); 124 fMessage.AddInt8("find_mode", kAsciiMode); 125 126 BFile file; 127 if (Open(&file, B_READ_ONLY) != B_OK) 128 return; 129 130 // TODO: load/save settings as flattened BMessage - but not yet, 131 // since that will break compatibility with R5's DiskProbe 132 133 disk_probe_settings settings; 134 if (file.Read(&settings, sizeof(settings)) == sizeof(settings)) { 135 #if B_HOST_IS_BENDIAN 136 // settings are saved in little endian 137 settings.window_frame.left = B_LENDIAN_TO_HOST_FLOAT( 138 settings.window_frame.left); 139 settings.window_frame.top = B_LENDIAN_TO_HOST_FLOAT( 140 settings.window_frame.top); 141 settings.window_frame.right = B_LENDIAN_TO_HOST_FLOAT( 142 settings.window_frame.right); 143 settings.window_frame.bottom = B_LENDIAN_TO_HOST_FLOAT( 144 settings.window_frame.bottom); 145 #endif 146 // check if the window frame is on screen at all 147 BScreen screen; 148 if (screen.Frame().Contains(settings.window_frame.LeftTop()) 149 && settings.window_frame.Width() < screen.Frame().Width() 150 && settings.window_frame.Height() < screen.Frame().Height()) 151 fMessage.ReplaceRect("window_frame", settings.window_frame); 152 153 if (settings.base_type == kHexBase 154 || settings.base_type == kDecimalBase) 155 fMessage.ReplaceInt32("base_type", 156 B_LENDIAN_TO_HOST_INT32(settings.base_type)); 157 if (settings.font_size >= 0 && settings.font_size <= 72) 158 fMessage.ReplaceFloat("font_size", 159 float(B_LENDIAN_TO_HOST_INT32(settings.font_size))); 160 161 fMessage.ReplaceBool("case_sensitive", 162 settings.flags & kCaseSensitive); 163 fMessage.ReplaceInt8("find_mode", 164 settings.flags & kHexFindMode ? kHexMode : kAsciiMode); 165 } 166 } 167 168 169 Settings::~Settings() 170 { 171 // only save the settings if something has changed 172 if (!fUpdated) 173 return; 174 175 BFile file; 176 if (Open(&file, B_CREATE_FILE | B_WRITE_ONLY) != B_OK) 177 return; 178 179 disk_probe_settings settings; 180 181 settings.window_frame = fMessage.FindRect("window_frame"); 182 #if B_HOST_IS_BENDIAN 183 // settings are saved in little endian 184 settings.window_frame.left = B_HOST_TO_LENDIAN_FLOAT( 185 settings.window_frame.left); 186 settings.window_frame.top = B_HOST_TO_LENDIAN_FLOAT( 187 settings.window_frame.top); 188 settings.window_frame.right = B_HOST_TO_LENDIAN_FLOAT( 189 settings.window_frame.right); 190 settings.window_frame.bottom = B_HOST_TO_LENDIAN_FLOAT( 191 settings.window_frame.bottom); 192 #endif 193 194 settings.base_type = B_HOST_TO_LENDIAN_INT32( 195 fMessage.FindInt32("base_type")); 196 settings.font_size = B_HOST_TO_LENDIAN_INT32( 197 int32(fMessage.FindFloat("font_size") + 0.5f)); 198 settings.flags = B_HOST_TO_LENDIAN_INT32( 199 (fMessage.FindBool("case_sensitive") ? kCaseSensitive : 0) 200 | (fMessage.FindInt8("find_mode") == kHexMode ? kHexFindMode : 0)); 201 202 file.Write(&settings, sizeof(settings)); 203 } 204 205 206 status_t 207 Settings::Open(BFile* file, int32 mode) 208 { 209 BPath path; 210 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK) 211 return B_ERROR; 212 213 path.Append("DiskProbe_data"); 214 215 return file->SetTo(path.Path(), mode); 216 } 217 218 219 void 220 Settings::UpdateFrom(BMessage* message) 221 { 222 BRect frame; 223 if (message->FindRect("window_frame", &frame) == B_OK) 224 fMessage.ReplaceRect("window_frame", frame); 225 226 int32 baseType; 227 if (message->FindInt32("base_type", &baseType) == B_OK) 228 fMessage.ReplaceInt32("base_type", baseType); 229 230 float fontSize; 231 if (message->FindFloat("font_size", &fontSize) == B_OK) 232 fMessage.ReplaceFloat("font_size", fontSize); 233 234 bool caseSensitive; 235 if (message->FindBool("case_sensitive", &caseSensitive) == B_OK) 236 fMessage.ReplaceBool("case_sensitive", caseSensitive); 237 238 int8 findMode; 239 if (message->FindInt8("find_mode", &findMode) == B_OK) 240 fMessage.ReplaceInt8("find_mode", findMode); 241 242 fUpdated = true; 243 } 244 245 246 // #pragma mark - 247 248 249 DiskProbe::DiskProbe() 250 : BApplication(kSignature), 251 fOpenWindow(NULL), 252 fFindWindow(NULL), 253 fWindowCount(0) 254 { 255 fFilePanel = new BFilePanel(); 256 fWindowFrame = fSettings.Message().FindRect("window_frame"); 257 } 258 259 260 DiskProbe::~DiskProbe() 261 { 262 delete fFilePanel; 263 } 264 265 266 void 267 DiskProbe::ReadyToRun() 268 { 269 // are there already windows open? 270 if (CountWindows() != 1) 271 return; 272 273 // if not, ask the user to open a file 274 PostMessage(kMsgOpenOpenWindow); 275 } 276 277 278 /*! Opens a window containing the file pointed to by the entry_ref. 279 This function will fail if that file doesn't exist or could not 280 be opened. 281 It will check if there already is a window that probes the 282 file in question and will activate it in that case. 283 This function must be called with the application looper locked. 284 */ 285 status_t 286 DiskProbe::Probe(BEntry& entry, const char* attribute) 287 { 288 entry_ref ref; 289 status_t status = entry.GetRef(&ref); 290 if (status < B_OK) 291 return status; 292 293 ProbeWindow* lastWindow(NULL); 294 295 // Do we already have that window open? 296 for (int32 i = CountWindows(); i-- > 0; ) { 297 ProbeWindow* window = dynamic_cast<ProbeWindow*>(WindowAt(i)); 298 if (window == NULL) 299 continue; 300 301 if (window->Contains(ref, attribute)) { 302 window->Activate(true); 303 return B_OK; 304 } 305 if (lastWindow == NULL) 306 lastWindow = window; 307 } 308 309 // Does the file really exist? 310 if (!entry.Exists()) 311 return B_ENTRY_NOT_FOUND; 312 313 entry.GetRef(&ref); 314 315 // cascade window 316 BRect rect; 317 if (lastWindow != NULL) 318 rect = lastWindow->Frame(); 319 else 320 rect = fWindowFrame; 321 322 rect.OffsetBy(kCascadeOffset, kCascadeOffset); 323 324 BWindow* window; 325 if (attribute != NULL) { 326 window = new AttributeWindow(rect, &ref, attribute, 327 &fSettings.Message()); 328 } else 329 window = new FileWindow(rect, &ref, &fSettings.Message()); 330 331 window->Show(); 332 333 // Adjust the cascading... we can only do this after the window was created 334 // to adjust to the real size. 335 rect.right = window->Frame().right; 336 rect.bottom = window->Frame().bottom; 337 338 BScreen screen; 339 BRect screenBorder = screen.Frame(); 340 341 float left = rect.left; 342 if (left + rect.Width() > screenBorder.right) 343 left = 7; 344 345 float top = rect.top; 346 if (top + rect.Height() > screenBorder.bottom) 347 top = 26; 348 349 rect.OffsetTo(BPoint(left, top)); 350 window->MoveTo(BPoint(left, top)); 351 352 fWindowCount++; 353 354 return B_OK; 355 } 356 357 358 void 359 DiskProbe::RefsReceived(BMessage* message) 360 { 361 bool traverseLinks = (modifiers() & B_SHIFT_KEY) == 0; 362 363 int32 index = 0; 364 entry_ref ref; 365 while (message->FindRef("refs", index++, &ref) == B_OK) { 366 const char* attribute = NULL; 367 if (message->FindString("attributes", index - 1, &attribute) == B_OK) 368 traverseLinks = false; 369 370 BEntry entry; 371 status_t status = entry.SetTo(&ref, traverseLinks); 372 373 if (status == B_OK) 374 status = Probe(entry, attribute); 375 376 if (status != B_OK) { 377 char buffer[1024]; 378 snprintf(buffer, sizeof(buffer), 379 B_TRANSLATE_COMMENT("Could not open \"%s\":\n" 380 "%s", "Opening of entry reference buffer for a DiskProbe " 381 "request Alert message. The name of entry reference and " 382 "error message is shown."), 383 ref.name, strerror(status)); 384 385 BAlert* alert = new BAlert(B_TRANSLATE("DiskProbe request"), 386 buffer, B_TRANSLATE("OK"), NULL, NULL, 387 B_WIDTH_AS_USUAL, B_STOP_ALERT); 388 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 389 alert->Go(); 390 } 391 } 392 } 393 394 395 void 396 DiskProbe::ArgvReceived(int32 argc, char** argv) 397 { 398 BMessage* message = CurrentMessage(); 399 400 BDirectory currentDirectory; 401 if (message) 402 currentDirectory.SetTo(message->FindString("cwd")); 403 404 BMessage refs; 405 406 for (int i = 1 ; i < argc ; i++) { 407 BPath path; 408 if (argv[i][0] == '/') 409 path.SetTo(argv[i]); 410 else 411 path.SetTo(¤tDirectory, argv[i]); 412 413 status_t status; 414 entry_ref ref; 415 BEntry entry; 416 417 if ((status = entry.SetTo(path.Path(), false)) != B_OK 418 || (status = entry.GetRef(&ref)) != B_OK) { 419 fprintf(stderr, B_TRANSLATE("Could not open file \"%s\": %s\n"), 420 path.Path(), strerror(status)); 421 continue; 422 } 423 424 refs.AddRef("refs", &ref); 425 } 426 427 RefsReceived(&refs); 428 } 429 430 431 void 432 DiskProbe::MessageReceived(BMessage* message) 433 { 434 switch (message->what) { 435 case kMsgOpenOpenWindow: 436 if (fOpenWindow == NULL) { 437 fOpenWindow = new OpenWindow(); 438 fOpenWindow->Show(); 439 fWindowCount++; 440 } else 441 fOpenWindow->Activate(true); 442 break; 443 444 case kMsgOpenWindowClosed: 445 fOpenWindow = NULL; 446 // supposed to fall through 447 case kMsgWindowClosed: 448 if (--fWindowCount == 0 && !fFilePanel->IsShowing()) 449 PostMessage(B_QUIT_REQUESTED); 450 break; 451 452 case kMsgSettingsChanged: 453 fSettings.UpdateFrom(message); 454 break; 455 456 case kMsgFindWindowClosed: 457 fFindWindow = NULL; 458 break; 459 case kMsgFindTarget: 460 { 461 BMessenger target; 462 if (message->FindMessenger("target", &target) != B_OK) 463 break; 464 465 if (fFindWindow != NULL && fFindWindow->Lock()) { 466 fFindWindow->SetTarget(target); 467 fFindWindow->Unlock(); 468 } 469 break; 470 } 471 case kMsgOpenFindWindow: 472 { 473 BMessenger target; 474 if (message->FindMessenger("target", &target) != B_OK) 475 break; 476 477 if (fFindWindow == NULL) { 478 // open it! 479 fFindWindow = new FindWindow(fWindowFrame.OffsetByCopy(80, 80), 480 *message, target, &fSettings.Message()); 481 fFindWindow->Show(); 482 } else 483 fFindWindow->Activate(); 484 break; 485 } 486 487 case kMsgOpenFilePanel: 488 fFilePanel->Show(); 489 break; 490 case B_CANCEL: 491 if (fWindowCount == 0) 492 PostMessage(B_QUIT_REQUESTED); 493 break; 494 495 default: 496 BApplication::MessageReceived(message); 497 break; 498 } 499 } 500 501 502 bool 503 DiskProbe::QuitRequested() 504 { 505 return true; 506 } 507 508 509 // #pragma mark - 510 511 512 int 513 main(int argc, char** argv) 514 { 515 DiskProbe probe; 516 517 probe.Run(); 518 return 0; 519 } 520