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