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