xref: /haiku/src/apps/diskprobe/DiskProbe.cpp (revision 830f67ef991407f287dbc1238aa5f5906d90c991)
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(&currentDirectory, 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