xref: /haiku/src/add-ons/tracker/zipomatic/ZipOMaticWindow.cpp (revision 1deede7388b04dbeec5af85cae7164735ea9e70d)
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