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
40ZippoWindow::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
87ZippoWindow::~ZippoWindow()
88{
89	delete fWindowInvoker;
90}
91
92
93void
94ZippoWindow::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
197bool
198ZippoWindow::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
226void
227ZippoWindow::_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
240void
241ZippoWindow::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
262bool
263ZippoWindow::IsZipping()
264{
265	if (fThread == NULL)
266		return false;
267	else
268		return true;
269}
270
271
272void
273ZippoWindow::_CloseWindowOrKeepOpen()
274{
275	if (!fKeepOpen)
276		PostMessage(B_QUIT_REQUESTED);
277}
278
279
280void
281ZippoWindow::_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
374void
375ZippoWindow::_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
400void
401ZippoWindow::_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
415BRect
416ZippoWindow::_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