1 /* 2 * Copyright 2010 Wim van der Meer <WPJvanderMeer@gmail.com> 3 * Copyright Karsten Heimrich, host.haiku@gmx.de. 4 * All rights reserved. Distributed under the terms of the MIT License. 5 * 6 * Authors: 7 * Karsten Heimrich 8 * Fredrik Modéen 9 * Christophe Huriaux 10 * Wim van der Meer 11 */ 12 13 14 #include "ScreenshotWindow.h" 15 16 #include <stdlib.h> 17 18 #include <Alert.h> 19 #include <Application.h> 20 #include <Bitmap.h> 21 #include <Box.h> 22 #include <Button.h> 23 #include <Catalog.h> 24 #include <CheckBox.h> 25 #include <File.h> 26 #include <FilePanel.h> 27 #include <FindDirectory.h> 28 #include <GridLayoutBuilder.h> 29 #include <GroupLayoutBuilder.h> 30 #include <Locale.h> 31 #include <Menu.h> 32 #include <MenuField.h> 33 #include <MenuItem.h> 34 #include <Path.h> 35 #include <Roster.h> 36 #include <String.h> 37 #include <StringView.h> 38 #include <TextControl.h> 39 #include <TranslationUtils.h> 40 41 #include "PreviewView.h" 42 #include "Utility.h" 43 44 45 enum { 46 kActiveWindow, 47 kIncludeBorder, 48 kIncludeCursor, 49 kNewScreenshot, 50 kImageFormat, 51 kLocationChanged, 52 kChooseLocation, 53 kSaveScreenshot 54 }; 55 56 57 class DirectoryRefFilter : public BRefFilter { 58 public: 59 virtual ~DirectoryRefFilter() 60 { 61 } 62 63 virtual bool Filter(const entry_ref* ref, BNode* node, 64 struct stat_beos* stat, const char* filetype) 65 { 66 return node->IsDirectory(); 67 } 68 }; 69 70 71 #undef B_TRANSLATE_CONTEXT 72 #define B_TRANSLATE_CONTEXT "ScreenshotWindow" 73 74 75 ScreenshotWindow::ScreenshotWindow(const Utility& utility, bool silent, 76 bool clipboard) 77 : 78 BWindow(BRect(0, 0, 200.0, 100.0), B_TRANSLATE("Screenshot"), 79 B_TITLED_WINDOW, B_NOT_ZOOMABLE | B_NOT_RESIZABLE | B_AVOID_FRONT 80 | B_QUIT_ON_WINDOW_CLOSE | B_AUTO_UPDATE_SIZE_LIMITS 81 | B_CLOSE_ON_ESCAPE), 82 fUtility(utility), 83 fDelayControl(NULL), 84 fScreenshot(NULL), 85 fOutputPathPanel(NULL), 86 fLastSelectedPath(NULL), 87 fDelay(0), 88 fIncludeBorder(false), 89 fIncludeCursor(false), 90 fGrabActiveWindow(false), 91 fOutputFilename(NULL), 92 fExtension(""), 93 fImageFileType(B_PNG_FORMAT) 94 { 95 // _ReadSettings() needs a valid fOutputPathMenu 96 fOutputPathMenu = new BMenu(B_TRANSLATE("Please select")); 97 _ReadSettings(); 98 99 // _NewScreenshot() needs a valid fNameControl 100 BString name(B_TRANSLATE(fUtility.sDefaultFileNameBase)); 101 name << 1; 102 name = _FindValidFileName(name.String()); 103 fNameControl = new BTextControl("", B_TRANSLATE("Name:"), name, NULL); 104 105 // Check if fUtility contains valid data 106 if (fUtility.wholeScreen == NULL) { 107 _NewScreenshot(silent, clipboard); 108 return; 109 } 110 111 fScreenshot = fUtility.MakeScreenshot(fIncludeCursor, fGrabActiveWindow, 112 fIncludeBorder); 113 114 fActiveWindow = new BCheckBox(B_TRANSLATE("Capture active window"), 115 new BMessage(kActiveWindow)); 116 if (fGrabActiveWindow) 117 fActiveWindow->SetValue(B_CONTROL_ON); 118 119 fWindowBorder = new BCheckBox(B_TRANSLATE("Include window border"), 120 new BMessage(kIncludeBorder)); 121 if (fIncludeBorder) 122 fWindowBorder->SetValue(B_CONTROL_ON); 123 if (!fGrabActiveWindow) 124 fWindowBorder->SetEnabled(false); 125 126 fShowCursor = new BCheckBox(B_TRANSLATE("Include mouse pointer"), 127 new BMessage(kIncludeCursor)); 128 if (fIncludeCursor) 129 fShowCursor->SetValue(B_CONTROL_ON); 130 131 BString delay; 132 delay << fDelay / 1000000; 133 fDelayControl = new BTextControl("", B_TRANSLATE("Delay:"), delay.String(), 134 NULL); 135 _DisallowChar(fDelayControl->TextView()); 136 fDelayControl->TextView()->SetAlignment(B_ALIGN_RIGHT); 137 BStringView* seconds = new BStringView("", B_TRANSLATE("seconds")); 138 seconds->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET)); 139 140 BMenuField* menuField2 = new BMenuField(B_TRANSLATE("Save in:"), 141 fOutputPathMenu); 142 143 fTranslatorMenu = new BMenu(B_TRANSLATE("Please select")); 144 _SetupTranslatorMenu(); 145 BMenuField* menuField = new BMenuField(B_TRANSLATE("Save as:"), 146 fTranslatorMenu); 147 148 BBox* divider = new BBox(B_FANCY_BORDER, NULL); 149 divider->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, 1)); 150 151 BButton* saveScreenshot = new BButton("", B_TRANSLATE("Save"), 152 new BMessage(kSaveScreenshot)); 153 154 fPreview = new PreviewView(); 155 156 BGridLayout* gridLayout = BGridLayoutBuilder(0.0, 5.0) 157 .Add(fNameControl->CreateLabelLayoutItem(), 0, 0) 158 .Add(fNameControl->CreateTextViewLayoutItem(), 1, 0) 159 .Add(menuField->CreateLabelLayoutItem(), 0, 1) 160 .Add(menuField->CreateMenuBarLayoutItem(), 1, 1) 161 .Add(menuField2->CreateLabelLayoutItem(), 0, 2) 162 .Add(menuField2->CreateMenuBarLayoutItem(), 1, 2); 163 gridLayout->SetMinColumnWidth(1, 164 menuField->StringWidth("SomethingLongHere")); 165 166 SetLayout(new BGroupLayout(B_HORIZONTAL)); 167 168 AddChild(BGroupLayoutBuilder(B_VERTICAL) 169 .Add(BGroupLayoutBuilder(B_HORIZONTAL, 10.0) 170 .Add(fPreview) 171 .AddGroup(B_VERTICAL) 172 .Add(fActiveWindow) 173 .Add(fWindowBorder) 174 .Add(fShowCursor) 175 .AddGroup(B_HORIZONTAL, 5.0) 176 .Add(fDelayControl->CreateLabelLayoutItem()) 177 .Add(fDelayControl->CreateTextViewLayoutItem()) 178 .Add(seconds) 179 .End() 180 .AddStrut(10.0) 181 .Add(gridLayout->View()) 182 .AddGlue() 183 .End()) 184 .AddStrut(10) 185 .Add(divider) 186 .AddStrut(10) 187 .AddGroup(B_HORIZONTAL, 10.0) 188 .Add(new BButton("", B_TRANSLATE("Copy to clipboard"), 189 new BMessage(B_COPY))) 190 .Add(new BButton("", B_TRANSLATE("New screenshot"), 191 new BMessage(kNewScreenshot))) 192 .AddGlue() 193 .Add(saveScreenshot) 194 .End() 195 .SetInsets(10.0, 10.0, 10.0, 10.0) 196 ); 197 198 saveScreenshot->MakeDefault(true); 199 200 _UpdatePreviewPanel(); 201 _UpdateFilenameSelection(); 202 203 CenterOnScreen(); 204 Show(); 205 } 206 207 208 ScreenshotWindow::~ScreenshotWindow() 209 { 210 if (fOutputPathPanel) 211 delete fOutputPathPanel->RefFilter(); 212 213 delete fOutputPathPanel; 214 delete fScreenshot; 215 } 216 217 218 void 219 ScreenshotWindow::MessageReceived(BMessage* message) 220 { 221 switch (message->what) { 222 case kActiveWindow: 223 fGrabActiveWindow = false; 224 if (fActiveWindow->Value() == B_CONTROL_ON) 225 fGrabActiveWindow = true; 226 227 fWindowBorder->SetEnabled(fGrabActiveWindow); 228 229 delete fScreenshot; 230 fScreenshot = fUtility.MakeScreenshot(fIncludeCursor, 231 fGrabActiveWindow, fIncludeBorder); 232 _UpdatePreviewPanel(); 233 break; 234 235 case kIncludeBorder: 236 fIncludeBorder = (fWindowBorder->Value() == B_CONTROL_ON); 237 delete fScreenshot; 238 fScreenshot = fUtility.MakeScreenshot(fIncludeCursor, 239 fGrabActiveWindow, fIncludeBorder); 240 _UpdatePreviewPanel(); 241 break; 242 243 case kIncludeCursor: 244 fIncludeCursor = (fShowCursor->Value() == B_CONTROL_ON); 245 delete fScreenshot; 246 fScreenshot = fUtility.MakeScreenshot(fIncludeCursor, 247 fGrabActiveWindow, fIncludeBorder); 248 _UpdatePreviewPanel(); 249 break; 250 251 case kNewScreenshot: 252 fDelay = (atoi(fDelayControl->Text()) * 1000000) + 50000; 253 _NewScreenshot(); 254 break; 255 256 case kImageFormat: 257 message->FindInt32("be:type", &fImageFileType); 258 fNameControl->SetText(_FindValidFileName( 259 fNameControl->Text()).String()); 260 _UpdateFilenameSelection(); 261 break; 262 263 case kLocationChanged: 264 { 265 void* source = NULL; 266 if (message->FindPointer("source", &source) == B_OK) 267 fLastSelectedPath = static_cast<BMenuItem*> (source); 268 269 fNameControl->SetText(_FindValidFileName( 270 fNameControl->Text()).String()); 271 272 _UpdateFilenameSelection(); 273 break; 274 } 275 276 case kChooseLocation: 277 { 278 if (!fOutputPathPanel) { 279 BMessenger target(this); 280 fOutputPathPanel = new BFilePanel(B_OPEN_PANEL, &target, NULL, 281 B_DIRECTORY_NODE, false, NULL, new DirectoryRefFilter()); 282 fOutputPathPanel->Window()->SetTitle( 283 B_TRANSLATE("Choose folder")); 284 fOutputPathPanel->SetButtonLabel(B_DEFAULT_BUTTON, 285 B_TRANSLATE("Select")); 286 fOutputPathPanel->SetButtonLabel(B_CANCEL_BUTTON, 287 B_TRANSLATE("Cancel")); 288 } 289 fOutputPathPanel->Show(); 290 break; 291 } 292 293 case B_CANCEL: 294 fLastSelectedPath->SetMarked(true); 295 break; 296 297 case kSaveScreenshot: 298 if (_SaveScreenshot() == B_OK) 299 be_app->PostMessage(B_QUIT_REQUESTED); 300 break; 301 302 case B_COPY: 303 fUtility.CopyToClipboard(fScreenshot); 304 break; 305 306 default: 307 BWindow::MessageReceived(message); 308 break; 309 } 310 } 311 312 313 void 314 ScreenshotWindow::Quit() 315 { 316 if (fUtility.wholeScreen != NULL) 317 _WriteSettings(); 318 BWindow::Quit(); 319 } 320 321 322 void 323 ScreenshotWindow::_NewScreenshot(bool silent, bool clipboard) 324 { 325 BMessage message(B_ARGV_RECEIVED); 326 int32 argc = 3; 327 BString delay; 328 delay << fDelay / 1000000; 329 message.AddString("argv", "screenshot"); 330 message.AddString("argv", "--delay"); 331 message.AddString("argv", delay); 332 333 if (silent || clipboard) { 334 if (silent) { 335 argc++; 336 message.AddString("argv", "--silent"); 337 } 338 if (clipboard) { 339 argc++; 340 message.AddString("argv", "--clipboard"); 341 } 342 if (fIncludeBorder) { 343 argc++; 344 message.AddString("argv", "--border"); 345 } 346 if (fIncludeCursor) { 347 argc++; 348 message.AddString("argv", "--mouse-pointer"); 349 } 350 if (fGrabActiveWindow) { 351 argc++; 352 message.AddString("argv", "--window"); 353 } 354 if (fLastSelectedPath) { 355 BPath path(_GetDirectory()); 356 if (path != NULL) { 357 path.Append(fNameControl->Text()); 358 argc++; 359 message.AddString("argv", path.Path()); 360 } 361 } 362 } 363 message.AddInt32("argc", argc); 364 365 be_roster->Launch("application/x-vnd.haiku-screenshot-cli", &message); 366 be_app->PostMessage(B_QUIT_REQUESTED); 367 } 368 369 370 void 371 ScreenshotWindow::_UpdatePreviewPanel() 372 { 373 float height = 150.0f; 374 375 float width = (fScreenshot->Bounds().Width() 376 / fScreenshot->Bounds().Height()) * height; 377 378 // to prevent a preview way too wide 379 if (width > 400.0f) { 380 width = 400.0f; 381 height = (fScreenshot->Bounds().Height() 382 / fScreenshot->Bounds().Width()) * width; 383 } 384 385 fPreview->SetExplicitMinSize(BSize(width, height)); 386 387 fPreview->ClearViewBitmap(); 388 fPreview->SetViewBitmap(fScreenshot, fScreenshot->Bounds(), 389 fPreview->Bounds(), B_FOLLOW_ALL, B_FILTER_BITMAP_BILINEAR); 390 } 391 392 393 void 394 ScreenshotWindow::_DisallowChar(BTextView* textView) 395 { 396 for (uint32 i = 0; i < '0'; ++i) 397 textView->DisallowChar(i); 398 399 for (uint32 i = '9' + 1; i < 255; ++i) 400 textView->DisallowChar(i); 401 } 402 403 404 void 405 ScreenshotWindow::_SetupOutputPathMenu(const BMessage& settings) 406 { 407 fOutputPathMenu->SetLabelFromMarked(true); 408 409 BString lastSelectedPath; 410 settings.FindString("lastSelectedPath", &lastSelectedPath); 411 412 BPath path; 413 find_directory(B_USER_DIRECTORY, &path); 414 415 BString label(B_TRANSLATE("Home folder")); 416 _AddItemToPathMenu(path.Path(), label, 0, 417 (path.Path() == lastSelectedPath)); 418 419 path.Append("Desktop"); 420 label.SetTo(B_TRANSLATE("Desktop")); 421 _AddItemToPathMenu(path.Path(), label, 0, ( 422 path.Path() == lastSelectedPath)); 423 424 find_directory(B_BEOS_ETC_DIRECTORY, &path); 425 path.Append("artwork"); 426 427 label.SetTo(B_TRANSLATE("Artwork folder")); 428 _AddItemToPathMenu(path.Path(), label, 2, 429 (path.Path() == lastSelectedPath)); 430 431 int32 i = 0; 432 BString userPath; 433 while (settings.FindString("path", ++i, &userPath) == B_OK) { 434 _AddItemToPathMenu(userPath.String(), userPath, 3, 435 (userPath == lastSelectedPath)); 436 } 437 438 if (!fLastSelectedPath) { 439 if (settings.IsEmpty() || lastSelectedPath.Length() == 0) { 440 fOutputPathMenu->ItemAt(1)->SetMarked(true); 441 fLastSelectedPath = fOutputPathMenu->ItemAt(1); 442 } else 443 _AddItemToPathMenu(lastSelectedPath.String(), lastSelectedPath, 3, 444 true); 445 } 446 447 fOutputPathMenu->AddItem(new BSeparatorItem()); 448 fOutputPathMenu->AddItem(new BMenuItem(B_TRANSLATE("Choose folder..."), 449 new BMessage(kChooseLocation))); 450 } 451 452 453 void 454 ScreenshotWindow::_AddItemToPathMenu(const char* path, BString& label, 455 int32 index, bool markItem) 456 { 457 BMessage* message = new BMessage(kLocationChanged); 458 message->AddString("path", path); 459 460 fOutputPathMenu->TruncateString(&label, B_TRUNCATE_MIDDLE, 461 fOutputPathMenu->StringWidth("SomethingLongHere")); 462 463 fOutputPathMenu->AddItem(new BMenuItem(label.String(), message), index); 464 465 if (markItem) { 466 fOutputPathMenu->ItemAt(index)->SetMarked(true); 467 fLastSelectedPath = fOutputPathMenu->ItemAt(index); 468 } 469 } 470 471 472 void 473 ScreenshotWindow::_UpdateFilenameSelection() 474 { 475 fNameControl->MakeFocus(true); 476 fNameControl->TextView()->Select(0, fNameControl->TextView()->TextLength() 477 - fExtension.Length()); 478 479 fNameControl->TextView()->ScrollToSelection(); 480 } 481 482 483 void 484 ScreenshotWindow::_SetupTranslatorMenu() 485 { 486 BMessage message(kImageFormat); 487 fTranslatorMenu = new BMenu("Please select"); 488 BTranslationUtils::AddTranslationItems(fTranslatorMenu, B_TRANSLATOR_BITMAP, 489 &message, NULL, NULL, NULL); 490 491 fTranslatorMenu->SetLabelFromMarked(true); 492 493 if (fTranslatorMenu->ItemAt(0)) 494 fTranslatorMenu->ItemAt(0)->SetMarked(true); 495 496 int32 imageFileType; 497 for (int32 i = 0; i < fTranslatorMenu->CountItems(); ++i) { 498 BMenuItem* item = fTranslatorMenu->ItemAt(i); 499 if (item && item->Message()) { 500 item->Message()->FindInt32("be:type", &imageFileType); 501 if (fImageFileType == imageFileType) { 502 item->SetMarked(true); 503 MessageReceived(item->Message()); 504 break; 505 } 506 } 507 } 508 } 509 510 511 status_t 512 ScreenshotWindow::_SaveScreenshot() 513 { 514 if (!fScreenshot || !fLastSelectedPath) 515 return B_ERROR; 516 517 BPath path(_GetDirectory()); 518 519 if (path == NULL) 520 return B_ERROR; 521 522 path.Append(fNameControl->Text()); 523 524 BEntry entry; 525 entry.SetTo(path.Path()); 526 527 if (entry.Exists()) { 528 BAlert* overwriteAlert = new BAlert( 529 B_TRANSLATE("overwrite"), 530 B_TRANSLATE("This file already exists.\n Are you sure would " 531 "you like to overwrite it?"), 532 B_TRANSLATE("Cancel"), 533 B_TRANSLATE("Overwrite"), 534 NULL, B_WIDTH_AS_USUAL, B_EVEN_SPACING, B_WARNING_ALERT); 535 536 overwriteAlert->SetShortcut(0, B_ESCAPE); 537 538 if (overwriteAlert->Go() == 0) 539 return B_CANCELED; 540 } 541 542 return fUtility.Save(&fScreenshot, path.Path(), fImageFileType); 543 } 544 545 546 BString 547 ScreenshotWindow::_FindValidFileName(const char* name) 548 { 549 BString baseName(name); 550 551 if (fExtension.Compare("")) 552 baseName.RemoveLast(fExtension); 553 554 if (!fLastSelectedPath) 555 return baseName; 556 557 BPath orgPath(_GetDirectory()); 558 if (orgPath == NULL) 559 return baseName; 560 561 fExtension = BString(fUtility.GetFileNameExtension(fImageFileType)); 562 563 BPath outputPath = orgPath; 564 BString fileName; 565 fileName << baseName << fExtension; 566 outputPath.Append(fileName); 567 568 if (!BEntry(outputPath.Path()).Exists()) 569 return fileName; 570 571 if (baseName.FindFirst(B_TRANSLATE(fUtility.sDefaultFileNameBase)) == 0) 572 baseName.SetTo(fUtility.sDefaultFileNameBase); 573 574 BEntry entry; 575 int32 index = 1; 576 577 do { 578 fileName = ""; 579 fileName << baseName << index++ << fExtension; 580 outputPath.SetTo(orgPath.Path()); 581 outputPath.Append(fileName); 582 entry.SetTo(outputPath.Path()); 583 } while (entry.Exists()); 584 585 return fileName; 586 } 587 588 589 BPath 590 ScreenshotWindow::_GetDirectory() 591 { 592 BPath path; 593 594 BMessage* message = fLastSelectedPath->Message(); 595 const char* stringPath; 596 if (message && message->FindString("path", &stringPath) == B_OK) 597 path.SetTo(stringPath); 598 599 return path; 600 } 601 602 603 void 604 ScreenshotWindow::_ReadSettings() 605 { 606 BMessage settings; 607 608 BPath settingsPath; 609 if (find_directory(B_USER_SETTINGS_DIRECTORY, &settingsPath) != B_OK) 610 return; 611 612 settingsPath.Append("Screenshot_settings"); 613 614 BFile file(settingsPath.Path(), B_READ_ONLY); 615 if (file.InitCheck() == B_OK) 616 settings.Unflatten(&file); 617 618 if (settings.FindInt32("type", &fImageFileType) != B_OK) 619 fImageFileType = B_PNG_FORMAT; 620 settings.FindBool("includeBorder", &fIncludeBorder); 621 settings.FindBool("includeCursor", &fIncludeCursor); 622 settings.FindBool("grabActiveWindow", &fGrabActiveWindow); 623 settings.FindInt64("delay", &fDelay); 624 settings.FindString("outputFilename", &fOutputFilename); 625 626 _SetupOutputPathMenu(settings); 627 } 628 629 630 void 631 ScreenshotWindow::_WriteSettings() 632 { 633 if (fDelayControl) 634 fDelay = (atoi(fDelayControl->Text()) * 1000000) + 50000; 635 636 BMessage settings; 637 638 settings.AddInt32("type", fImageFileType); 639 settings.AddBool("includeBorder", fIncludeBorder); 640 settings.AddBool("includeCursor", fIncludeCursor); 641 settings.AddBool("grabActiveWindow", fGrabActiveWindow); 642 settings.AddInt64("delay", fDelay); 643 settings.AddString("outputFilename", fOutputFilename); 644 645 BString path; 646 int32 count = fOutputPathMenu->CountItems(); 647 if (count > 5) { 648 for (int32 i = count - 3; i > count - 8 && i > 2; --i) { 649 BMenuItem* item = fOutputPathMenu->ItemAt(i); 650 if (item) { 651 BMessage* msg = item->Message(); 652 if (msg && msg->FindString("path", &path) == B_OK) 653 settings.AddString("path", path.String()); 654 } 655 } 656 } 657 658 if (fLastSelectedPath) { 659 BMessage* msg = fLastSelectedPath->Message(); 660 if (msg && msg->FindString("path", &path) == B_OK) 661 settings.AddString("lastSelectedPath", path.String()); 662 } 663 664 BPath settingsPath; 665 if (find_directory(B_USER_SETTINGS_DIRECTORY, &settingsPath) != B_OK) 666 return; 667 settingsPath.Append("Screenshot_settings"); 668 669 BFile file(settingsPath.Path(), B_CREATE_FILE | B_ERASE_FILE 670 | B_WRITE_ONLY); 671 if (file.InitCheck() == B_OK) { 672 ssize_t size; 673 settings.Flatten(&file, &size); 674 } 675 } 676