xref: /haiku/src/kits/tracker/StatusWindow.cpp (revision ed6250c95736c0b55da79d6e9dd01369532260c0)
1 /*
2 Open Tracker License
3 
4 Terms and Conditions
5 
6 Copyright (c) 1991-2000, Be Incorporated. All rights reserved.
7 
8 Permission is hereby granted, free of charge, to any person obtaining a copy of
9 this software and associated documentation files (the "Software"), to deal in
10 the Software without restriction, including without limitation the rights to
11 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
12 of the Software, and to permit persons to whom the Software is furnished to do
13 so, subject to the following conditions:
14 
15 The above copyright notice and this permission notice applies to all licensees
16 and shall be included in all copies or substantial portions of the Software.
17 
18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY,
20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21 BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
22 AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION
23 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 
25 Except as contained in this notice, the name of Be Incorporated shall not be
26 used in advertising or otherwise to promote the sale, use or other dealings in
27 this Software without prior written authorization from Be Incorporated.
28 
29 Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks
30 of Be Incorporated in the United States and other countries. Other brand product
31 names are registered trademarks or trademarks of their respective holders.
32 All rights reserved.
33 */
34 
35 //					A subclass of BWindow that is used to display
36 //					the status of the tracker (Copying, Deleting, etc.).
37 
38 #include <Application.h>
39 #include <Button.h>
40 #include <Debug.h>
41 #include <MessageFilter.h>
42 #include <StringView.h>
43 #include <String.h>
44 
45 #include <string.h>
46 
47 #include "AutoLock.h"
48 #include "Bitmaps.h"
49 #include "Commands.h"
50 #include "StatusWindow.h"
51 #include "DeskWindow.h"
52 
53 
54 const float	kDefaultStatusViewHeight = 50;
55 const float kUpdateGrain = 100000;
56 const BRect kStatusRect(200, 200, 550, 200);
57 
58 
59 class TCustomButton : public BButton {
60 	public:
61 		TCustomButton(BRect frame, uint32 command);
62 		virtual	void Draw(BRect);
63 	private:
64 		typedef BButton _inherited;
65 };
66 
67 class BStatusMouseFilter : public BMessageFilter {
68 	public:
69 		BStatusMouseFilter()
70 			:	BMessageFilter(B_ANY_DELIVERY, B_ANY_SOURCE, B_MOUSE_DOWN)
71 			{}
72 
73 		virtual	filter_result Filter(BMessage *message, BHandler **target);
74 };
75 
76 
77 namespace BPrivate {
78 BStatusWindow *gStatusWindow = NULL;
79 }
80 
81 
82 filter_result
83 BStatusMouseFilter::Filter(BMessage *, BHandler **target)
84 {
85 	if ((*target)->Name()
86 		&& strcmp((*target)->Name(), "StatusBar") == 0) {
87 		BView *view = dynamic_cast<BView *>(*target);
88 		if (view)
89 			view = view->Parent();
90 		if (view)
91 			*target = view;
92 	}
93 
94 	return B_DISPATCH_MESSAGE;
95 }
96 
97 
98 TCustomButton::TCustomButton(BRect frame, uint32 what)
99 	:	BButton(frame, "", "", new BMessage(what), B_FOLLOW_LEFT | B_FOLLOW_TOP,
100 			B_WILL_DRAW)
101 {
102 }
103 
104 
105 void
106 TCustomButton::Draw(BRect updateRect)
107 {
108 	_inherited::Draw(updateRect);
109 
110 	if (Message()->what == kStopButton) {
111 		updateRect = Bounds();
112 		updateRect.InsetBy(9, 7);
113 		SetHighColor(0, 0, 0);
114 		FillRect(updateRect);
115 	} else {
116 		updateRect = Bounds();
117 		updateRect.InsetBy(9, 6);
118 		BRect rect(updateRect);
119 		rect.right -= 3;
120 
121 		updateRect.left += 3;
122 		updateRect.OffsetBy(1, 0);
123 		SetHighColor(0, 0, 0);
124 		FillRect(updateRect);
125 		FillRect(rect);
126 	}
127 }
128 
129 
130 BStatusWindow::BStatusWindow()
131 	:	BWindow(kStatusRect, "Tracker Status", B_TITLED_WINDOW,
132 			B_NOT_CLOSABLE | B_NOT_RESIZABLE | B_NOT_ZOOMABLE,
133 			B_ALL_WORKSPACES),
134 		fRetainDesktopFocus(false)
135 {
136 	SetSizeLimits(0, 100000, 0, 100000);
137 	fMouseDownFilter = new BStatusMouseFilter();
138 	AddCommonFilter(fMouseDownFilter);
139 
140 	BRect bounds(Bounds());
141 
142 	BView *view = new BView(bounds, "BackView", B_FOLLOW_ALL, B_WILL_DRAW);
143 	view->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
144 	AddChild(view);
145 
146 	Run();
147 }
148 
149 
150 BStatusWindow::~BStatusWindow()
151 {
152 }
153 
154 
155 bool
156 BStatusWindow::CheckCanceledOrPaused(thread_id thread)
157 {
158 	bool wasCanceled = false;
159 	bool isPaused = false;
160 
161 	BStatusView *view = NULL;
162 
163 	for (;;) {
164 
165 		AutoLock<BWindow> lock(this);
166 		// check if cancel or pause hit
167 		for (int32 index = fViewList.CountItems() - 1; index >= 0; index--) {
168 
169 			view = fViewList.ItemAt(index);
170 			if (view && view->Thread() == thread) {
171 				isPaused = view->IsPaused();
172 				wasCanceled = view->WasCanceled();
173 				break;
174 			}
175 		}
176 		lock.Unlock();
177 
178 		if (wasCanceled || !isPaused)
179 			break;
180 
181 		if (isPaused && view) {
182 			AutoLock<BWindow> lock(this);
183 			// say we are paused
184 			view->Invalidate();
185 			lock.Unlock();
186 
187 			ASSERT(find_thread(NULL) == view->Thread());
188 
189 			// and suspend ourselves
190 			// we will get resumend from BStatusView::MessageReceived
191 			suspend_thread(view->Thread());
192 		}
193 		break;
194 
195 	}
196 
197 	return wasCanceled;
198 }
199 
200 
201 bool
202 BStatusWindow::AttemptToQuit()
203 {
204 	// called when tracker is quitting
205 	// try to cancel all the move/copy/empty trash threads in a nice way
206 	// by issuing cancels
207 	int32 count = fViewList.CountItems();
208 
209 	if (count == 0)
210 		return true;
211 
212 	for (int32 index = 0; index < count; index++)
213 		fViewList.ItemAt(index)->SetWasCanceled();
214 
215 	// maybe next time everything will have been canceled
216 	return false;
217 }
218 
219 
220 void
221 BStatusWindow::CreateStatusItem(thread_id thread, StatusWindowState type)
222 {
223 	AutoLock<BWindow> lock(this);
224 
225 	BRect rect(Bounds());
226 	if (BStatusView* lastView = fViewList.LastItem())
227 		rect.top = lastView->Frame().bottom + 1;
228 	rect.bottom = rect.top + kDefaultStatusViewHeight - 1;
229 
230 	BStatusView *view = new BStatusView(rect, thread, type);
231 	// the BStatusView will resize itself if needed in its constructor
232 	ChildAt(0)->AddChild(view);
233 	fViewList.AddItem(view);
234 
235 	ResizeTo(Bounds().Width(), view->Frame().bottom);
236 
237 	// find out if the desktop is the active window
238 	// if the status window is the only thing to take over active state and
239 	// desktop was active to begin with, return focus back to desktop
240 	// when we are done
241 	bool desktopActive = false;
242 	{
243 		AutoLock<BLooper> lock(be_app);
244 		int32 count = be_app->CountWindows();
245 		for (int32 index = 0; index < count; index++)
246 			if (dynamic_cast<BDeskWindow *>(be_app->WindowAt(index))
247 				&& be_app->WindowAt(index)->IsActive()) {
248 				desktopActive = true;
249 				break;
250 			}
251 	}
252 
253 	if (IsHidden()) {
254 		fRetainDesktopFocus = desktopActive;
255 		Minimize(false);
256 		Show();
257 	} else
258 		fRetainDesktopFocus &= desktopActive;
259 }
260 
261 
262 void
263 BStatusWindow::WindowActivated(bool state)
264 {
265 	if (!state)
266 		fRetainDesktopFocus = false;
267 
268 	return _inherited::WindowActivated(state);
269 }
270 
271 
272 void
273 BStatusWindow::RemoveStatusItem(thread_id thread)
274 {
275 	AutoLock<BWindow> lock(this);
276 	BStatusView *winner = NULL;
277 
278 	int32 numItems = fViewList.CountItems();
279 	int32 index;
280 	for (index = 0; index < numItems; index++) {
281 		BStatusView *view = fViewList.ItemAt(index);
282 		if (view->Thread() == thread) {
283 			winner = view;
284 			break;
285 		}
286 	}
287 
288 	if (winner) {
289 		// the height by which the other views will have to be moved (in pixel count)
290 		float height = winner->Bounds().Height() + 1;
291 		fViewList.RemoveItem(winner);
292 		winner->RemoveSelf();
293 		delete winner;
294 
295 		if (--numItems == 0 && !IsHidden()) {
296 			BDeskWindow *desktop = NULL;
297 			if (fRetainDesktopFocus) {
298 				AutoLock<BLooper> lock(be_app);
299 				int32 count = be_app->CountWindows();
300 				for (int32 index = 0; index < count; index++) {
301 					desktop = dynamic_cast<BDeskWindow *>(be_app->WindowAt(index));
302 					if (desktop)
303 						break;
304 				}
305 			}
306 			Hide();
307 			if (desktop)
308 				// desktop was active when we first started,
309 				// make it active again
310 				desktop->Activate();
311 		}
312 
313 		for (; index < numItems; index++)
314 			fViewList.ItemAt(index)->MoveBy(0, -height);
315 
316 		ResizeTo(Bounds().Width(), Bounds().Height() - height);
317 	}
318 
319 }
320 
321 
322 bool
323 BStatusWindow::HasStatus(thread_id thread)
324 {
325 	AutoLock<BWindow> lock(this);
326 
327 	int32 numItems = fViewList.CountItems();
328 	for (int32 index = 0; index < numItems; index++) {
329 		BStatusView *view = fViewList.ItemAt(index);
330 		if (view->Thread() == thread)
331 			return true;
332 
333 	}
334 
335 	return false;
336 }
337 
338 
339 void
340 BStatusWindow::UpdateStatus(thread_id thread, const char *curItem, off_t itemSize,
341 	bool optional)
342 {
343 	AutoLock<BWindow> lock(this);
344 
345 	int32 numItems = fViewList.CountItems();
346 	for (int32 index = 0; index < numItems; index++) {
347 		BStatusView *view = fViewList.ItemAt(index);
348 		if (view->Thread() == thread) {
349 			view->UpdateStatus(curItem, itemSize, optional);
350 			break;
351 		}
352 	}
353 
354 }
355 
356 
357 void
358 BStatusWindow::InitStatusItem(thread_id thread, int32 totalItems,  off_t totalSize,
359 	const entry_ref *destDir, bool showCount)
360 {
361 	AutoLock<BWindow> lock(this);
362 
363 	int32 numItems = fViewList.CountItems();
364 	for (int32 index = 0; index < numItems; index++) {
365 		BStatusView *view = fViewList.ItemAt(index);
366 		if (view->Thread() == thread) {
367 			view->InitStatus(totalItems, totalSize, destDir, showCount);
368 			break;
369 		}
370 	}
371 
372 }
373 
374 
375 BStatusView::BStatusView(BRect bounds, thread_id thread, StatusWindowState type)
376 	:	BView(bounds, "StatusView", B_FOLLOW_NONE, B_WILL_DRAW),
377 		fBitmap(NULL)
378 {
379 	Init();
380 
381 	fThread = thread;
382 	fType = type;
383 
384 	SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
385 	SetLowColor(ViewColor());
386 	SetHighColor(20, 20, 20);
387 	SetDrawingMode(B_OP_OVER);
388 
389 	BRect rect(bounds);
390 	rect.OffsetTo(B_ORIGIN);
391 	rect.left += 40;
392 	rect.right -= 50;
393 	rect.top += 6;
394 	rect.bottom = rect.top + 15;
395 
396 
397 	const char *caption = NULL;
398 	int32 id = 0;
399 
400 	switch (type) {
401 		case kCopyState:
402 			caption = "Preparing to copy items" B_UTF8_ELLIPSIS;
403 			id = kResCopyStatusBitmap;
404 			break;
405 
406 		case kMoveState:
407 			caption = "Preparing to move items" B_UTF8_ELLIPSIS;
408 			id = kResMoveStatusBitmap;
409 			break;
410 
411 		case kCreateLinkState:
412 			caption = "Preparing to create links" B_UTF8_ELLIPSIS;
413 			id = kResMoveStatusBitmap;
414 			break;
415 
416 		case kTrashState:
417 			caption = "Preparing to empty Trash" B_UTF8_ELLIPSIS;
418 			id = kResTrashStatusBitmap;
419 			break;
420 
421 
422 		case kVolumeState:
423 			caption = "Searching for disks to mount" B_UTF8_ELLIPSIS;
424 			break;
425 
426 		case kDeleteState:
427 			caption = "Preparing to delete items" B_UTF8_ELLIPSIS;
428 			id = kResTrashStatusBitmap;
429 			break;
430 
431 		case kRestoreFromTrashState:
432 			caption = "Preparing to restore items" B_UTF8_ELLIPSIS;
433 			break;
434 
435 		default:
436 			TRESPASS();
437 			break;
438 	}
439 
440 	if (caption) {
441 		fStatusBar = new BStatusBar(rect, "StatusBar", caption);
442 		fStatusBar->SetBarHeight(12);
443 		float width, height;
444 		fStatusBar->GetPreferredSize(&width, &height);
445 		fStatusBar->ResizeTo(fStatusBar->Frame().Width(), height);
446 		AddChild(fStatusBar);
447 
448 		// figure out how much room we need to display
449 		// the additional status message below the bar
450 		font_height fh;
451 		GetFontHeight(&fh);
452 		BRect f = fStatusBar->Frame();
453 		// height is 3 x the "room from the top" + bar height + room for string
454 		ResizeTo(Bounds().Width(), f.top + f.Height() + fh.leading + fh.ascent + fh.descent + f.top);
455 	}
456 
457 	if (id)
458 	 	GetTrackerResources()->GetBitmapResource(B_MESSAGE_TYPE, id, &fBitmap);
459 
460 
461 	rect = Bounds();
462 	rect.left = rect.right - 46;
463 	rect.right = rect.left + 17;
464 	rect.top += 17;
465 	rect.bottom = rect.top + 10;
466 
467 	fPauseButton = new TCustomButton(rect, kPauseButton);
468 	fPauseButton->ResizeTo(22, 18);
469 	AddChild(fPauseButton);
470 
471 	rect.OffsetBy(20, 0);
472 	fStopButton = new TCustomButton(rect, kStopButton);
473 	fStopButton->ResizeTo(22, 18);
474 	AddChild(fStopButton);
475 }
476 
477 
478 BStatusView::~BStatusView()
479 {
480 	delete fBitmap;
481 }
482 
483 
484 void
485 BStatusView::Init()
486 {
487 	fDestDir = "";
488 	fCurItem = 0;
489 	fPendingStatusString[0] = '\0';
490 	fWasCanceled = false;
491 	fIsPaused = false;
492 	fLastUpdateTime = 0;
493 	fItemSize = 0;
494 }
495 
496 
497 void
498 BStatusView::AttachedToWindow()
499 {
500 	fPauseButton->SetTarget(this);
501 	fStopButton->SetTarget(this);
502 }
503 
504 
505 void
506 BStatusView::InitStatus(int32 totalItems, off_t totalSize,
507 	const entry_ref *destDir, bool showCount)
508 {
509 	Init();
510 	fTotalSize = totalSize;
511 	fShowCount = showCount;
512 
513 	BEntry entry;
514 	char name[B_FILE_NAME_LENGTH];
515 	if (destDir && (entry.SetTo(destDir) == B_OK)) {
516 		entry.GetName(name);
517 		fDestDir = name;
518 	}
519 
520 	BString buffer;
521 	buffer << "of " << totalItems;
522 
523 	switch (fType) {
524 		case kCopyState:
525 			fStatusBar->Reset("Copying: ", buffer.String());
526 			break;
527 
528 		case kCreateLinkState:
529 			fStatusBar->Reset("Creating Links: ", buffer.String());
530 			break;
531 
532 		case kMoveState:
533 			fStatusBar->Reset("Moving: ", buffer.String());
534 			break;
535 
536 		case kTrashState:
537 			fStatusBar->Reset("Emptying Trash" B_UTF8_ELLIPSIS " ", buffer.String());
538 			break;
539 
540 		case kDeleteState:
541 			fStatusBar->Reset("Deleting: ", buffer.String());
542 			break;
543 
544 		case kRestoreFromTrashState:
545 			fStatusBar->Reset("Restoring: ", buffer.String());
546 			break;
547 
548 		default:
549 			break;
550 	}
551 
552 	fStatusBar->SetMaxValue(1);
553 		// SetMaxValue has to be here because Reset changes it to 100
554 	Invalidate();
555 }
556 
557 
558 void
559 BStatusView::UpdateStatus(const char *curItem, off_t itemSize, bool optional)
560 {
561 	float currentTime = system_time();
562 
563 	if (fShowCount) {
564 
565 		if (curItem)
566 			fCurItem++;
567 
568 		fItemSize += itemSize;
569 
570 		if (!optional || ((currentTime - fLastUpdateTime) > kUpdateGrain)) {
571 			if (curItem != NULL || fPendingStatusString[0]) {
572 				// forced update or past update time
573 
574 				BString buffer;
575 				buffer <<  fCurItem << " ";
576 
577 				// if we don't have curItem, take the one from the stash
578 				const char *statusItem = curItem != NULL
579 					? curItem : fPendingStatusString;
580 
581 				fStatusBar->Update((float)fItemSize / fTotalSize, statusItem, buffer.String());
582 
583 				// we already displayed this item, clear the stash
584 				fPendingStatusString[0] =  '\0';
585 
586 				fLastUpdateTime = currentTime;
587 			}
588 			else
589 				// don't have a file to show, just update the bar
590 				fStatusBar->Update((float)fItemSize / fTotalSize);
591 
592 			fItemSize = 0;
593 		} else if (curItem != NULL) {
594 			// stash away the name of the item we are currently processing
595 			// so we can show it when the time comes
596 			strncpy(fPendingStatusString, curItem, 127);
597 			fPendingStatusString[127] = '0';
598 		}
599 	} else {
600 		fStatusBar->Update((float)fItemSize / fTotalSize);
601 		fItemSize = 0;
602 	}
603 }
604 
605 
606 void
607 BStatusView::MessageReceived(BMessage *message)
608 {
609 	switch (message->what) {
610 		case kPauseButton:
611 			fIsPaused = !fIsPaused;
612 			if (!fIsPaused) {
613 
614 				// force window update
615 				Invalidate();
616 
617 				// let 'er rip
618 				resume_thread(Thread());
619 			}
620 			break;
621 
622 		case kStopButton:
623 			fWasCanceled = true;
624 			if (fIsPaused) {
625 				// resume so that the copy loop gets a chance to finish up
626 				fIsPaused = false;
627 
628 				// force window update
629 				Invalidate();
630 
631 				// let 'er rip
632 				resume_thread(Thread());
633 			}
634 			break;
635 
636 		default:
637 			_inherited::MessageReceived(message);
638 			break;
639 	}
640 }
641 
642 
643 void
644 BStatusView::Draw(BRect)
645 {
646 	if (fBitmap)
647 		DrawBitmap(fBitmap, BPoint(4, 10));
648 
649 	BRect bounds(Bounds());
650 
651 	// draw a frame, which also separates multiple BStatusViews
652 	rgb_color light = tint_color(ViewColor(), B_LIGHTEN_MAX_TINT);
653 	rgb_color shadow = tint_color(ViewColor(), B_DARKEN_1_TINT);
654 	BeginLineArray(4);
655 		AddLine(BPoint(bounds.left, bounds.bottom - 1.0f),
656 				BPoint(bounds.left, bounds.top), light);
657 		AddLine(BPoint(bounds.left + 1.0f, bounds.top),
658 				BPoint(bounds.right, bounds.top), light);
659 		AddLine(BPoint(bounds.right, bounds.top + 1.0f),
660 				BPoint(bounds.right, bounds.bottom), shadow);
661 		AddLine(BPoint(bounds.right - 1.0f, bounds.bottom),
662 				BPoint(bounds.left, bounds.bottom), shadow);
663 	EndLineArray();
664 
665 	SetHighColor(0, 0, 0);
666 
667 	BPoint tp = fStatusBar->Frame().LeftBottom();
668 	font_height fh;
669 	GetFontHeight(&fh);
670 	tp.x += 2;
671 	tp.y += fh.leading + fh.ascent;
672 	MovePenTo(tp);
673 
674 	if (IsPaused())
675 		DrawString("Paused: click to resume or stop");
676 	else if (fDestDir.Length()) {
677 		BString buffer;
678 		buffer << "To: " << fDestDir;
679 		SetHighColor(0, 0, 0);
680 		DrawString(buffer.String());
681 	}
682 }
683 
684 
685 void
686 BStatusView::SetWasCanceled()
687 {
688 	fWasCanceled = true;
689 }
690 
691