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
~RouteApp()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
RouteApp()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
QuitRequested()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
MessageReceived(BMessage * message)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
RefsReceived(BMessage * message)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
xmlExportBegin(ExportContext & context) const236 void RouteApp::xmlExportBegin(
237 ExportContext& context) const {
238 context.beginElement(s_rootElement);
239 }
240
xmlExportAttributes(ExportContext & context) const241 void RouteApp::xmlExportAttributes(
242 ExportContext& context) const {} //nyi: write version info +++++
243
244 // +++++
xmlExportContent(ExportContext & context) const245 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
xmlExportEnd(ExportContext & context) const283 void RouteApp::xmlExportEnd(
284 ExportContext& context) const {
285 context.endElement();
286 }
287
288 // IMPORT
289
xmlImportBegin(ImportContext & context)290 void RouteApp::xmlImportBegin(
291 ImportContext& context) {
292
293 m_readState = _READ_ROOT;
294 }
295
xmlImportAttribute(const char * key,const char * value,ImportContext & context)296 void RouteApp::xmlImportAttribute(
297 const char* key,
298 const char* value,
299 ImportContext& context) {} //nyi
300
xmlImportContent(const char * data,uint32 length,ImportContext & context)301 void RouteApp::xmlImportContent(
302 const char* data,
303 uint32 length,
304 ImportContext& context) {} //nyi
305
xmlImportChild(IPersistent * child,ImportContext & context)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
xmlImportComplete(ImportContext & context)339 void RouteApp::xmlImportComplete(
340 ImportContext& context) {} //nyi
341
xmlImportChildBegin(const char * name,ImportContext & context)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
xmlImportChildComplete(const char * name,ImportContext & context)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
importState(const BMessage * archive)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
exportState(BMessage * archive) const390 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
_createSettingsDocType()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
_createNodeSetDocType()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
_readSettings()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
_writeSettings()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:
_RouteAppImportContext(list<BString> & errors,MediaRoutingView * routingView)523 _RouteAppImportContext(
524 list<BString>& errors,
525 MediaRoutingView* routingView) :
526 ImportContext(errors),
527 m_routingView(routingView) {}
528
529 public: // *** hooks
importUIState(const BMessage * archive)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
_readNodeSet(entry_ref * ref)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:
_RouteAppExportContext(MediaRoutingView * routingView)591 _RouteAppExportContext(
592 MediaRoutingView* routingView) :
593 m_routingView(routingView) {}
594
595 public: // *** hooks
exportUIState(BMessage * archive)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
_writeSelectedNodeSet(entry_ref * dirRef,const char * filename)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*/
_InitMimeTypes()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
main(int argc,char ** argv)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