1/*
2Open Tracker License
3
4Terms and Conditions
5
6Copyright (c) 1991-2000, Be Incorporated. All rights reserved.
7
8Permission is hereby granted, free of charge, to any person obtaining a copy of
9this software and associated documentation files (the "Software"), to deal in
10the Software without restriction, including without limitation the rights to
11use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
12of the Software, and to permit persons to whom the Software is furnished to do
13so, subject to the following conditions:
14
15The above copyright notice and this permission notice applies to all licensees
16and shall be included in all copies or substantial portions of the Software.
17
18THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY,
20FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
22AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION
23WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
25Except as contained in this notice, the name of Be Incorporated shall not be
26used in advertising or otherwise to promote the sale, use or other dealings in
27this Software without prior written authorization from Be Incorporated.
28
29Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks
30of Be Incorporated in the United States and other countries. Other brand product
31names are registered trademarks or trademarks of their respective holders.
32All rights reserved.
33*/
34
35// defines the status area drawn in the bottom left corner of a Tracker window
36
37
38#include "CountView.h"
39
40#include <Application.h>
41#include <Catalog.h>
42#include <ControlLook.h>
43#include <Locale.h>
44#include <StringFormat.h>
45
46#include "AutoLock.h"
47#include "Bitmaps.h"
48#include "ContainerWindow.h"
49#include "DirMenu.h"
50#include "PoseView.h"
51#include "Utilities.h"
52
53
54#undef B_TRANSLATION_CONTEXT
55#define B_TRANSLATION_CONTEXT "CountView"
56
57
58const bigtime_t kBarberPoleDelay = 500000;
59static const float kMinFontSize = 8.0f;
60
61
62//	#pragma mark - BCountView
63
64
65BCountView::BCountView(BPoseView* view)
66	:
67	BView("CountVw", B_PULSE_NEEDED | B_WILL_DRAW),
68	fLastCount(-1),
69	fPoseView(view),
70	fShowingBarberPole(false),
71	fBarberPoleMap(NULL),
72	fLastBarberPoleOffset(5),
73	fStartSpinningAfter(0),
74	fTypeAheadString(""),
75	fFilterString("")
76{
77	GetTrackerResources()->GetBitmapResource(B_MESSAGE_TYPE,
78		R_BarberPoleBitmap, &fBarberPoleMap);
79}
80
81
82BCountView::~BCountView()
83{
84	delete fBarberPoleMap;
85}
86
87
88void
89BCountView::TrySpinningBarberPole()
90{
91	if (!fShowingBarberPole)
92		return;
93
94	if (fStartSpinningAfter && system_time() < fStartSpinningAfter)
95		return;
96
97	// When the barber pole just starts spinning we need to invalidate
98	// the whole rectangle of text and barber pole.
99	// After this the text needs no updating since only the pole changes.
100	if (fStartSpinningAfter) {
101		fStartSpinningAfter = 0;
102		Invalidate(TextAndBarberPoleRect());
103	} else
104		Invalidate(BarberPoleInnerRect());
105}
106
107
108void
109BCountView::Pulse()
110{
111	TrySpinningBarberPole();
112}
113
114
115void
116BCountView::EndBarberPole()
117{
118	if (!fShowingBarberPole)
119		return;
120
121	fShowingBarberPole = false;
122	Invalidate();
123}
124
125
126void
127BCountView::StartBarberPole()
128{
129	AutoLock<BWindow> lock(Window());
130	if (fShowingBarberPole)
131		return;
132
133	fShowingBarberPole = true;
134	fStartSpinningAfter = system_time() + kBarberPoleDelay;
135		// wait a bit before showing the barber pole
136}
137
138
139BRect
140BCountView::BarberPoleInnerRect() const
141{
142	BRect result = Bounds();
143	result.InsetBy(3, 4);
144	result.left = result.right - 7;
145	result.bottom = result.top + 7;
146	return result;
147}
148
149
150BRect
151BCountView::BarberPoleOuterRect() const
152{
153	BRect result(BarberPoleInnerRect());
154	result.InsetBy(-1, -1);
155	return result;
156}
157
158
159BRect
160BCountView::TextInvalRect() const
161{
162	BRect result = TextAndBarberPoleRect();
163
164	// if the barber pole is not present, use its space for text
165	if (fShowingBarberPole)
166		result.right -= 10;
167
168	return result;
169}
170
171
172BRect
173BCountView::TextAndBarberPoleRect() const
174{
175	BRect result = Bounds();
176	result.InsetBy(be_control_look->ComposeSpacing(B_USE_SMALL_SPACING) / 2,
177		floorf(result.Height() * 0.25f));
178
179	return result;
180}
181
182
183void
184BCountView::CheckCount()
185{
186	// invalidate the count text area if necessary
187	if (fLastCount != fPoseView->CountItems()) {
188		fLastCount = fPoseView->CountItems();
189		Invalidate(TextInvalRect());
190	}
191
192	// invalidate barber pole area if necessary
193	TrySpinningBarberPole();
194}
195
196
197void
198BCountView::Draw(BRect updateRect)
199{
200	BRect bounds(Bounds());
201
202	rgb_color color = ViewColor();
203	if (IsTypingAhead())
204		color = ui_color(B_DOCUMENT_BACKGROUND_COLOR);
205
206	SetLowColor(color);
207	be_control_look->DrawBorder(this, bounds, updateRect,
208		color, B_PLAIN_BORDER, 0,
209		BControlLook::B_BOTTOM_BORDER | BControlLook::B_LEFT_BORDER);
210	be_control_look->DrawMenuBarBackground(this, bounds, updateRect, color);
211
212	BString itemString;
213	if (IsTypingAhead())
214		itemString << TypeAhead();
215	else if (IsFiltering()) {
216		itemString << fLastCount << " " << Filter();
217	} else {
218		if (fLastCount == 0)
219			itemString << B_TRANSLATE("no items");
220		else {
221			static BStringFormat format(B_TRANSLATE_COMMENT(
222				"{0, plural, one{# item} other{# items}}",
223				"Number of selected items: \"1 item\" or \"2 items\""));
224			format.Format(itemString, fLastCount);
225		}
226	}
227
228	BRect textRect(TextInvalRect());
229
230	TruncateString(&itemString, IsTypingAhead() ? B_TRUNCATE_BEGINNING
231			: IsFiltering() ? B_TRUNCATE_MIDDLE : B_TRUNCATE_END,
232		textRect.Width());
233
234	if (IsTypingAhead()) {
235		// use a muted gray for the typeahead
236		SetHighUIColor(B_DOCUMENT_TEXT_COLOR);
237	} else
238		SetHighUIColor(B_PANEL_TEXT_COLOR);
239
240	MovePenTo(textRect.LeftBottom());
241	DrawString(itemString.String());
242
243	bounds.top++;
244
245	rgb_color light = tint_color(ViewColor(), B_LIGHTEN_MAX_TINT);
246	rgb_color shadow = tint_color(ViewColor(), B_DARKEN_2_TINT);
247
248	BeginLineArray(fShowingBarberPole && !fStartSpinningAfter ? 9 : 5);
249
250	if (!fShowingBarberPole || fStartSpinningAfter) {
251		EndLineArray();
252		return;
253	}
254
255	BRect barberPoleRect(BarberPoleOuterRect());
256
257	AddLine(barberPoleRect.LeftTop(), barberPoleRect.RightTop(), shadow);
258	AddLine(barberPoleRect.LeftTop(), barberPoleRect.LeftBottom(), shadow);
259	AddLine(barberPoleRect.LeftBottom(), barberPoleRect.RightBottom(), light);
260	AddLine(barberPoleRect.RightBottom(), barberPoleRect.RightTop(), light);
261	EndLineArray();
262
263	barberPoleRect.InsetBy(1, 1);
264
265	BRect destRect(fBarberPoleMap
266		? fBarberPoleMap->Bounds() : BRect(0, 0, 0, 0));
267	destRect.OffsetTo(barberPoleRect.LeftTop()
268		- BPoint(0, fLastBarberPoleOffset));
269	fLastBarberPoleOffset -= 1;
270	if (fLastBarberPoleOffset < 0)
271		fLastBarberPoleOffset = 5;
272
273	BRegion region;
274	region.Set(BarberPoleInnerRect());
275	ConstrainClippingRegion(&region);
276
277	if (fBarberPoleMap)
278		DrawBitmap(fBarberPoleMap, destRect);
279}
280
281
282void
283BCountView::MouseDown(BPoint)
284{
285	BContainerWindow* window = dynamic_cast<BContainerWindow*>(Window());
286	ThrowOnAssert(window != NULL);
287
288	window->Activate();
289	window->UpdateIfNeeded();
290
291	if (fPoseView->IsFilePanel() || fPoseView->TargetModel() == NULL)
292		return;
293
294	if (!window->TargetModel()->IsRoot()) {
295		BDirMenu* menu = new BDirMenu(NULL, be_app, B_REFS_RECEIVED);
296		BEntry entry;
297		if (entry.SetTo(window->TargetModel()->EntryRef()) == B_OK)
298			menu->Populate(&entry, Window(), false, false, true, false, true);
299		else
300			menu->Populate(NULL, Window(), false, false, true, false, true);
301
302		BPoint point = Bounds().LeftBottom();
303		point.y += 3;
304		ConvertToScreen(&point);
305		BRect clickToOpenRect(Bounds());
306		ConvertToScreen(&clickToOpenRect);
307		menu->Go(point, true, true, clickToOpenRect);
308		delete menu;
309	}
310}
311
312
313void
314BCountView::AttachedToWindow()
315{
316	SetFont(be_plain_font);
317	SetFontSize(std::max(kMinFontSize,
318		ceilf(be_plain_font->Size() * 0.75f)));
319
320	SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
321	SetLowUIColor(ViewUIColor());
322
323	CheckCount();
324}
325
326
327void
328BCountView::SetTypeAhead(const char* string)
329{
330	fTypeAheadString = string;
331	Invalidate();
332}
333
334
335const char*
336BCountView::TypeAhead() const
337{
338	return fTypeAheadString.String();
339}
340
341
342bool
343BCountView::IsTypingAhead() const
344{
345	return fTypeAheadString.Length() != 0;
346}
347
348
349void
350BCountView::AddFilterCharacter(const char* character)
351{
352	fFilterString.AppendChars(character, 1);
353	Invalidate();
354}
355
356
357void
358BCountView::RemoveFilterCharacter()
359{
360	fFilterString.TruncateChars(fFilterString.CountChars() - 1);
361	Invalidate();
362}
363
364
365void
366BCountView::CancelFilter()
367{
368	fFilterString.Truncate(0);
369	Invalidate();
370}
371
372
373const char*
374BCountView::Filter() const
375{
376	return fFilterString.String();
377}
378
379
380bool
381BCountView::IsFiltering() const
382{
383	return fFilterString.Length() > 0;
384}
385