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
62extern "C" void SetNewLeakChecking(bool);
63extern "C" void SetMallocLeakChecking(bool);
64
65using namespace std;
66
67__USE_CORTEX_NAMESPACE
68
69const char* const		RouteApp::s_settingsDirectory = "Cortex";
70const char* const		RouteApp::s_settingsFile = "cortex_settings";
71
72const char* const		RouteApp::s_appSignature = "application/x-vnd.Cortex.Route";
73
74BMimeType						RouteApp::s_nodeSetType("text/x-vnd.Cortex.NodeSet");
75
76const char* const		RouteApp::s_rootElement = "cortex_settings";
77const char* const		RouteApp::s_mediaRoutingViewElement = "MediaRoutingView";
78const char* const		RouteApp::s_routeWindowElement = "RouteWindow";
79
80// -------------------------------------------------------- //
81// ctor/dtor
82// -------------------------------------------------------- //
83
84RouteApp::~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
113RouteApp::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
140bool
141RouteApp::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
165void 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
208void 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
236void RouteApp::xmlExportBegin(
237	ExportContext&						context) const {
238	context.beginElement(s_rootElement);
239}
240
241void RouteApp::xmlExportAttributes(
242	ExportContext&						context) const {} //nyi: write version info +++++
243
244// +++++
245void 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
284void RouteApp::xmlExportEnd(
285	ExportContext&						context) const {
286	context.endElement();
287}
288
289// IMPORT
290
291void RouteApp::xmlImportBegin(
292	ImportContext&						context) {
293
294	m_readState = _READ_ROOT;
295}
296
297void RouteApp::xmlImportAttribute(
298	const char*								key,
299	const char*								value,
300	ImportContext&						context) {} //nyi
301
302void RouteApp::xmlImportContent(
303	const char*								data,
304	uint32										length,
305	ImportContext&						context) {} //nyi
306
307void 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
340void RouteApp::xmlImportComplete(
341	ImportContext&						context) {} //nyi
342
343void 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
363void 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
378status_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
391status_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
404XML::DocumentType* RouteApp::_createSettingsDocType() {
405
406	XML::DocumentType* docType = new XML::DocumentType(
407		s_rootElement);
408	MessageIO::AddTo(docType);
409
410	return docType;
411}
412
413XML::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
422status_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
463status_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
519class _RouteAppImportContext :
520	public	ImportContext,
521	public	NodeSetIOContext {
522
523public:
524	_RouteAppImportContext(
525		list<BString>&							errors,
526		MediaRoutingView*						routingView) :
527		ImportContext(errors),
528		m_routingView(routingView) {}
529
530public:													// *** 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
556status_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
587class _RouteAppExportContext :
588	public	ExportContext,
589	public	NodeSetIOContext {
590
591public:
592	_RouteAppExportContext(
593		MediaRoutingView*						routingView) :
594		m_routingView(routingView) {}
595
596public:													// *** 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
615status_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*/
694status_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
725int 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