xref: /haiku/src/apps/cortex/RouteApp/RouteApp.cpp (revision e1c4049fed1047bdb957b0529e1921e97ef94770)
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 	context.beginContent();
249 
250 	// export app settings
251 	{
252 		BMessage m;
253 		exportState(&m);
254 		MessageIO io(&m);
255 		status_t err __attribute__((unused)) = context.writeObject(&io);
256 		ASSERT(err == B_OK);
257 	}
258 
259 	if(routeWindow) {
260 		// export main routing window (frame/palette) settings
261 		context.beginElement(s_routeWindowElement);
262 		context.beginContent();
263 		BMessage m;
264 		if (routeWindow->Lock()) {
265 			routeWindow->exportState(&m);
266 			routeWindow->Unlock();
267 		}
268 		MessageIO io(&m);
269 		context.writeObject(&io);
270 		context.endElement();
271 
272 		// export routing view (content) settings
273 		m.MakeEmpty();
274 		ASSERT(routeWindow->m_routingView);
275 		context.beginElement(s_mediaRoutingViewElement);
276 		context.beginContent();
277 		routeWindow->m_routingView->exportState(&m);
278 		context.writeObject(&io);
279 		context.endElement();
280 	}
281 }
282 
283 void RouteApp::xmlExportEnd(
284 	ExportContext&						context) const {
285 	context.endElement();
286 }
287 
288 // IMPORT
289 
290 void RouteApp::xmlImportBegin(
291 	ImportContext&						context) {
292 
293 	m_readState = _READ_ROOT;
294 }
295 
296 void RouteApp::xmlImportAttribute(
297 	const char*								key,
298 	const char*								value,
299 	ImportContext&						context) {} //nyi
300 
301 void RouteApp::xmlImportContent(
302 	const char*								data,
303 	uint32										length,
304 	ImportContext&						context) {} //nyi
305 
306 void RouteApp::xmlImportChild(
307 	IPersistent*							child,
308 	ImportContext&						context) {
309 
310 	MessageIO* io = dynamic_cast<MessageIO*>(child);
311 	if(io) {
312 		ASSERT(io->message());
313 //		PRINT(("* RouteApp::xmlImportChild() [flat message]:\n"));
314 //		io->message()->PrintToStream();
315 
316 		switch(m_readState) {
317 			case _READ_ROOT:
318 				importState(io->message());
319 				break;
320 
321 			case _READ_ROUTE_WINDOW:
322 				ASSERT(routeWindow);
323 				routeWindow->importState(io->message());
324 				break;
325 
326 			case _READ_MEDIA_ROUTING_VIEW:
327 				ASSERT(routeWindow);
328 				ASSERT(routeWindow->m_routingView);
329 				routeWindow->m_routingView->importState(io->message());
330 				break;
331 
332 			default:
333 				PRINT(("! RouteApp::xmlImportChild(): unimplemented target\n"));
334 				break;
335 		}
336 	}
337 }
338 
339 void RouteApp::xmlImportComplete(
340 	ImportContext&						context) {} //nyi
341 
342 void RouteApp::xmlImportChildBegin(
343 	const char*								name,
344 	ImportContext&						context) {
345 
346 	if(m_readState != _READ_ROOT) {
347 		context.reportError("RouteApp import: invalid nested element");
348 		return;
349 	}
350 
351 	if(!strcmp(name, s_routeWindowElement)) {
352 		m_readState = _READ_ROUTE_WINDOW;
353 	}
354 	else if(!strcmp(name, s_mediaRoutingViewElement)) {
355 		m_readState = _READ_MEDIA_ROUTING_VIEW;
356 	}
357 	else {
358 		context.reportError("RouteApp import: unknown child element");
359 	}
360 }
361 
362 void RouteApp::xmlImportChildComplete(
363 	const char*								name,
364 	ImportContext&						context) {
365 
366 	if(m_readState == _READ_ROOT) {
367 		context.reportError("RouteApp import: garbled state");
368 		return;
369 	}
370 	m_readState = _READ_ROOT;
371 }
372 
373 // -------------------------------------------------------- //
374 // *** IStateArchivable
375 // -------------------------------------------------------- //
376 
377 status_t RouteApp::importState(
378 	const BMessage*						archive) {
379 
380 	const char* last;
381 	if(archive->FindString("lastDir", &last) == B_OK) {
382 		m_lastIODir.SetTo(last);
383 		m_openPanel.SetPanelDirectory(last);
384 		m_savePanel.SetPanelDirectory(last);
385 	}
386 
387 	return B_OK;
388 }
389 
390 status_t RouteApp::exportState(
391 	BMessage*									archive) const {
392 
393 	if(m_lastIODir.InitCheck() == B_OK)
394 		archive->AddString("lastDir", m_lastIODir.Path());
395 
396 	return B_OK;
397 }
398 
399 // -------------------------------------------------------- //
400 // implementation
401 // -------------------------------------------------------- //
402 
403 XML::DocumentType* RouteApp::_createSettingsDocType() {
404 
405 	XML::DocumentType* docType = new XML::DocumentType(
406 		s_rootElement);
407 	MessageIO::AddTo(docType);
408 
409 	return docType;
410 }
411 
412 XML::DocumentType* RouteApp::_createNodeSetDocType() {
413 
414 	XML::DocumentType* docType = new XML::DocumentType(
415 		_NODE_SET_ELEMENT);
416 	RouteAppNodeManager::AddTo(docType);
417 
418 	return docType;
419 }
420 
421 status_t RouteApp::_readSettings() {
422 
423 	// figure path
424 	BPath path;
425 	status_t err = find_directory(
426 		B_USER_SETTINGS_DIRECTORY,
427 		&path);
428 	ASSERT(err == B_OK);
429 
430 	path.Append(s_settingsDirectory);
431 	BEntry entry(path.Path());
432 	if(!entry.Exists())
433 		return B_ENTRY_NOT_FOUND;
434 
435 	path.Append(s_settingsFile);
436 	entry.SetTo(path.Path());
437 	if(!entry.Exists())
438 		return B_ENTRY_NOT_FOUND;
439 
440 	// open the settings file
441 	BFile file(&entry, B_READ_ONLY);
442 	if(file.InitCheck() != B_OK)
443 		return file.InitCheck();
444 
445 	// read it:
446 	list<BString> errors;
447 	err = XML::Read(
448 		&file,
449 		this,
450 		m_settingsDocType,
451 		&errors);
452 
453 	if(errors.size()) {
454 		fputs("!!! RouteApp::_readSettings():", stderr);
455 		for(list<BString>::iterator it = errors.begin();
456 			it != errors.end(); ++it)
457 			fputs((*it).String(), stderr);
458 	}
459 	return err;
460 }
461 
462 status_t RouteApp::_writeSettings() {
463 	// figure path, creating settings folder if necessary
464 	BPath path;
465 	status_t err = find_directory(
466 		B_USER_SETTINGS_DIRECTORY,
467 		&path);
468 	ASSERT(err == B_OK);
469 
470 	BDirectory baseDirectory, settingsDirectory;
471 
472 	err = baseDirectory.SetTo(path.Path());
473 	if(err < B_OK)
474 		return err;
475 
476 	path.Append(s_settingsDirectory);
477 
478 	BEntry folderEntry(path.Path());
479 	if(!folderEntry.Exists()) {
480 		// create folder
481 		err = baseDirectory.CreateDirectory(s_settingsDirectory, &settingsDirectory);
482 		ASSERT(err == B_OK);
483 	}
484 	else
485 		settingsDirectory.SetTo(&folderEntry);
486 
487 	// open/clobber file
488 	BFile file(
489 		&settingsDirectory,
490 		s_settingsFile,
491 		B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
492 	err = file.InitCheck();
493 	if(err < B_OK)
494 		return err;
495 
496 	// write document header
497 	const char* header = "<?xml version=\"1.0\"?>\n";
498 	file.Write((const void*)header, strlen(header));
499 
500 	// write content
501 	BString errorText;
502 	err = XML::Write(
503 		&file,
504 		this,
505 		&errorText);
506 
507 	if(err < B_OK) {
508 		fprintf(stderr,
509 			"!!! RouteApp::_writeSettings() failed: %s\n",
510 			errorText.String());
511 	}
512 
513 	return err;
514 }
515 
516 // -------------------------------------------------------- //
517 
518 class _RouteAppImportContext :
519 	public	ImportContext,
520 	public	NodeSetIOContext {
521 
522 public:
523 	_RouteAppImportContext(
524 		list<BString>&							errors,
525 		MediaRoutingView*						routingView) :
526 		ImportContext(errors),
527 		m_routingView(routingView) {}
528 
529 public:													// *** hooks
530 	virtual void importUIState(
531 		const BMessage*							archive) {
532 
533 		PRINT((
534 			"### importUIState\n"));
535 
536 		if(m_routingView) {
537 //			m_routingView->LockLooper();
538 			m_routingView->DeselectAll();
539 			status_t err = m_routingView->importStateFor(
540 				this,
541 				archive);
542 			if(err < B_OK) {
543 				PRINT((
544 					"!!! _RouteAppImportContext::importStateFor() failed:\n"
545 					"    %s\n", strerror(err)));
546 			}
547 			m_routingView->Invalidate(); // +++++ not particularly clean
548 //			m_routingView->UnlockLooper();
549 		}
550 	}
551 
552 	MediaRoutingView*							m_routingView;
553 };
554 
555 status_t RouteApp::_readNodeSet(
556 	entry_ref*								ref) {
557 
558 	BFile file(ref, B_READ_ONLY);
559 	status_t err = file.InitCheck();
560 	if(err < B_OK)
561 		return err;
562 
563 	routeWindow->Lock();
564 
565 	list<BString> errors;
566 
567 	err = XML::Read(
568 		&file,
569 		manager,
570 		m_nodeSetDocType,
571 		new _RouteAppImportContext(errors, routeWindow->m_routingView));
572 
573 	routeWindow->Unlock();
574 
575 	if(errors.size()) {
576 		fputs("!!! RouteApp::_readNodeSet():", stderr);
577 		for(list<BString>::iterator it = errors.begin();
578 			it != errors.end(); ++it)
579 			fputs((*it).String(), stderr);
580 	}
581 	return err;
582 }
583 
584 // -------------------------------------------------------- //
585 
586 class _RouteAppExportContext :
587 	public	ExportContext,
588 	public	NodeSetIOContext {
589 
590 public:
591 	_RouteAppExportContext(
592 		MediaRoutingView*						routingView) :
593 		m_routingView(routingView) {}
594 
595 public:													// *** hooks
596 	virtual void exportUIState(
597 		BMessage*										archive) {
598 
599 		PRINT((
600 			"### exportUIState\n"));
601 
602 		if(m_routingView) {
603 			m_routingView->LockLooper();
604 			m_routingView->exportStateFor(
605 				this,
606 				archive);
607 			m_routingView->UnlockLooper();
608 		}
609 	}
610 
611 	MediaRoutingView*							m_routingView;
612 };
613 
614 status_t RouteApp::_writeSelectedNodeSet(
615 	entry_ref*								dirRef,
616 	const char*								filename) {
617 
618 	status_t err;
619 
620 
621 	// sanity-check & fetch the selection
622 	routeWindow->Lock();
623 
624 	MediaRoutingView* v = routeWindow->m_routingView;
625 	ASSERT(v);
626 
627 	if(
628 		v->CountSelectedItems() < 0 ||
629 		v->SelectedType() != DiagramItem::M_BOX) {
630 		PRINT((
631 			"!!! RouteApp::_writeSelectedNodeSet():\n"
632 			"    Invalid selection!\n"));
633 
634 		routeWindow->Unlock();
635 		return B_NOT_ALLOWED;
636 	}
637 
638 	_RouteAppExportContext context(v);
639 
640 	for(uint32 i = 0; i < v->CountSelectedItems(); ++i) {
641 		MediaNodePanel* panel = dynamic_cast<MediaNodePanel*>(v->SelectedItemAt(i));
642 		if(!panel)
643 			continue;
644 		err = context.addNode(panel->ref->id());
645 		if(err < B_OK) {
646 			PRINT((
647 				"!!! context.addNode() failed: '%s\n", strerror(err)));
648 		}
649 	}
650 	routeWindow->Unlock();
651 
652 	// open/clobber file
653 	BDirectory dir(dirRef);
654 	err = dir.InitCheck();
655 	if(err < B_OK)
656 		return err;
657 
658 	BFile file(
659 		&dir,
660 		filename,
661 		B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
662 	err = file.InitCheck();
663 	if(err < B_OK)
664 		return err;
665 
666 	// write document header
667 	const char* header = "<?xml version=\"1.0\"?>\n";
668 	file.Write((const void*)header, strlen(header));
669 
670 	// export nodes
671 	context.stream = &file;
672 	err = context.writeObject(manager);
673 	if(err < B_OK) {
674 		PRINT((
675 			"!!! RouteApp::_writeSelectedNodeSet(): error:\n"
676 			"    %s\n", context.errorText()));
677 
678 		// +++++ delete the malformed file
679 
680 	}
681 
682 
683 	// write MIME type
684 	BNodeInfo* fileInfo = new BNodeInfo(&file);
685 	fileInfo->SetType(s_nodeSetType.Type());
686 	fileInfo->SetPreferredApp(s_appSignature);
687 	delete fileInfo;
688 
689 	return B_OK;
690 }
691 
692 /*static*/
693 status_t RouteApp::_InitMimeTypes() {
694 
695 	status_t err;
696 
697 	ASSERT(s_nodeSetType.IsValid());
698 
699 	if(!s_nodeSetType.IsInstalled()) {
700 		err = s_nodeSetType.Install();
701 		if(err < B_OK) {
702 			PRINT((
703 				"!!! RouteApp::_InitMimeTypes(): Install():\n"
704 				"    %s\n", strerror(err)));
705 			return err;
706 		}
707 
708 		err = s_nodeSetType.SetPreferredApp(s_appSignature);
709 		if(err < B_OK) {
710 			PRINT((
711 				"!!! RouteApp::_InitMimeTypes(): SetPreferredApp():\n"
712 				"    %s\n", strerror(err)));
713 			return err;
714 		}
715 	}
716 
717 	return B_OK;
718 }
719 
720 // -------------------------------------------------------- //
721 // main() stub
722 // -------------------------------------------------------- //
723 
724 int main(int argc, char** argv) {
725 //	SetNewLeakChecking(true);
726 //	SetMallocLeakChecking(true);
727 
728 	RouteApp app;
729 	app.Run();
730 
731 	return 0;
732 }
733 
734 // END -- RouteApp.cpp --
735