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