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