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