1 /* 2 * Copyright 2014, Stephan Aßmus <superstippi@gmx.de>. 3 * Copyright 2017, Julian Harnath <julian.harnath@rwth-aachen.de>. 4 * Copyright 2020-2024, Andrew Lindesay <apl@lindesay.co.nz>. 5 * All rights reserved. Distributed under the terms of the MIT License. 6 */ 7 8 #include "ScreenshotWindow.h" 9 10 #include <algorithm> 11 12 #include <Autolock.h> 13 #include <Catalog.h> 14 #include <LayoutBuilder.h> 15 #include <MessageRunner.h> 16 #include <StringView.h> 17 18 #include "BarberPole.h" 19 #include "BitmapView.h" 20 #include "HaikuDepotConstants.h" 21 #include "Logger.h" 22 #include "Model.h" 23 #include "PackageUtils.h" 24 #include "SharedIcons.h" 25 #include "WebAppInterface.h" 26 27 28 #undef B_TRANSLATION_CONTEXT 29 #define B_TRANSLATION_CONTEXT "ScreenshotWindow" 30 31 32 static const rgb_color kBackgroundColor = { 51, 102, 152, 255 }; 33 // Drawn as a border around the screenshots and also what's behind their 34 // transparent regions 35 36 37 ScreenshotWindow::ScreenshotWindow(BWindow* parent, BRect frame, Model* model) 38 : 39 BWindow(frame, B_TRANSLATE("Screenshot"), 40 B_FLOATING_WINDOW_LOOK, B_FLOATING_SUBSET_WINDOW_FEEL, 41 B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS), 42 fBarberPoleShown(false), 43 fDownloadPending(false), 44 fWorkerThread(-1), 45 fModel(model) 46 { 47 AddToSubset(parent); 48 49 atomic_set(&fCurrentScreenshotIndex, 0); 50 51 fBarberPole = new BarberPole("barber pole"); 52 fBarberPole->SetExplicitMaxSize(BSize(100, B_SIZE_UNLIMITED)); 53 fBarberPole->Hide(); 54 55 fIndexView = new BStringView("screenshot index", NULL); 56 57 fToolBar = new BToolBar(); 58 fToolBar->AddAction(MSG_PREVIOUS_SCREENSHOT, this, 59 SharedIcons::IconArrowLeft22Scaled()->Bitmap(), NULL, NULL); 60 fToolBar->AddAction(MSG_NEXT_SCREENSHOT, this, SharedIcons::IconArrowRight22Scaled()->Bitmap(), 61 NULL, NULL); 62 fToolBar->AddView(fIndexView); 63 fToolBar->AddGlue(); 64 fToolBar->AddView(fBarberPole); 65 66 fScreenshotView = new BitmapView("screenshot view"); 67 fScreenshotView->SetExplicitMaxSize( 68 BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED)); 69 fScreenshotView->SetScaleBitmap(false); 70 71 BGroupView* groupView = new BGroupView(B_VERTICAL); 72 groupView->SetViewColor(kBackgroundColor); 73 74 // Build layout 75 BLayoutBuilder::Group<>(this, B_VERTICAL, 0) 76 .SetInsets(0, 3, 0, 0) 77 .Add(fToolBar) 78 .AddStrut(3) 79 .AddGroup(groupView) 80 .Add(fScreenshotView) 81 .SetInsets(B_USE_WINDOW_INSETS) 82 .End() 83 ; 84 85 fScreenshotView->SetLowColor(kBackgroundColor); 86 // Set after attaching all views to prevent it from being overridden 87 // again by BitmapView::AllAttached() 88 89 CenterOnScreen(); 90 } 91 92 93 ScreenshotWindow::~ScreenshotWindow() 94 { 95 BAutolock locker(&fLock); 96 97 if (fWorkerThread >= 0) 98 wait_for_thread(fWorkerThread, NULL); 99 } 100 101 102 bool 103 ScreenshotWindow::QuitRequested() 104 { 105 if (fOnCloseTarget.IsValid() && fOnCloseMessage.what != 0) 106 fOnCloseTarget.SendMessage(&fOnCloseMessage); 107 108 Hide(); 109 return false; 110 } 111 112 113 void 114 ScreenshotWindow::MessageReceived(BMessage* message) 115 { 116 switch (message->what) { 117 case MSG_NEXT_SCREENSHOT: 118 { 119 atomic_add(&fCurrentScreenshotIndex, 1); 120 _UpdateToolBar(); 121 _DownloadScreenshot(); 122 break; 123 } 124 125 case MSG_PREVIOUS_SCREENSHOT: 126 atomic_add(&fCurrentScreenshotIndex, -1); 127 _UpdateToolBar(); 128 _DownloadScreenshot(); 129 break; 130 131 case MSG_DOWNLOAD_START: 132 if (!fBarberPoleShown) { 133 fBarberPole->Start(); 134 fBarberPole->Show(); 135 fBarberPoleShown = true; 136 } 137 break; 138 139 case MSG_DOWNLOAD_STOP: 140 if (fBarberPoleShown) { 141 fBarberPole->Hide(); 142 fBarberPole->Stop(); 143 fBarberPoleShown = true; 144 } 145 break; 146 147 default: 148 BWindow::MessageReceived(message); 149 break; 150 } 151 } 152 153 154 void 155 ScreenshotWindow::SetOnCloseMessage( 156 const BMessenger& messenger, const BMessage& message) 157 { 158 fOnCloseTarget = messenger; 159 fOnCloseMessage = message; 160 } 161 162 163 void 164 ScreenshotWindow::SetPackage(const PackageInfoRef& package) 165 { 166 if (!package.IsSet()) 167 HDFATAL("attempt to provide an unset package"); 168 169 if (fPackage == package) 170 return; 171 172 fPackage = package; 173 BString title = B_TRANSLATE("Screenshot"); 174 PackageUtils::TitleOrName(fPackage, title); 175 SetTitle(title); 176 177 if (package.IsSet()) 178 _DownloadScreenshot(); 179 180 atomic_set(&fCurrentScreenshotIndex, 0); 181 182 _UpdateToolBar(); 183 } 184 185 186 // #pragma mark - private 187 188 189 void 190 ScreenshotWindow::_DownloadScreenshot() 191 { 192 BAutolock locker(&fLock); 193 194 if (fWorkerThread >= 0) { 195 fDownloadPending = true; 196 return; 197 } 198 199 thread_id thread = spawn_thread(&_DownloadThreadEntry, 200 "Screenshot Loader", B_NORMAL_PRIORITY, this); 201 if (thread >= 0) 202 _SetWorkerThread(thread); 203 } 204 205 206 void 207 ScreenshotWindow::_SetWorkerThread(thread_id thread) 208 { 209 if (!Lock()) 210 return; 211 212 // bool enabled = thread < 0; 213 // 214 // fPreviewsButton->SetEnabled(enabled); 215 // fNextButton->SetEnabled(enabled); 216 // fCloseButton->SetEnabled(enabled); 217 218 if (thread >= 0) { 219 fWorkerThread = thread; 220 resume_thread(fWorkerThread); 221 } else { 222 fWorkerThread = -1; 223 224 if (fDownloadPending) { 225 _DownloadScreenshot(); 226 fDownloadPending = false; 227 } 228 } 229 230 Unlock(); 231 } 232 233 234 int32 235 ScreenshotWindow::_DownloadThreadEntry(void* data) 236 { 237 ScreenshotWindow* window 238 = reinterpret_cast<ScreenshotWindow*>(data); 239 window->_DownloadThread(); 240 window->_SetWorkerThread(-1); 241 return 0; 242 } 243 244 245 void 246 ScreenshotWindow::_DownloadThread() 247 { 248 ScreenshotInfoRef info; 249 250 if (!Lock()) { 251 HDERROR("failed to lock screenshot window"); 252 return; 253 } 254 255 fScreenshotView->UnsetBitmap(); 256 _ResizeToFitAndCenter(); 257 258 if (!fPackage.IsSet()) 259 HDINFO("package not set"); 260 else { 261 PackageScreenshotInfoRef screenshotInfo = fPackage->ScreenshotInfo(); 262 263 if (!screenshotInfo.IsSet() || screenshotInfo->Count() == 0) { 264 HDINFO("package has no screenshots"); 265 } else { 266 int32 index = atomic_get(&fCurrentScreenshotIndex); 267 info = screenshotInfo->ScreenshotAtIndex(index); 268 } 269 } 270 271 Unlock(); 272 273 if (!info.IsSet()) { 274 HDINFO("screenshot not set"); 275 return; 276 } 277 278 // Only indicate being busy with the download if it takes a little while 279 BMessenger messenger(this); 280 BMessageRunner delayedMessenger(messenger, 281 new BMessage(MSG_DOWNLOAD_START), 282 kProgressIndicatorDelay, 1); 283 284 BitmapHolderRef screenshot; 285 286 // Retrieve screenshot from web-app 287 status_t status = fModel->GetPackageScreenshotRepository()->LoadScreenshot( 288 ScreenshotCoordinate(info->Code(), info->Width(), info->Height()), screenshot); 289 290 delayedMessenger.SetCount(0); 291 messenger.SendMessage(MSG_DOWNLOAD_STOP); 292 293 if (status == B_OK && Lock()) { 294 HDINFO("got screenshot"); 295 fScreenshot = screenshot; 296 fScreenshotView->SetBitmap(fScreenshot); 297 _ResizeToFitAndCenter(); 298 Unlock(); 299 } else 300 HDERROR("failed to download screenshot"); 301 } 302 303 304 BSize 305 ScreenshotWindow::_MaxWidthAndHeightOfAllScreenshots() 306 { 307 BSize size(0, 0); 308 309 // Find out dimensions of the largest screenshot of this package 310 if (fPackage.IsSet()) { 311 PackageScreenshotInfoRef screenshotInfo = fPackage->ScreenshotInfo(); 312 int count = 0; 313 314 if (screenshotInfo.IsSet()) 315 count = screenshotInfo->Count(); 316 317 for(int32 i = 0; i < count; i++) { 318 const ScreenshotInfoRef& screenshot = screenshotInfo->ScreenshotAtIndex(i); 319 320 if (screenshot.IsSet()) { 321 float w = static_cast<float>(screenshot->Width()); 322 float h = static_cast<float>(screenshot->Height()); 323 if (w > size.Width()) 324 size.SetWidth(w); 325 if (h > size.Height()) 326 size.SetHeight(h); 327 } 328 } 329 } 330 331 return size; 332 } 333 334 335 void 336 ScreenshotWindow::_ResizeToFitAndCenter() 337 { 338 fScreenshotView->SetExplicitMinSize(_MaxWidthAndHeightOfAllScreenshots()); 339 Layout(false); 340 341 // TODO: Limit window size to screen size (with a little margin), 342 // the image should then become scrollable. 343 344 float minWidth; 345 float minHeight; 346 GetSizeLimits(&minWidth, NULL, &minHeight, NULL); 347 ResizeTo(minWidth, minHeight); 348 CenterOnScreen(); 349 } 350 351 352 void 353 ScreenshotWindow::_UpdateToolBar() 354 { 355 int32 numScreenshots = 0; 356 357 if (fPackage.IsSet()) { 358 PackageScreenshotInfoRef screenshotInfo = fPackage->ScreenshotInfo(); 359 if (screenshotInfo.IsSet()) 360 numScreenshots = screenshotInfo->Count(); 361 } 362 363 const int32 currentIndex = atomic_get(&fCurrentScreenshotIndex); 364 365 fToolBar->SetActionEnabled(MSG_PREVIOUS_SCREENSHOT, 366 currentIndex > 0); 367 fToolBar->SetActionEnabled(MSG_NEXT_SCREENSHOT, 368 currentIndex < numScreenshots - 1); 369 370 BString text; 371 text << currentIndex + 1; 372 text << " / "; 373 text << numScreenshots; 374 fIndexView->SetText(text); 375 } 376