1 /* 2 * Copyright 2004-2006, Jérôme DUVAL. All rights reserved. 3 * Copyright 2010, Karsten Heimrich. All rights reserved. 4 * Distributed under the terms of the MIT License. 5 */ 6 7 8 #include "ExpanderApp.h" 9 #include "ExpanderWindow.h" 10 #include "ExpanderThread.h" 11 #include "ExpanderPreferences.h" 12 13 14 #include <Alert.h> 15 #include <Application.h> 16 #include <Box.h> 17 #include <Button.h> 18 #include <CheckBox.h> 19 #include <ControlLook.h> 20 #include <Entry.h> 21 #include <File.h> 22 #include <GroupLayout.h> 23 #include <GroupLayoutBuilder.h> 24 #include <Menu.h> 25 #include <MenuBar.h> 26 #include <MenuItem.h> 27 #include <Path.h> 28 #include <ScrollView.h> 29 #include <StringView.h> 30 #include <TextView.h> 31 32 33 const uint32 MSG_SOURCE = 'mSOU'; 34 const uint32 MSG_DEST = 'mDES'; 35 const uint32 MSG_EXPAND = 'mEXP'; 36 const uint32 MSG_SHOW = 'mSHO'; 37 const uint32 MSG_STOP = 'mSTO'; 38 const uint32 MSG_PREFERENCES = 'mPRE'; 39 const uint32 MSG_SOURCETEXT = 'mSTX'; 40 const uint32 MSG_DESTTEXT = 'mDTX'; 41 const uint32 MSG_SHOWCONTENTS = 'mSCT'; 42 43 44 ExpanderWindow::ExpanderWindow(BRect frame, const entry_ref* ref, 45 BMessage* settings) 46 : 47 BWindow(frame, "Expander", B_TITLED_WINDOW, B_NOT_ZOOMABLE), 48 fSourcePanel(NULL), 49 fDestPanel(NULL), 50 fSourceChanged(true), 51 fListingThread(NULL), 52 fListingStarted(false), 53 fExpandingThread(NULL), 54 fExpandingStarted(false), 55 fSettings(*settings), 56 fPreferences(NULL) 57 { 58 BGroupLayout* layout = new BGroupLayout(B_VERTICAL); 59 SetLayout(layout); 60 61 _AddMenuBar(layout); 62 63 fDestButton = new BButton("Destination", new BMessage(MSG_DEST)); 64 fSourceButton = new BButton("Source", new BMessage(MSG_SOURCE)); 65 fExpandButton = new BButton("Expand", new BMessage(MSG_EXPAND)); 66 67 BSize size = fDestButton->PreferredSize(); 68 size.width = max_c(size.width, fSourceButton->PreferredSize().width); 69 size.width = max_c(size.width, fExpandButton->PreferredSize().width); 70 71 fDestButton->SetExplicitMaxSize(size); 72 fSourceButton->SetExplicitMaxSize(size); 73 fExpandButton->SetExplicitMaxSize(size); 74 75 fListingText = new BTextView("listingText"); 76 fListingText->SetText(""); 77 fListingText->MakeEditable(false); 78 BScrollView* scrollView = new BScrollView("", fListingText, 79 B_INVALIDATE_AFTER_LAYOUT, false, true); 80 81 BView* topView = layout->View(); 82 const float spacing = be_control_look->DefaultItemSpacing(); 83 topView->AddChild(BGroupLayoutBuilder(B_VERTICAL, spacing) 84 .AddGroup(B_HORIZONTAL, spacing) 85 .AddGroup(B_VERTICAL, 5.0) 86 .Add(fDestButton) 87 .Add(fSourceButton) 88 .Add(fExpandButton) 89 .End() 90 .AddGroup(B_VERTICAL, spacing) 91 .Add(fSourceText = new BTextControl(NULL, NULL, 92 new BMessage(MSG_SOURCETEXT))) 93 .Add(fDestText = new BTextControl(NULL, NULL, 94 new BMessage(MSG_DESTTEXT))) 95 .AddGroup(B_HORIZONTAL, spacing) 96 .Add(fShowContents = new BCheckBox("Show contents", 97 new BMessage(MSG_SHOWCONTENTS))) 98 .Add(fStatusView = new BStringView(NULL, NULL)) 99 .End() 100 .End() 101 .End() 102 .Add(scrollView) 103 .SetInsets(spacing, spacing, spacing, spacing) 104 ); 105 106 size = topView->PreferredSize(); 107 fSizeLimit = size.Height() - scrollView->PreferredSize().height - spacing; 108 109 ResizeTo(Bounds().Width(), fSizeLimit); 110 SetSizeLimits(size.Width(), 32767.0f, fSizeLimit, fSizeLimit); 111 112 Show(); 113 } 114 115 116 ExpanderWindow::~ExpanderWindow() 117 { 118 if (fDestPanel && fDestPanel->RefFilter()) 119 delete fDestPanel->RefFilter(); 120 121 if (fSourcePanel && fSourcePanel->RefFilter()) 122 delete fSourcePanel->RefFilter(); 123 124 delete fDestPanel; 125 delete fSourcePanel; 126 } 127 128 129 bool 130 ExpanderWindow::ValidateDest() 131 { 132 BEntry entry(fDestText->Text(), true); 133 BVolume volume; 134 if (!entry.Exists()) { 135 BAlert* alert = new BAlert("destAlert", "The destination" 136 " folder does not exist.", "Cancel", NULL, NULL, 137 B_WIDTH_AS_USUAL, B_EVEN_SPACING, B_WARNING_ALERT); 138 alert->Go(); 139 return false; 140 } else if (!entry.IsDirectory()) { 141 (new BAlert("destAlert", "The destination" 142 " is not a folder.", "Cancel", NULL, NULL, 143 B_WIDTH_AS_USUAL, B_EVEN_SPACING, B_WARNING_ALERT))->Go(); 144 return false; 145 } else if (entry.GetVolume(&volume) != B_OK || volume.IsReadOnly()) { 146 (new BAlert("destAlert", "The destination is read only.", 147 "Cancel", NULL, NULL, B_WIDTH_AS_USUAL, B_EVEN_SPACING, 148 B_WARNING_ALERT))->Go(); 149 return false; 150 } else { 151 entry.GetRef(&fDestRef); 152 return true; 153 } 154 } 155 156 157 void 158 ExpanderWindow::FrameResized(float width, float height) 159 { 160 if (fListingText->DoesWordWrap()) { 161 BRect textRect; 162 textRect = fListingText->Bounds(); 163 textRect.OffsetTo(B_ORIGIN); 164 textRect.InsetBy(1, 1); 165 fListingText->SetTextRect(textRect); 166 } 167 } 168 169 170 void 171 ExpanderWindow::MessageReceived(BMessage* msg) 172 { 173 switch (msg->what) { 174 case MSG_SOURCE: 175 { 176 BEntry entry(fSourceText->Text(), true); 177 entry_ref srcRef; 178 if (entry.Exists() && entry.IsDirectory()) 179 entry.GetRef(&srcRef); 180 if (!fSourcePanel) { 181 BMessenger messenger(this); 182 fSourcePanel = new BFilePanel(B_OPEN_PANEL, &messenger, &srcRef, 183 B_FILE_NODE, false, NULL, new RuleRefFilter(fRules), true); 184 } else 185 fSourcePanel->SetPanelDirectory(&srcRef); 186 fSourcePanel->Show(); 187 break; 188 } 189 190 case MSG_DEST: 191 { 192 BEntry entry(fDestText->Text(), true); 193 entry_ref destRef; 194 if (entry.Exists() && entry.IsDirectory()) 195 entry.GetRef(&destRef); 196 if (!fDestPanel) { 197 BMessenger messenger(this); 198 fDestPanel = new DirectoryFilePanel(B_OPEN_PANEL, &messenger, 199 &destRef, B_DIRECTORY_NODE, false, NULL, 200 new DirectoryRefFilter(), true); 201 } else 202 fDestPanel->SetPanelDirectory(&destRef); 203 fDestPanel->Show(); 204 break; 205 } 206 207 case MSG_DIRECTORY: 208 { 209 entry_ref ref; 210 fDestPanel->GetPanelDirectory(&ref); 211 fDestRef = ref; 212 BEntry entry(&ref); 213 BPath path(&entry); 214 fDestText->SetText(path.Path()); 215 fDestPanel->Hide(); 216 break; 217 } 218 219 case B_SIMPLE_DATA: 220 case B_REFS_RECEIVED: 221 RefsReceived(msg); 222 break; 223 224 case MSG_EXPAND: 225 if (!ValidateDest()) 226 break; 227 if (!fExpandingStarted) { 228 StartExpanding(); 229 break; 230 } 231 // supposed to fall through 232 case MSG_STOP: 233 if (fExpandingStarted) { 234 fExpandingThread->SuspendExternalExpander(); 235 BAlert* alert = new BAlert("stopAlert", "Are you sure you want " 236 "to stop expanding this\narchive? The expanded items may " 237 "not be complete.", "Stop", "Continue", NULL, 238 B_WIDTH_AS_USUAL, B_EVEN_SPACING, B_WARNING_ALERT); 239 if (alert->Go() == 0) { 240 fExpandingThread->ResumeExternalExpander(); 241 StopExpanding(); 242 } else 243 fExpandingThread->ResumeExternalExpander(); 244 } 245 break; 246 247 case MSG_SHOW: 248 fShowContents->SetValue(fShowContents->Value() == B_CONTROL_OFF 249 ? B_CONTROL_ON : B_CONTROL_OFF); 250 // supposed to fall through 251 case MSG_SHOWCONTENTS: 252 // change menu item label 253 fShowItem->SetLabel(fShowContents->Value() == B_CONTROL_OFF 254 ? "Show contents" : "Hide contents"); 255 256 if (fShowContents->Value() == B_CONTROL_OFF) { 257 if (fListingStarted) 258 StopListing(); 259 260 _UpdateWindowSize(false); 261 } else 262 StartListing(); 263 break; 264 265 case MSG_SOURCETEXT: 266 { 267 BEntry entry(fSourceText->Text(), true); 268 if (!entry.Exists()) { 269 BAlert* alert = new BAlert("srcAlert", "The file doesn't exist", 270 "Cancel", NULL, NULL, 271 B_WIDTH_AS_USUAL, B_EVEN_SPACING, B_WARNING_ALERT); 272 alert->Go(); 273 break; 274 } 275 276 entry_ref ref; 277 entry.GetRef(&ref); 278 ExpanderRule* rule = fRules.MatchingRule(&ref); 279 if (rule) { 280 fSourceChanged = true; 281 fSourceRef = ref; 282 fShowContents->SetEnabled(true); 283 fExpandButton->SetEnabled(true); 284 fExpandItem->SetEnabled(true); 285 fShowItem->SetEnabled(true); 286 break; 287 } 288 289 BString string = "The file : "; 290 string += fSourceText->Text(); 291 string += " is not supported"; 292 BAlert* alert = new BAlert("srcAlert", string.String(), "Cancel", 293 NULL, NULL, B_WIDTH_AS_USUAL, B_EVEN_SPACING, B_INFO_ALERT); 294 alert->Go(); 295 296 fShowContents->SetEnabled(false); 297 fExpandButton->SetEnabled(false); 298 fExpandItem->SetEnabled(false); 299 fShowItem->SetEnabled(false); 300 } 301 break; 302 303 case MSG_DESTTEXT: 304 ValidateDest(); 305 break; 306 307 case MSG_PREFERENCES: 308 if (!fPreferences) 309 fPreferences = new ExpanderPreferences(&fSettings); 310 fPreferences->Show(); 311 break; 312 313 case 'outp': 314 if (!fExpandingStarted && fListingStarted) { 315 BString string; 316 int32 i = 0; 317 while (msg->FindString("output", i++, &string) == B_OK) { 318 // expand the window if we need... 319 float delta = fListingText->StringWidth(string.String()) 320 - fListingText->Frame().Width(); 321 if (delta > fLargestDelta) { 322 fLargestDelta = delta; 323 } 324 fListingText->Insert(string.String()); 325 } 326 fListingText->ScrollToSelection(); 327 } 328 break; 329 330 case 'exit': 331 // thread has finished (finished, quit, killed, we don't know) 332 // reset window state 333 if (fExpandingStarted) { 334 fStatusView->SetText("File expanded"); 335 StopExpanding(); 336 OpenDestFolder(); 337 CloseWindowOrKeepOpen(); 338 } else if (fListingStarted){ 339 fSourceChanged = false; 340 StopListing(); 341 if (fLargestDelta > 0.0f) 342 ResizeBy(fLargestDelta, 0.0f); 343 fLargestDelta = 0.0f; 344 } else 345 fStatusView->SetText(""); 346 break; 347 348 case 'exrr': // thread has finished 349 // reset window state 350 351 fStatusView->SetText("Error when expanding archive"); 352 CloseWindowOrKeepOpen(); 353 break; 354 355 default: 356 BWindow::MessageReceived(msg); 357 break; 358 } 359 } 360 361 362 bool 363 ExpanderWindow::CanQuit() 364 { 365 if ((fSourcePanel && fSourcePanel->IsShowing()) 366 || (fDestPanel && fDestPanel->IsShowing())) 367 return false; 368 369 if (fExpandingStarted) { 370 fExpandingThread->SuspendExternalExpander(); 371 BAlert* alert = new BAlert("stopAlert", "Are you sure you want to stop " 372 "expanding this\narchive? The expanded items may not be complete.", 373 "Stop", "Continue", NULL, B_WIDTH_AS_USUAL, B_EVEN_SPACING, 374 B_WARNING_ALERT); 375 if (alert->Go() == 0) { 376 fExpandingThread->ResumeExternalExpander(); 377 StopExpanding(); 378 } else { 379 fExpandingThread->ResumeExternalExpander(); 380 return false; 381 } 382 } 383 return true; 384 } 385 386 387 bool 388 ExpanderWindow::QuitRequested() 389 { 390 if (!CanQuit()) 391 return false; 392 393 if (fListingStarted) 394 StopListing(); 395 396 be_app->PostMessage(B_QUIT_REQUESTED); 397 fSettings.ReplacePoint("window_position", Frame().LeftTop()); 398 ((ExpanderApp*)(be_app))->UpdateSettingsFrom(&fSettings); 399 return true; 400 } 401 402 403 void 404 ExpanderWindow::RefsReceived(BMessage* msg) 405 { 406 entry_ref ref; 407 int32 i = 0; 408 int8 destination_folder = 0x63; 409 fSettings.FindInt8("destination_folder", &destination_folder); 410 411 while (msg->FindRef("refs", i++, &ref) == B_OK) { 412 BEntry entry(&ref, true); 413 BPath path(&entry); 414 BNode node(&entry); 415 416 if (node.IsFile()) { 417 fSourceChanged = true; 418 fSourceRef = ref; 419 fSourceText->SetText(path.Path()); 420 if (destination_folder == 0x63) { 421 BPath parent; 422 path.GetParent(&parent); 423 fDestText->SetText(parent.Path()); 424 get_ref_for_path(parent.Path(), &fDestRef); 425 } else if (destination_folder == 0x65) { 426 fSettings.FindRef("destination_folder_use", &fDestRef); 427 BEntry dEntry(&fDestRef, true); 428 BPath dPath(&dEntry); 429 fDestText->SetText(dPath.Path()); 430 } 431 432 BEntry dEntry(&fDestRef, true); 433 if (dEntry.Exists()) { 434 fExpandButton->SetEnabled(true); 435 fExpandItem->SetEnabled(true); 436 } 437 438 if (fShowContents->Value() == B_CONTROL_ON) { 439 StopListing(); 440 StartListing(); 441 } else { 442 fShowContents->SetEnabled(true); 443 fShowItem->SetEnabled(true); 444 } 445 446 bool fromApp; 447 if (msg->FindBool("fromApp", &fromApp) == B_OK) { 448 AutoExpand(); 449 } else 450 AutoListing(); 451 } else if (node.IsDirectory()) { 452 fDestRef = ref; 453 fDestText->SetText(path.Path()); 454 } 455 } 456 } 457 458 void 459 ExpanderWindow::_AddMenuBar(BLayout* layout) 460 { 461 fBar = new BMenuBar("menu_bar", B_ITEMS_IN_ROW, B_INVALIDATE_AFTER_LAYOUT); 462 BMenu* menu = new BMenu("File"); 463 BMenuItem* item; 464 menu->AddItem(item = new BMenuItem("About Expander" B_UTF8_ELLIPSIS, 465 new BMessage(B_ABOUT_REQUESTED))); 466 item->SetTarget(be_app_messenger); 467 menu->AddSeparatorItem(); 468 menu->AddItem(fSourceItem = new BMenuItem("Set source" B_UTF8_ELLIPSIS, 469 new BMessage(MSG_SOURCE), 'O')); 470 menu->AddItem(fDestItem = new BMenuItem("Set destination" B_UTF8_ELLIPSIS, 471 new BMessage(MSG_DEST), 'D')); 472 menu->AddSeparatorItem(); 473 menu->AddItem(fExpandItem = new BMenuItem("Expand", new BMessage(MSG_EXPAND), 474 'E')); 475 fExpandItem->SetEnabled(false); 476 menu->AddItem(fShowItem = new BMenuItem("Show contents", 477 new BMessage(MSG_SHOW), 'L')); 478 fShowItem->SetEnabled(false); 479 menu->AddSeparatorItem(); 480 menu->AddItem(fStopItem = new BMenuItem("Stop", new BMessage(MSG_STOP), 'K')); 481 fStopItem->SetEnabled(false); 482 menu->AddSeparatorItem(); 483 menu->AddItem(new BMenuItem("Close", new BMessage(B_QUIT_REQUESTED), 'W')); 484 fBar->AddItem(menu); 485 486 menu = new BMenu("Settings"); 487 menu->AddItem(fPreferencesItem = new BMenuItem("Settings" B_UTF8_ELLIPSIS, 488 new BMessage(MSG_PREFERENCES), 'S')); 489 fBar->AddItem(menu); 490 layout->AddView(fBar); 491 } 492 493 494 void 495 ExpanderWindow::StartExpanding() 496 { 497 ExpanderRule* rule = fRules.MatchingRule(&fSourceRef); 498 if (!rule) 499 return; 500 501 BEntry destEntry(fDestText->Text(), true); 502 if (!destEntry.Exists()) { 503 BAlert* alert = new BAlert("destAlert", "The folder was either moved, " 504 "renamed or not\nsupported.", "Cancel", NULL, NULL, 505 B_WIDTH_AS_USUAL, B_EVEN_SPACING, B_WARNING_ALERT); 506 alert->Go(); 507 return; 508 } 509 510 BMessage message; 511 message.AddString("cmd", rule->ExpandCmd()); 512 message.AddRef("srcRef", &fSourceRef); 513 message.AddRef("destRef", &fDestRef); 514 515 fExpandButton->SetLabel("Stop"); 516 fSourceButton->SetEnabled(false); 517 fDestButton->SetEnabled(false); 518 fShowContents->SetEnabled(false); 519 fSourceItem->SetEnabled(false); 520 fDestItem->SetEnabled(false); 521 fExpandItem->SetEnabled(false); 522 fShowItem->SetEnabled(false); 523 fStopItem->SetEnabled(true); 524 fPreferencesItem->SetEnabled(false); 525 526 BEntry entry(&fSourceRef); 527 BPath path(&entry); 528 BString text("Expanding file "); 529 text.Append(path.Leaf()); 530 fStatusView->SetText(text.String()); 531 532 fExpandingThread = new ExpanderThread(&message, new BMessenger(this)); 533 fExpandingThread->Start(); 534 535 fExpandingStarted = true; 536 } 537 538 539 void 540 ExpanderWindow::StopExpanding(void) 541 { 542 if (fExpandingThread) { 543 fExpandingThread->InterruptExternalExpander(); 544 fExpandingThread = NULL; 545 } 546 547 fExpandingStarted = false; 548 549 fExpandButton->SetLabel("Expand"); 550 fSourceButton->SetEnabled(true); 551 fDestButton->SetEnabled(true); 552 fShowContents->SetEnabled(true); 553 fSourceItem->SetEnabled(true); 554 fDestItem->SetEnabled(true); 555 fExpandItem->SetEnabled(true); 556 fShowItem->SetEnabled(true); 557 fStopItem->SetEnabled(false); 558 fPreferencesItem->SetEnabled(true); 559 } 560 561 562 void 563 ExpanderWindow::_UpdateWindowSize(bool showContents) 564 { 565 float minWidth, maxWidth, minHeight, maxHeight; 566 GetSizeLimits(&minWidth, &maxWidth, &minHeight, &maxHeight); 567 568 float bottom = fSizeLimit; 569 570 if (showContents) { 571 font_height fontHeight; 572 be_plain_font->GetHeight(&fontHeight); 573 float lineHeight = ceilf(fontHeight.ascent + fontHeight.descent 574 + fontHeight.leading); 575 576 minHeight = bottom + 5.0 * lineHeight; 577 maxHeight = 32767.0; 578 bottom = minHeight + 10.0 * lineHeight; 579 } else { 580 minHeight = fSizeLimit; 581 maxHeight = fSizeLimit; 582 } 583 584 SetSizeLimits(minWidth, maxWidth, minHeight, maxHeight); 585 ResizeTo(Frame().Width(), bottom); 586 } 587 588 589 void 590 ExpanderWindow::StartListing() 591 { 592 _UpdateWindowSize(true); 593 594 fLargestDelta = 0.0f; 595 596 if (!fSourceChanged) 597 return; 598 599 ExpanderRule* rule = fRules.MatchingRule(&fSourceRef); 600 if (!rule) 601 return; 602 603 BMessage message; 604 message.AddString("cmd", rule->ListingCmd()); 605 message.AddRef("srcRef", &fSourceRef); 606 607 fShowContents->SetEnabled(true); 608 fSourceItem->SetEnabled(false); 609 fDestItem->SetEnabled(false); 610 fExpandItem->SetEnabled(false); 611 fShowItem->SetEnabled(true); 612 fShowItem->SetLabel("Hide contents"); 613 fStopItem->SetEnabled(false); 614 fPreferencesItem->SetEnabled(false); 615 616 fSourceButton->SetEnabled(false); 617 fDestButton->SetEnabled(false); 618 fExpandButton->SetEnabled(false); 619 620 BEntry entry(&fSourceRef); 621 BPath path(&entry); 622 BString text("Creating listing for "); 623 text.Append(path.Leaf()); 624 fStatusView->SetText(text.String()); 625 fListingText->SetText(""); 626 627 fListingThread = new ExpanderThread(&message, new BMessenger(this)); 628 fListingThread->Start(); 629 630 fListingStarted = true; 631 } 632 633 634 void 635 ExpanderWindow::StopListing(void) 636 { 637 if (fListingThread) { 638 fListingThread->InterruptExternalExpander(); 639 fListingThread = NULL; 640 } 641 642 fListingStarted = false; 643 644 fShowContents->SetEnabled(true); 645 fSourceItem->SetEnabled(true); 646 fDestItem->SetEnabled(true); 647 fExpandItem->SetEnabled(true); 648 fShowItem->SetEnabled(true); 649 fStopItem->SetEnabled(false); 650 fPreferencesItem->SetEnabled(true); 651 652 fSourceButton->SetEnabled(true); 653 fDestButton->SetEnabled(true); 654 fExpandButton->SetEnabled(true); 655 fStatusView->SetText(""); 656 } 657 658 659 void 660 ExpanderWindow::CloseWindowOrKeepOpen() 661 { 662 bool expandFiles = false; 663 fSettings.FindBool("automatically_expand_files", &expandFiles); 664 665 bool closeWhenDone = false; 666 fSettings.FindBool("close_when_done", &closeWhenDone); 667 668 if (expandFiles || closeWhenDone) 669 PostMessage(B_QUIT_REQUESTED); 670 } 671 672 673 void 674 ExpanderWindow::OpenDestFolder() 675 { 676 bool openFolder = true; 677 fSettings.FindBool("open_destination_folder", &openFolder); 678 679 if (!openFolder) 680 return; 681 682 BMessage* message = new BMessage(B_REFS_RECEIVED); 683 message->AddRef("refs", &fDestRef); 684 BPath path(&fDestRef); 685 BMessenger tracker("application/x-vnd.Be-TRAK"); 686 tracker.SendMessage(message); 687 } 688 689 690 void 691 ExpanderWindow::AutoListing() 692 { 693 bool showContents = false; 694 fSettings.FindBool("show_contents_listing", &showContents); 695 696 if (!showContents) 697 return; 698 699 fShowContents->SetValue(B_CONTROL_ON); 700 fShowContents->Invoke(); 701 } 702 703 704 void 705 ExpanderWindow::AutoExpand() 706 { 707 bool expandFiles = false; 708 fSettings.FindBool("automatically_expand_files", &expandFiles); 709 710 if (!expandFiles) { 711 AutoListing(); 712 return; 713 } 714 715 fExpandButton->Invoke(); 716 } 717