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
ZippoWindow(BList windowList,bool keepOpen)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
~ZippoWindow()88 ZippoWindow::~ZippoWindow()
89 {
90 delete fWindowInvoker;
91 }
92
93
94 void
MessageReceived(BMessage * message)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
QuitRequested()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
_StartZipping(BMessage * message)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
StopZipping()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
IsZipping()259 ZippoWindow::IsZipping()
260 {
261 if (fThread == NULL)
262 return false;
263 else
264 return true;
265 }
266
267
268 void
_CloseWindowOrKeepOpen()269 ZippoWindow::_CloseWindowOrKeepOpen()
270 {
271 if (!fKeepOpen)
272 PostMessage(B_QUIT_REQUESTED);
273 }
274
275
276 void
_FindBestPlacement()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
_OffsetRect(BRect * rect,direction whereTo)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
_OffscreenBounceBack(BRect * rect,direction * primaryDirection,direction secondaryDirection)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
_NearestRect(BRect goalRect,BRect a,BRect b)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