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
Message() const65 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
Settings()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
~Settings()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
Open(BFile * file,int32 mode)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
UpdateFrom(BMessage * message)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
DiskProbe()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
~DiskProbe()260 DiskProbe::~DiskProbe()
261 {
262 delete fFilePanel;
263 }
264
265
266 void
ReadyToRun()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
Probe(BEntry & entry,const char * attribute)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
RefsReceived(BMessage * message)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
ArgvReceived(int32 argc,char ** argv)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
MessageReceived(BMessage * message)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
QuitRequested()503 DiskProbe::QuitRequested()
504 {
505 return true;
506 }
507
508
509 // #pragma mark -
510
511
512 int
main(int argc,char ** argv)513 main(int argc, char** argv)
514 {
515 DiskProbe probe;
516
517 probe.Run();
518 return 0;
519 }
520