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