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
41ZippoWindow::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
88ZippoWindow::~ZippoWindow()
89{
90	delete fWindowInvoker;
91}
92
93
94void
95ZippoWindow::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
193bool
194ZippoWindow::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
222void
223ZippoWindow::_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
236void
237ZippoWindow::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
258bool
259ZippoWindow::IsZipping()
260{
261	if (fThread == NULL)
262		return false;
263	else
264		return true;
265}
266
267
268void
269ZippoWindow::_CloseWindowOrKeepOpen()
270{
271	if (!fKeepOpen)
272		PostMessage(B_QUIT_REQUESTED);
273}
274
275
276void
277ZippoWindow::_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
370void
371ZippoWindow::_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
396void
397ZippoWindow::_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
411BRect
412ZippoWindow::_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