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	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
283void RouteApp::xmlExportEnd(
284	ExportContext&						context) const {
285	context.endElement();
286}
287
288// IMPORT
289
290void RouteApp::xmlImportBegin(
291	ImportContext&						context) {
292
293	m_readState = _READ_ROOT;
294}
295
296void RouteApp::xmlImportAttribute(
297	const char*								key,
298	const char*								value,
299	ImportContext&						context) {} //nyi
300
301void RouteApp::xmlImportContent(
302	const char*								data,
303	uint32										length,
304	ImportContext&						context) {} //nyi
305
306void 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
339void RouteApp::xmlImportComplete(
340	ImportContext&						context) {} //nyi
341
342void 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
362void 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
377status_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
390status_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
403XML::DocumentType* RouteApp::_createSettingsDocType() {
404
405	XML::DocumentType* docType = new XML::DocumentType(
406		s_rootElement);
407	MessageIO::AddTo(docType);
408
409	return docType;
410}
411
412XML::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
421status_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
462status_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
518class _RouteAppImportContext :
519	public	ImportContext,
520	public	NodeSetIOContext {
521
522public:
523	_RouteAppImportContext(
524		list<BString>&							errors,
525		MediaRoutingView*						routingView) :
526		ImportContext(errors),
527		m_routingView(routingView) {}
528
529public:													// *** 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
555status_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
586class _RouteAppExportContext :
587	public	ExportContext,
588	public	NodeSetIOContext {
589
590public:
591	_RouteAppExportContext(
592		MediaRoutingView*						routingView) :
593		m_routingView(routingView) {}
594
595public:													// *** 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
614status_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*/
693status_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
724int 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