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