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