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