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