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