1 /* 2 * Copyright 2003-2009, Haiku, Inc. All Rights Reserved. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Jonas Sundström, jonas@kirilla.com 7 */ 8 9 10 #include "ZipOMaticWindow.h" 11 12 #include <stdio.h> 13 #include <stdlib.h> 14 15 #include <Alert.h> 16 #include <Application.h> 17 #include <Catalog.h> 18 #include <Directory.h> 19 #include <File.h> 20 #include <FindDirectory.h> 21 #include <GroupLayout.h> 22 #include <LayoutBuilder.h> 23 #include <Locale.h> 24 #include <Path.h> 25 #include <Roster.h> 26 #include <Screen.h> 27 #include <SeparatorView.h> 28 #include <String.h> 29 30 #include "ZipOMatic.h" 31 #include "ZipOMaticActivity.h" 32 #include "ZipOMaticMisc.h" 33 #include "ZipperThread.h" 34 35 36 #define B_TRANSLATE_CONTEXT "file:ZipOMaticWindow.cpp" 37 38 39 ZippoWindow::ZippoWindow(BList windowList, bool keepOpen) 40 : 41 BWindow(BRect(0, 0, 0, 0), "Zip-O-Matic", B_TITLED_WINDOW, 42 B_NOT_RESIZABLE | B_AUTO_UPDATE_SIZE_LIMITS | B_NOT_ZOOMABLE), 43 fWindowList(windowList), 44 fThread(NULL), 45 fKeepOpen(keepOpen), 46 fZippingWasStopped(false), 47 fFileCount(0), 48 fWindowInvoker(new BInvoker(new BMessage(ZIPPO_QUIT_OR_CONTINUE), NULL, 49 this)) 50 { 51 fActivityView = new Activity("activity"); 52 fActivityView->SetExplicitMinSize(BSize(171, 17)); 53 54 fArchiveNameView = new BStringView("archive_text", ""); 55 fArchiveNameView->SetExplicitAlignment(BAlignment(B_ALIGN_LEFT, 56 B_ALIGN_VERTICAL_UNSET)); 57 58 fZipOutputView = new BStringView("output_text", 59 B_TRANSLATE("Drop files to zip.")); 60 fZipOutputView->SetExplicitAlignment(BAlignment(B_ALIGN_LEFT, 61 B_ALIGN_VERTICAL_UNSET)); 62 63 fStopButton = new BButton("stop", B_TRANSLATE("Stop"), 64 new BMessage(B_QUIT_REQUESTED)); 65 fStopButton->SetEnabled(false); 66 fStopButton->SetExplicitAlignment(BAlignment(B_ALIGN_RIGHT, 67 B_ALIGN_VERTICAL_UNSET)); 68 69 BSeparatorView* separator = new BSeparatorView(B_HORIZONTAL); 70 71 BLayoutBuilder::Group<>(this) 72 .AddGroup(B_VERTICAL, 10) 73 .Add(fActivityView) 74 .Add(fArchiveNameView) 75 .Add(fZipOutputView) 76 .Add(separator) 77 .Add(fStopButton) 78 .SetInsets(14, 14, 14, 14) 79 .End() 80 .End(); 81 82 _FindBestPlacement(); 83 } 84 85 86 ZippoWindow::~ZippoWindow() 87 { 88 delete fWindowInvoker; 89 } 90 91 92 void 93 ZippoWindow::MessageReceived(BMessage* message) 94 { 95 switch (message->what) { 96 case B_REFS_RECEIVED: 97 case B_SIMPLE_DATA: 98 if (IsZipping()) { 99 message->what = B_REFS_RECEIVED; 100 be_app_messenger.SendMessage(message); 101 } else { 102 _StartZipping(message); 103 } 104 break; 105 106 case ZIPPO_THREAD_EXIT: 107 fThread = NULL; 108 fActivityView->Stop(); 109 fStopButton->SetEnabled(false); 110 fArchiveNameView->SetText(" "); 111 if (fZippingWasStopped) 112 fZipOutputView->SetText(B_TRANSLATE("Stopped")); 113 else 114 fZipOutputView->SetText(B_TRANSLATE("Archive created OK")); 115 116 _CloseWindowOrKeepOpen(); 117 break; 118 119 case ZIPPO_THREAD_EXIT_ERROR: 120 // TODO: figure out why this case does not happen when it should 121 fThread = NULL; 122 fActivityView->Stop(); 123 fStopButton->SetEnabled(false); 124 fArchiveNameView->SetText(""); 125 fZipOutputView->SetText(B_TRANSLATE("Error creating archive")); 126 break; 127 128 case ZIPPO_TASK_DESCRIPTION: 129 { 130 BString filename; 131 if (message->FindString("archive_filename", &filename) == B_OK) { 132 fArchiveName = filename; 133 BString temp(B_TRANSLATE("Creating archive: %s")); 134 temp.ReplaceFirst("%s", filename.String()); 135 fArchiveNameView->SetText(temp.String()); 136 } 137 break; 138 } 139 140 case ZIPPO_LINE_OF_STDOUT: 141 { 142 BString string; 143 if (message->FindString("zip_output", &string) == B_OK) { 144 if (string.FindFirst("Adding: ") == 0) { 145 146 // This is a workaround for the current window resizing 147 // behavior as the window resizes for each line of output. 148 // Instead of showing the true output of /bin/zip 149 // we display a file count and whether the archive is 150 // being created (added to) or if we're updating an 151 // already existing archive. 152 153 BString output; 154 fFileCount++; 155 BString count; 156 count << fFileCount; 157 158 if (fFileCount == 1) { 159 output << B_TRANSLATE("1 file added."); 160 } else { 161 output << B_TRANSLATE("%ld files added."); 162 output.ReplaceFirst("%ld", count.String()); 163 } 164 165 fZipOutputView->SetText(output.String()); 166 } else { 167 fZipOutputView->SetText(string.String()); 168 } 169 } 170 break; 171 } 172 173 case ZIPPO_QUIT_OR_CONTINUE: 174 { 175 int32 which_button = -1; 176 if (message->FindInt32("which", &which_button) == B_OK) { 177 if (which_button == 0) { 178 StopZipping(); 179 } else { 180 if (fThread != NULL) 181 fThread->ResumeExternalZip(); 182 183 fActivityView->Start(); 184 } 185 } 186 break; 187 } 188 189 default: 190 BWindow::MessageReceived(message); 191 break; 192 } 193 } 194 195 196 bool 197 ZippoWindow::QuitRequested() 198 { 199 if (!IsZipping()) { 200 BMessage message(ZIPPO_WINDOW_QUIT); 201 message.AddRect("frame", Frame()); 202 be_app_messenger.SendMessage(&message); 203 return true; 204 } else { 205 fThread->SuspendExternalZip(); 206 fActivityView->Pause(); 207 208 BString message; 209 message << B_TRANSLATE("Are you sure you want to stop creating this " 210 "archive?"); 211 message << "\n\n"; 212 message << B_TRANSLATE("Filename: %s"); 213 message << "\n"; 214 message.ReplaceFirst("%s", fArchiveName.String()); 215 216 BAlert* alert = new BAlert(NULL, message.String(), B_TRANSLATE("Stop"), 217 B_TRANSLATE("Continue"), NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 218 alert->Go(fWindowInvoker); 219 220 return false; 221 } 222 } 223 224 225 void 226 ZippoWindow::_StartZipping(BMessage* message) 227 { 228 fStopButton->SetEnabled(true); 229 fActivityView->Start(); 230 fFileCount = 0; 231 232 fThread = new ZipperThread(message, this); 233 fThread->Start(); 234 235 fZippingWasStopped = false; 236 } 237 238 239 void 240 ZippoWindow::StopZipping() 241 { 242 fZippingWasStopped = true; 243 244 fStopButton->SetEnabled(false); 245 fActivityView->Stop(); 246 247 fThread->InterruptExternalZip(); 248 fThread->Quit(); 249 250 status_t status = B_OK; 251 fThread->WaitForThread(&status); 252 fThread = NULL; 253 254 fArchiveNameView->SetText(" "); 255 fZipOutputView->SetText(B_TRANSLATE("Stopped")); 256 257 _CloseWindowOrKeepOpen(); 258 } 259 260 261 bool 262 ZippoWindow::IsZipping() 263 { 264 if (fThread == NULL) 265 return false; 266 else 267 return true; 268 } 269 270 271 void 272 ZippoWindow::_CloseWindowOrKeepOpen() 273 { 274 if (!fKeepOpen) 275 PostMessage(B_QUIT_REQUESTED); 276 } 277 278 279 void 280 ZippoWindow::_FindBestPlacement() 281 { 282 CenterOnScreen(); 283 284 BScreen screen; 285 BRect centeredRect = Frame(); 286 BRect tryRect = centeredRect; 287 BList tryRectList; 288 289 if (!screen.Frame().Contains(centeredRect)) 290 return; 291 292 // build a list of possible locations 293 tryRectList.AddItem(new BRect(centeredRect)); 294 295 // up and left 296 direction primaryDirection = up; 297 while (true) { 298 _OffsetRect(&tryRect, primaryDirection); 299 300 if (!screen.Frame().Contains(tryRect)) 301 _OffscreenBounceBack(&tryRect, &primaryDirection, left); 302 303 if (!screen.Frame().Contains(tryRect)) 304 break; 305 306 tryRectList.AddItem(new BRect(tryRect)); 307 } 308 309 // down and right 310 primaryDirection = down; 311 tryRect = centeredRect; 312 while (true) { 313 _OffsetRect(&tryRect, primaryDirection); 314 315 if (!screen.Frame().Contains(tryRect)) 316 _OffscreenBounceBack(&tryRect, &primaryDirection, right); 317 318 if (!screen.Frame().Contains(tryRect)) 319 break; 320 321 tryRectList.AddItem(new BRect(tryRect)); 322 } 323 324 // remove rects that overlap an existing window 325 for (int32 i = 0;; i++) { 326 BWindow* win = static_cast<BWindow*>(fWindowList.ItemAt(i)); 327 if (win == NULL) 328 break; 329 330 ZippoWindow* window = dynamic_cast<ZippoWindow*>(win); 331 if (window == NULL) 332 continue; 333 334 if (window == this) 335 continue; 336 337 if (window->Lock()) { 338 BRect frame = window->Frame(); 339 for (int32 m = 0;; m++) { 340 BRect* rect = static_cast<BRect*>(tryRectList.ItemAt(m)); 341 if (rect == NULL) 342 break; 343 344 if (frame.Intersects(*rect)) { 345 tryRectList.RemoveItem(m); 346 delete rect; 347 m--; 348 } 349 } 350 window->Unlock(); 351 } 352 } 353 354 // find nearest rect 355 bool gotRect = false; 356 BRect nearestRect(0, 0, 0, 0); 357 358 while (true) { 359 BRect* rect = static_cast<BRect*>(tryRectList.RemoveItem((int32)0)); 360 if (rect == NULL) 361 break; 362 363 nearestRect = _NearestRect(centeredRect, nearestRect, *rect); 364 gotRect = true; 365 delete rect; 366 } 367 368 if (gotRect) 369 MoveTo(nearestRect.LeftTop()); 370 } 371 372 373 void 374 ZippoWindow::_OffsetRect(BRect* rect, direction whereTo) 375 { 376 float width = rect->Width(); 377 float height = rect->Height(); 378 379 switch (whereTo) { 380 case up: 381 rect->OffsetBy(0, -(height * 1.5)); 382 break; 383 384 case down: 385 rect->OffsetBy(0, height * 1.5); 386 break; 387 388 case left: 389 rect->OffsetBy(-(width * 1.5), 0); 390 break; 391 392 case right: 393 rect->OffsetBy(width * 1.5, 0); 394 break; 395 } 396 } 397 398 399 void 400 ZippoWindow::_OffscreenBounceBack(BRect* rect, direction* primaryDirection, 401 direction secondaryDirection) 402 { 403 if (*primaryDirection == up) { 404 *primaryDirection = down; 405 } else { 406 *primaryDirection = up; 407 } 408 409 _OffsetRect(rect, *primaryDirection); 410 _OffsetRect(rect, secondaryDirection); 411 } 412 413 414 BRect 415 ZippoWindow::_NearestRect(BRect goalRect, BRect a, BRect b) 416 { 417 double aSum = fabs(goalRect.left - a.left) + fabs(goalRect.top - a.top); 418 double bSum = fabs(goalRect.left - b.left) + fabs(goalRect.top - b.top); 419 420 if (aSum < bSum) 421 return a; 422 else 423 return b; 424 } 425 426