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