xref: /haiku/src/apps/cortex/RouteApp/RouteApp.cpp (revision 9ecf9d1c1d4888d341a6eac72112c72d1ae3a4cb)
1 // RouteApp.cpp
2 // e.moon 14may99
3 
4 #include "RouteApp.h"
5 #include "RouteWindow.h"
6 #include "DormantNodeWindow.h"
7 #include "MediaRoutingView.h"
8 #include "MediaNodePanel.h"
9 
10 #include "RouteAppNodeManager.h"
11 #include "NodeRef.h"
12 
13 #include "TipManager.h"
14 
15 #include "AddOnHost.h"
16 
17 #include "route_app_io.h"
18 #include "XML.h"
19 #include "MessageIO.h"
20 #include "NodeSetIOContext.h"
21 
22 #include <Debug.h>
23 #include <OS.h>
24 #include <Roster.h>
25 #include <Directory.h>
26 #include <FindDirectory.h>
27 #include <NodeInfo.h>
28 #include <Path.h>
29 #include <Entry.h>
30 
31 extern "C" void SetNewLeakChecking(bool);
32 extern "C" void SetMallocLeakChecking(bool);
33 
34 using namespace std;
35 
36 __USE_CORTEX_NAMESPACE
37 
38 const char* const		RouteApp::s_settingsDirectory = "Cortex";
39 const char* const		RouteApp::s_settingsFile = "cortex_settings";
40 
41 const char* const		RouteApp::s_appSignature = "application/x-vnd.Cortex.Route";
42 
43 BMimeType						RouteApp::s_nodeSetType("text/x-vnd.Cortex.NodeSet");
44 
45 const char* const		RouteApp::s_rootElement = "cortex_settings";
46 const char* const		RouteApp::s_mediaRoutingViewElement = "MediaRoutingView";
47 const char* const		RouteApp::s_routeWindowElement = "RouteWindow";
48 
49 // -------------------------------------------------------- //
50 // ctor/dtor
51 // -------------------------------------------------------- //
52 
53 RouteApp::~RouteApp() {
54 //	PRINT((
55 //		"RouteApp::~RouteApp()\n"));
56 
57 	ASSERT(manager);
58 	thread_id id = manager->Thread();
59 	manager->release();
60 
61 //	PRINT((
62 //		"- waiting for manager to die\n"));
63 	if(id >= B_OK) {
64 		status_t err;
65 		while(wait_for_thread(id, &err) == B_INTERRUPTED) {
66 			PRINT((" * RouteApp::~RouteApp(): B_INTERRUPTED\n"));
67 		}
68 	}
69 //	PRINT((
70 //		"- RouteApp done.\n"));
71 
72 	// [e.moon 6nov99] kill off the AddOnHost app, if any
73 	AddOnHost::Kill();
74 
75 	if(m_settingsDocType)
76 		delete m_settingsDocType;
77 
78 	if(m_nodeSetDocType)
79 		delete m_nodeSetDocType;
80 }
81 
82 RouteApp::RouteApp() :
83 	BApplication(s_appSignature),
84 	manager(new RouteAppNodeManager(true)),
85 	routeWindow(0),
86 	m_settingsDocType(_createSettingsDocType()),
87 	m_nodeSetDocType(_createNodeSetDocType()),
88 	m_openPanel(B_OPEN_PANEL),
89 	m_savePanel(B_SAVE_PANEL) {
90 
91 	// register MIME type(s)
92 	_InitMimeTypes();
93 
94 	// create the window hierarchy
95 	RouteWindow*& r = const_cast<RouteWindow*&>(routeWindow);
96 	r = new RouteWindow(manager);
97 
98 	// restore settings
99 	_readSettings();
100 
101 	// fit windows to screen
102 	routeWindow->constrainToScreen();
103 
104 	// show main window & palettes
105 	routeWindow->Show();
106 }
107 
108 bool RouteApp::QuitRequested() {
109 
110 	// [e.moon 20oct99] make sure the main window is dead before quitting
111 
112 	// store window positions & other settings
113 	app_info ai;
114 	status_t err = GetAppInfo(&ai);
115 	ASSERT(err == B_OK);
116 
117 	BFile appFile(&ai.ref, B_READ_WRITE);
118 
119 	// write settings file
120 	_writeSettings();
121 
122 	routeWindow->_closePalettes();
123 	routeWindow->Lock();
124 	routeWindow->Quit();
125 	RouteWindow*& r = const_cast<RouteWindow*&>(routeWindow);
126 	r = 0;
127 
128 	// clean up the TipManager [e.moon 19oct99]
129 	TipManager::QuitInstance();
130 
131 	return true;
132 }
133 
134 // -------------------------------------------------------- //
135 // *** BHandler
136 // -------------------------------------------------------- //
137 
138 void RouteApp::MessageReceived(
139 	BMessage*									message) {
140 
141 	status_t err;
142 
143 	entry_ref ref;
144 	const char* name;
145 
146 	switch(message->what) {
147 
148 		case M_SHOW_OPEN_PANEL:
149 			m_openPanel.Show();
150 			break;
151 
152 		case M_SHOW_SAVE_PANEL:
153 			m_savePanel.Show();
154 			break;
155 
156 		case B_SAVE_REQUESTED: {
157 			err = message->FindRef("directory", &ref);
158 			if(err < B_OK)
159 				break;
160 			err = message->FindString("name", &name);
161 			if(err < B_OK)
162 				break;
163 
164 			_writeSelectedNodeSet(&ref, name);
165 
166 			m_savePanel.GetPanelDirectory(&ref);
167 			BEntry e(&ref);
168 			m_lastIODir.SetTo(&e);
169 			break;
170 		}
171 
172 		default:
173 			_inherited::MessageReceived(message);
174 	}
175 }
176 
177 // -------------------------------------------------------- //
178 // *** BApplication
179 // -------------------------------------------------------- //
180 
181 void RouteApp::RefsReceived(
182 	BMessage*									message) {
183 
184 	PRINT(("### RefsReceived\n"));
185 
186 	status_t err;
187 
188 	entry_ref ref;
189 
190 	for(int32 n = 0; ; ++n) {
191 		err = message->FindRef("refs", n, &ref);
192 		if(err < B_OK)
193 			break;
194 
195 		_readNodeSet(&ref);
196 
197 		m_openPanel.GetPanelDirectory(&ref);
198 		BEntry e(&ref);
199 		m_lastIODir.SetTo(&e);
200 	}
201 }
202 
203 // -------------------------------------------------------- //
204 // *** IPersistent
205 // -------------------------------------------------------- //
206 
207 // EXPORT
208 
209 void RouteApp::xmlExportBegin(
210 	ExportContext&						context) const {
211 	context.beginElement(s_rootElement);
212 }
213 
214 void RouteApp::xmlExportAttributes(
215 	ExportContext&						context) const {} //nyi: write version info +++++
216 
217 // +++++
218 void RouteApp::xmlExportContent(
219 	ExportContext&						context) const {
220 
221 	status_t err;
222 	context.beginContent();
223 
224 	// export app settings
225 	{
226 		BMessage m;
227 		exportState(&m);
228 		MessageIO io(&m);
229 		err = context.writeObject(&io);
230 		ASSERT(err == B_OK);
231 	}
232 
233 	if(routeWindow) {
234 		// export main routing window (frame/palette) settings
235 		context.beginElement(s_routeWindowElement);
236 		context.beginContent();
237 		BMessage m;
238 		if (routeWindow->Lock()) {
239 			routeWindow->exportState(&m);
240 			routeWindow->Unlock();
241 		}
242 		MessageIO io(&m);
243 		context.writeObject(&io);
244 		context.endElement();
245 
246 		// export routing view (content) settings
247 		m.MakeEmpty();
248 		ASSERT(routeWindow->m_routingView);
249 		context.beginElement(s_mediaRoutingViewElement);
250 		context.beginContent();
251 		routeWindow->m_routingView->exportState(&m);
252 		context.writeObject(&io);
253 		context.endElement();
254 	}
255 }
256 
257 void RouteApp::xmlExportEnd(
258 	ExportContext&						context) const {
259 	context.endElement();
260 }
261 
262 // IMPORT
263 
264 void RouteApp::xmlImportBegin(
265 	ImportContext&						context) {
266 
267 	m_readState = _READ_ROOT;
268 }
269 
270 void RouteApp::xmlImportAttribute(
271 	const char*								key,
272 	const char*								value,
273 	ImportContext&						context) {} //nyi
274 
275 void RouteApp::xmlImportContent(
276 	const char*								data,
277 	uint32										length,
278 	ImportContext&						context) {} //nyi
279 
280 void RouteApp::xmlImportChild(
281 	IPersistent*							child,
282 	ImportContext&						context) {
283 
284 	MessageIO* io = dynamic_cast<MessageIO*>(child);
285 	if(io) {
286 		ASSERT(io->message());
287 //		PRINT(("* RouteApp::xmlImportChild() [flat message]:\n"));
288 //		io->message()->PrintToStream();
289 
290 		switch(m_readState) {
291 			case _READ_ROOT:
292 				importState(io->message());
293 				break;
294 
295 			case _READ_ROUTE_WINDOW:
296 				ASSERT(routeWindow);
297 				routeWindow->importState(io->message());
298 				break;
299 
300 			case _READ_MEDIA_ROUTING_VIEW:
301 				ASSERT(routeWindow);
302 				ASSERT(routeWindow->m_routingView);
303 				routeWindow->m_routingView->importState(io->message());
304 				break;
305 
306 			default:
307 				PRINT(("! RouteApp::xmlImportChild(): unimplemented target\n"));
308 				break;
309 		}
310 	}
311 }
312 
313 void RouteApp::xmlImportComplete(
314 	ImportContext&						context) {} //nyi
315 
316 void RouteApp::xmlImportChildBegin(
317 	const char*								name,
318 	ImportContext&						context) {
319 
320 	if(m_readState != _READ_ROOT) {
321 		context.reportError("RouteApp import: invalid nested element");
322 		return;
323 	}
324 
325 	if(!strcmp(name, s_routeWindowElement)) {
326 		m_readState = _READ_ROUTE_WINDOW;
327 	}
328 	else if(!strcmp(name, s_mediaRoutingViewElement)) {
329 		m_readState = _READ_MEDIA_ROUTING_VIEW;
330 	}
331 	else {
332 		context.reportError("RouteApp import: unknown child element");
333 	}
334 }
335 
336 void RouteApp::xmlImportChildComplete(
337 	const char*								name,
338 	ImportContext&						context) {
339 
340 	if(m_readState == _READ_ROOT) {
341 		context.reportError("RouteApp import: garbled state");
342 		return;
343 	}
344 	m_readState = _READ_ROOT;
345 }
346 
347 // -------------------------------------------------------- //
348 // *** IStateArchivable
349 // -------------------------------------------------------- //
350 
351 status_t RouteApp::importState(
352 	const BMessage*						archive) {
353 
354 	const char* last;
355 	if(archive->FindString("lastDir", &last) == B_OK) {
356 		m_lastIODir.SetTo(last);
357 		m_openPanel.SetPanelDirectory(last);
358 		m_savePanel.SetPanelDirectory(last);
359 	}
360 
361 	return B_OK;
362 }
363 
364 status_t RouteApp::exportState(
365 	BMessage*									archive) const {
366 
367 	if(m_lastIODir.InitCheck() == B_OK)
368 		archive->AddString("lastDir", m_lastIODir.Path());
369 
370 	return B_OK;
371 }
372 
373 // -------------------------------------------------------- //
374 // implementation
375 // -------------------------------------------------------- //
376 
377 XML::DocumentType* RouteApp::_createSettingsDocType() {
378 
379 	XML::DocumentType* docType = new XML::DocumentType(
380 		s_rootElement);
381 	MessageIO::AddTo(docType);
382 
383 	return docType;
384 }
385 
386 XML::DocumentType* RouteApp::_createNodeSetDocType() {
387 
388 	XML::DocumentType* docType = new XML::DocumentType(
389 		_NODE_SET_ELEMENT);
390 	RouteAppNodeManager::AddTo(docType);
391 
392 	return docType;
393 }
394 
395 status_t RouteApp::_readSettings() {
396 
397 	// figure path
398 	BPath path;
399 	status_t err = find_directory(
400 		B_USER_SETTINGS_DIRECTORY,
401 		&path);
402 	ASSERT(err == B_OK);
403 
404 	path.Append(s_settingsDirectory);
405 	BEntry entry(path.Path());
406 	if(!entry.Exists())
407 		return B_ENTRY_NOT_FOUND;
408 
409 	path.Append(s_settingsFile);
410 	entry.SetTo(path.Path());
411 	if(!entry.Exists())
412 		return B_ENTRY_NOT_FOUND;
413 
414 	// open the settings file
415 	BFile file(&entry, B_READ_ONLY);
416 	if(file.InitCheck() != B_OK)
417 		return file.InitCheck();
418 
419 	// read it:
420 	list<BString> errors;
421 	err = XML::Read(
422 		&file,
423 		this,
424 		m_settingsDocType,
425 		&errors);
426 
427 	if(errors.size()) {
428 		fputs("!!! RouteApp::_readSettings():", stderr);
429 		for(list<BString>::iterator it = errors.begin();
430 			it != errors.end(); ++it)
431 			fputs((*it).String(), stderr);
432 	}
433 	return err;
434 }
435 
436 status_t RouteApp::_writeSettings() {
437 	// figure path, creating settings folder if necessary
438 	BPath path;
439 	status_t err = find_directory(
440 		B_USER_SETTINGS_DIRECTORY,
441 		&path);
442 	ASSERT(err == B_OK);
443 
444 	BDirectory baseDirectory, settingsDirectory;
445 
446 	err = baseDirectory.SetTo(path.Path());
447 	if(err < B_OK)
448 		return err;
449 
450 	path.Append(s_settingsDirectory);
451 
452 	BEntry folderEntry(path.Path());
453 	if(!folderEntry.Exists()) {
454 		// create folder
455 		err = baseDirectory.CreateDirectory(s_settingsDirectory, &settingsDirectory);
456 		ASSERT(err == B_OK);
457 	}
458 	else
459 		settingsDirectory.SetTo(&folderEntry);
460 
461 	// open/clobber file
462 	BFile file(
463 		&settingsDirectory,
464 		s_settingsFile,
465 		B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
466 	err = file.InitCheck();
467 	if(err < B_OK)
468 		return err;
469 
470 	// write document header
471 	const char* header = "<?xml version=\"1.0\"?>\n";
472 	file.Write((const void*)header, strlen(header));
473 
474 	// write content
475 	BString errorText;
476 	err = XML::Write(
477 		&file,
478 		this,
479 		&errorText);
480 
481 	if(err < B_OK) {
482 		fprintf(stderr,
483 			"!!! RouteApp::_writeSettings() failed: %s\n",
484 			errorText.String());
485 	}
486 
487 	return err;
488 }
489 
490 // -------------------------------------------------------- //
491 
492 class _RouteAppImportContext :
493 	public	ImportContext,
494 	public	NodeSetIOContext {
495 
496 public:
497 	_RouteAppImportContext(
498 		list<BString>&							errors,
499 		MediaRoutingView*						routingView) :
500 		ImportContext(errors),
501 		m_routingView(routingView) {}
502 
503 public:													// *** hooks
504 	virtual void importUIState(
505 		const BMessage*							archive) {
506 
507 		PRINT((
508 			"### importUIState\n"));
509 
510 		if(m_routingView) {
511 //			m_routingView->LockLooper();
512 			m_routingView->deselectAll();
513 			status_t err = m_routingView->importStateFor(
514 				this,
515 				archive);
516 			if(err < B_OK) {
517 				PRINT((
518 					"!!! _RouteAppImportContext::importStateFor() failed:\n"
519 					"    %s\n", strerror(err)));
520 			}
521 			m_routingView->Invalidate(); // +++++ not particularly clean
522 //			m_routingView->UnlockLooper();
523 		}
524 	}
525 
526 	MediaRoutingView*							m_routingView;
527 };
528 
529 status_t RouteApp::_readNodeSet(
530 	entry_ref*								ref) {
531 
532 	BFile file(ref, B_READ_ONLY);
533 	status_t err = file.InitCheck();
534 	if(err < B_OK)
535 		return err;
536 
537 	routeWindow->Lock();
538 
539 	list<BString> errors;
540 
541 	err = XML::Read(
542 		&file,
543 		manager,
544 		m_nodeSetDocType,
545 		new _RouteAppImportContext(errors, routeWindow->m_routingView));
546 
547 	routeWindow->Unlock();
548 
549 	if(errors.size()) {
550 		fputs("!!! RouteApp::_readNodeSet():", stderr);
551 		for(list<BString>::iterator it = errors.begin();
552 			it != errors.end(); ++it)
553 			fputs((*it).String(), stderr);
554 	}
555 	return err;
556 }
557 
558 // -------------------------------------------------------- //
559 
560 class _RouteAppExportContext :
561 	public	ExportContext,
562 	public	NodeSetIOContext {
563 
564 public:
565 	_RouteAppExportContext(
566 		MediaRoutingView*						routingView) :
567 		m_routingView(routingView) {}
568 
569 public:													// *** hooks
570 	virtual void exportUIState(
571 		BMessage*										archive) {
572 
573 		PRINT((
574 			"### exportUIState\n"));
575 
576 		if(m_routingView) {
577 			m_routingView->LockLooper();
578 			m_routingView->exportStateFor(
579 				this,
580 				archive);
581 			m_routingView->UnlockLooper();
582 		}
583 	}
584 
585 	MediaRoutingView*							m_routingView;
586 };
587 
588 status_t RouteApp::_writeSelectedNodeSet(
589 	entry_ref*								dirRef,
590 	const char*								filename) {
591 
592 	status_t err;
593 
594 
595 	// sanity-check & fetch the selection
596 	routeWindow->Lock();
597 
598 	MediaRoutingView* v = routeWindow->m_routingView;
599 	ASSERT(v);
600 
601 	if(
602 		v->countSelectedItems() < 0 ||
603 		v->selectedType() != DiagramItem::M_BOX) {
604 		PRINT((
605 			"!!! RouteApp::_writeSelectedNodeSet():\n"
606 			"    Invalid selection!\n"));
607 
608 		routeWindow->Unlock();
609 		return B_NOT_ALLOWED;
610 	}
611 
612 	_RouteAppExportContext context(v);
613 
614 	for(uint32 i = 0; i < v->countSelectedItems(); ++i) {
615 		MediaNodePanel* panel = dynamic_cast<MediaNodePanel*>(v->selectedItemAt(i));
616 		if(!panel)
617 			continue;
618 		err = context.addNode(panel->ref->id());
619 		if(err < B_OK) {
620 			PRINT((
621 				"!!! context.addNode() failed: '%s\n", strerror(err)));
622 		}
623 	}
624 	routeWindow->Unlock();
625 
626 	// open/clobber file
627 	BDirectory dir(dirRef);
628 	err = dir.InitCheck();
629 	if(err < B_OK)
630 		return err;
631 
632 	BFile file(
633 		&dir,
634 		filename,
635 		B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
636 	err = file.InitCheck();
637 	if(err < B_OK)
638 		return err;
639 
640 	// write document header
641 	const char* header = "<?xml version=\"1.0\"?>\n";
642 	file.Write((const void*)header, strlen(header));
643 
644 	// export nodes
645 	context.stream = &file;
646 	err = context.writeObject(manager);
647 	if(err < B_OK) {
648 		PRINT((
649 			"!!! RouteApp::_writeSelectedNodeSet(): error:\n"
650 			"    %s\n", context.errorText()));
651 
652 		// +++++ delete the malformed file
653 
654 	}
655 
656 
657 	// write MIME type
658 	BNodeInfo* fileInfo = new BNodeInfo(&file);
659 	fileInfo->SetType(s_nodeSetType.Type());
660 	fileInfo->SetPreferredApp(s_appSignature);
661 	delete fileInfo;
662 
663 	return B_OK;
664 }
665 
666 /*static*/
667 status_t RouteApp::_InitMimeTypes() {
668 
669 	status_t err;
670 
671 	ASSERT(s_nodeSetType.IsValid());
672 
673 	if(!s_nodeSetType.IsInstalled()) {
674 		err = s_nodeSetType.Install();
675 		if(err < B_OK) {
676 			PRINT((
677 				"!!! RouteApp::_InitMimeTypes(): Install():\n"
678 				"    %s\n", strerror(err)));
679 			return err;
680 		}
681 
682 		err = s_nodeSetType.SetPreferredApp(s_appSignature);
683 		if(err < B_OK) {
684 			PRINT((
685 				"!!! RouteApp::_InitMimeTypes(): SetPreferredApp():\n"
686 				"    %s\n", strerror(err)));
687 			return err;
688 		}
689 	}
690 
691 	return B_OK;
692 }
693 
694 // -------------------------------------------------------- //
695 // main() stub
696 // -------------------------------------------------------- //
697 
698 int main(int argc, char** argv) {
699 //	SetNewLeakChecking(true);
700 //	SetMallocLeakChecking(true);
701 
702 	RouteApp app;
703 	app.Run();
704 
705 	return 0;
706 }
707 
708 // END -- RouteApp.cpp --
709