xref: /haiku/src/apps/soundrecorder/TrackSlider.cpp (revision d3d8b26997fac34a84981e6d2b649521de2cc45a)
1 /*
2  * Copyright 2005, Jérôme Duval. All rights reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * Inspired by SoundCapture from Be newsletter (Media Kit Basics: Consumers and Producers)
6  */
7 
8 #include <stdio.h>
9 #include <string.h>
10 
11 #include "TrackSlider.h"
12 #include "icon_button.h"
13 
14 TrackSlider::TrackSlider(BRect rect, const char *title, BMessage *msg, uint32 resizeFlags)
15 	: BControl(rect, "slider", NULL, msg, resizeFlags, B_WILL_DRAW | B_FRAME_EVENTS),
16 	leftBitmap(BRect(BPoint(0,0), kLeftRightTrackSliderSize), B_CMAP8),
17 	rightBitmap(BRect(BPoint(0,0), kLeftRightTrackSliderSize), B_CMAP8),
18 	leftThumbBitmap(BRect(0, 0, kLeftRightThumbWidth - 1, kLeftRightThumbHeight - 1), B_CMAP8),
19 	rightThumbBitmap(BRect(0, 0, kLeftRightThumbWidth - 1, kLeftRightThumbHeight - 1), B_CMAP8),
20 	fLeftTime(0), fRightTime(1000000), fMainTime(0), fTotalTime(1000000),
21 	fLeftTracking(false), fRightTracking(false), fMainTracking(false)
22 {
23 	fFont.SetSize(8.0);
24 	fFont.SetFlags(B_DISABLE_ANTIALIASING);
25 
26 	int32 numFamilies = count_font_families();
27 	for (int32 i = 0; i < numFamilies; i++ ) {
28 		font_family family;
29 		uint32 flags;
30 		if ((get_font_family(i, &family, &flags) == B_OK)
31 			&& (strcmp(family, "Baskerville") == 0)) {
32 			fFont.SetFamilyAndFace(family, B_REGULAR_FACE);
33 			break;
34 		}
35 	}
36 	leftBitmap.SetBits(kLeftTrackSliderBits, kLeftRightTrackSliderWidth * kLeftRightTrackSliderHeight, 0, B_CMAP8);
37 	rightBitmap.SetBits(kRightTrackSliderBits, kLeftRightTrackSliderWidth * kLeftRightTrackSliderHeight, 0, B_CMAP8);
38 	leftThumbBitmap.SetBits(kLeftThumbBits, kLeftRightThumbWidth * kLeftRightThumbHeight, 0, B_CMAP8);
39 	rightThumbBitmap.SetBits(kRightThumbBits, kLeftRightThumbWidth * kLeftRightThumbHeight, 0, B_CMAP8);
40 
41 	fRight = Bounds().right - kLeftRightTrackSliderWidth;
42 	if (fTotalTime == 0) {
43 		fLeftX = 14;
44 		fRightX = fRight;
45 		fPositionX = 15;
46 	} else {
47 		fLeftX = 14 + (fRight - 15) * ((double)fLeftTime / fTotalTime);
48 		fRightX = 15 + (fRight - 16) * ((double)fRightTime / fTotalTime);
49 		fPositionX = 15 + (fRight - 14) * ((double)fMainTime / fTotalTime);
50 	}
51 }
52 
53 
54 TrackSlider::~TrackSlider()
55 {
56 
57 }
58 
59 
60 void
61 TrackSlider::AttachedToWindow()
62 {
63 	BControl::AttachedToWindow();
64 	SetViewColor(B_TRANSPARENT_COLOR);
65 }
66 
67 
68 #define SLIDER_BASE 10
69 
70 void
71 TrackSlider::Draw(BRect updateRect)
72 {
73 	SetHighColor(189,186,189);
74 	StrokeLine(BPoint(11,SLIDER_BASE+1), BPoint(fRight,SLIDER_BASE+1));
75 	SetHighColor(0,0,0);
76 	StrokeLine(BPoint(11,SLIDER_BASE+2), BPoint(fRight,SLIDER_BASE+2));
77 	SetHighColor(255,255,255);
78 	StrokeLine(BPoint(11,SLIDER_BASE+17), BPoint(fRight,SLIDER_BASE+17));
79 	SetHighColor(231,227,231);
80 	StrokeLine(BPoint(11,SLIDER_BASE+18), BPoint(fRight,SLIDER_BASE+18));
81 
82 	SetDrawingMode(B_OP_OVER);
83 
84 	SetHighColor(216,216,216);
85 	FillRect(BRect(0,1,fPositionX < 18 ? 18 : fPositionX-22,SLIDER_BASE));
86 	FillRect(BRect(fPositionX < 18 ? 65 : fPositionX > fRight - 3 ? fRight : fPositionX+22,0,Bounds().right,SLIDER_BASE));
87 	FillRect(BRect(0,0,Bounds().right,0));
88 	FillRect(BRect(0,SLIDER_BASE+18,Bounds().right,SLIDER_BASE+21));
89 	FillRect(BRect(0,0,10,SLIDER_BASE+21));
90 	FillRect(BRect(Bounds().right - 10,0,Bounds().right,SLIDER_BASE+21));
91 
92 	SetLowColor(HighColor());
93 
94 	BPoint leftPoint(5,SLIDER_BASE+1);
95 	DrawBitmapAsync(&leftBitmap, BRect(BPoint(0,0), kLeftRightTrackSliderSize - BPoint(5,0)),
96 		BRect(leftPoint, leftPoint+kLeftRightTrackSliderSize-BPoint(5,0)));
97 	BPoint rightPoint(fRight + 1,SLIDER_BASE+1);
98 	DrawBitmapAsync(&rightBitmap, BRect(BPoint(5,0), kLeftRightTrackSliderSize),
99 		BRect(rightPoint, rightPoint+kLeftRightTrackSliderSize-BPoint(5,0)));
100 
101 	SetHighColor(153,153,153);
102 	FillRect(BRect(11,SLIDER_BASE+3,fLeftX-9,SLIDER_BASE+16));
103 	FillRect(BRect(fRightX+9,SLIDER_BASE+3,fRight,SLIDER_BASE+16));
104 	if (fLeftX>19) {
105 		StrokeLine(BPoint(fLeftX-9,SLIDER_BASE+3),BPoint(fLeftX-6,SLIDER_BASE+3));
106 		StrokeLine(BPoint(fLeftX-9,SLIDER_BASE+4),BPoint(fLeftX-7,SLIDER_BASE+4));
107 		StrokeLine(BPoint(fLeftX-9,SLIDER_BASE+5),BPoint(fLeftX-8,SLIDER_BASE+5));
108 		StrokeLine(BPoint(fLeftX-9,SLIDER_BASE+16),BPoint(fLeftX-6,SLIDER_BASE+16));
109 		StrokeLine(BPoint(fLeftX-9,SLIDER_BASE+15),BPoint(fLeftX-7,SLIDER_BASE+15));
110 		StrokeLine(BPoint(fLeftX-9,SLIDER_BASE+14),BPoint(fLeftX-8,SLIDER_BASE+14));
111 	}
112 	if (fRightX < fRight - 5) {
113 		StrokeLine(BPoint(fRightX+5,SLIDER_BASE+3),BPoint(fRightX+8,SLIDER_BASE+3));
114 		StrokeLine(BPoint(fRightX+7,SLIDER_BASE+4),BPoint(fRightX+8,SLIDER_BASE+4));
115 		StrokeLine(BPoint(fRightX+8,SLIDER_BASE+5),BPoint(fRightX+8,SLIDER_BASE+6));
116 		StrokeLine(BPoint(fRightX+8,SLIDER_BASE+13),BPoint(fRightX+8,SLIDER_BASE+14));
117 		StrokeLine(BPoint(fRightX+5,SLIDER_BASE+16),BPoint(fRightX+8,SLIDER_BASE+16));
118 		StrokeLine(BPoint(fRightX+7,SLIDER_BASE+15),BPoint(fRightX+8,SLIDER_BASE+15));
119 	}
120 	SetHighColor(144,186,136);
121 	FillRect(BRect(fLeftX+1,SLIDER_BASE+3,fRightX,SLIDER_BASE+4));
122 	FillRect(BRect(fLeftX+1,SLIDER_BASE+5,fLeftX+2,SLIDER_BASE+16));
123 	SetHighColor(171,221,161);
124 	FillRect(BRect(fLeftX+3,SLIDER_BASE+5,fRightX,SLIDER_BASE+16));
125 
126 	int i = 17;
127 	int j = 18;
128 	SetHighColor(128,128,128);
129 	for (; i<fLeftX-9; i+=6) {
130 		StrokeLine(BPoint(i,SLIDER_BASE+7), BPoint(i,SLIDER_BASE+13));
131 	}
132 	SetHighColor(179,179,179);
133 	for (; j<fLeftX-9; j+=6) {
134 		StrokeLine(BPoint(j,SLIDER_BASE+7), BPoint(j,SLIDER_BASE+13));
135 	}
136 
137 	while (i<=fLeftX)
138 		i+=6;
139 	while (j<=fLeftX)
140 		j+=6;
141 
142 	SetHighColor(144,186,136);
143 	for (; i<=fRightX; i+=6) {
144 		StrokeLine(BPoint(i,SLIDER_BASE+7), BPoint(i,SLIDER_BASE+13));
145 	}
146 	SetHighColor(189,244,178);
147 	for (; j<=fRightX; j+=6) {
148 		StrokeLine(BPoint(j,SLIDER_BASE+7), BPoint(j,SLIDER_BASE+13));
149 	}
150 
151 	while (i<=fRightX+9)
152 		i+=6;
153 	while (j<=fRightX+9)
154 		j+=6;
155 
156 	SetHighColor(128,128,128);
157 	for (; i<=fRight + 1; i+=6) {
158 		StrokeLine(BPoint(i,SLIDER_BASE+7), BPoint(i,SLIDER_BASE+13));
159 	}
160 	SetHighColor(179,179,179);
161 	for (; j<=fRight + 1; j+=6) {
162 		StrokeLine(BPoint(j,SLIDER_BASE+7), BPoint(j,SLIDER_BASE+13));
163 	}
164 
165 	SetLowColor(HighColor());
166 
167 	BPoint leftThumbPoint(fLeftX-8,SLIDER_BASE+3);
168 	DrawBitmapAsync(&leftThumbBitmap, BRect(BPoint(0,0), kLeftRightThumbSize - BPoint(7,0)),
169 		BRect(leftThumbPoint, leftThumbPoint+kLeftRightThumbSize-BPoint(7,0)));
170 
171 	BPoint rightThumbPoint(fRightX,SLIDER_BASE+3);
172 	DrawBitmapAsync(&rightThumbBitmap, BRect(BPoint(6,0), kLeftRightThumbSize),
173 		BRect(rightThumbPoint, rightThumbPoint+kLeftRightThumbSize-BPoint(6,0)));
174 
175 	rgb_color black = {0,0,0};
176 	rgb_color rose = {255,152,152};
177 	rgb_color red = {255,0,0};
178 	rgb_color bordeau = {178,0,0};
179 	rgb_color white = {255,255,255};
180 
181 	DrawCounter(fMainTime, fPositionX, fMainTracking);
182 	if (fLeftTracking)
183 		DrawCounter(fLeftTime, fLeftX, fLeftTracking);
184 	else if (fRightTracking)
185 		DrawCounter(fRightTime, fRightX, fRightTracking);
186 
187 	BeginLineArray(30);
188 	AddLine(BPoint(fPositionX,SLIDER_BASE+7), BPoint(fPositionX-4,SLIDER_BASE+3), black);
189 	AddLine(BPoint(fPositionX-4,SLIDER_BASE+3), BPoint(fPositionX-4,SLIDER_BASE+1), black);
190 	AddLine(BPoint(fPositionX-4,SLIDER_BASE+1), BPoint(fPositionX+4,SLIDER_BASE+1), black);
191 	AddLine(BPoint(fPositionX+4,SLIDER_BASE+1), BPoint(fPositionX+4,SLIDER_BASE+3), black);
192 	AddLine(BPoint(fPositionX+4,SLIDER_BASE+3), BPoint(fPositionX,SLIDER_BASE+7), black);
193 
194 
195 	AddLine(BPoint(fPositionX-3,SLIDER_BASE+2), BPoint(fPositionX+3,SLIDER_BASE+2), rose);
196 	AddLine(BPoint(fPositionX-3,SLIDER_BASE+3), BPoint(fPositionX-1,SLIDER_BASE+5), rose);
197 
198 	AddLine(BPoint(fPositionX-2,SLIDER_BASE+3), BPoint(fPositionX+2,SLIDER_BASE+3), red);
199 	AddLine(BPoint(fPositionX-1,SLIDER_BASE+4), BPoint(fPositionX+1,SLIDER_BASE+4), red);
200 	AddLine(BPoint(fPositionX,SLIDER_BASE+5), BPoint(fPositionX,SLIDER_BASE+5), red);
201 
202 	AddLine(BPoint(fPositionX,SLIDER_BASE+6), BPoint(fPositionX+3,SLIDER_BASE+3), bordeau);
203 
204 	AddLine(BPoint(fPositionX,SLIDER_BASE+12), BPoint(fPositionX-4,SLIDER_BASE+16), black);
205 	AddLine(BPoint(fPositionX-4,SLIDER_BASE+16), BPoint(fPositionX-4,SLIDER_BASE+17), black);
206 	AddLine(BPoint(fPositionX-4,SLIDER_BASE+17), BPoint(fPositionX+4,SLIDER_BASE+17), black);
207 	AddLine(BPoint(fPositionX+4,SLIDER_BASE+17), BPoint(fPositionX+4,SLIDER_BASE+16), black);
208 	AddLine(BPoint(fPositionX+4,SLIDER_BASE+16), BPoint(fPositionX,SLIDER_BASE+12), black);
209 	AddLine(BPoint(fPositionX-4,SLIDER_BASE+18), BPoint(fPositionX+4,SLIDER_BASE+18), white);
210 
211 	AddLine(BPoint(fPositionX-3,SLIDER_BASE+16), BPoint(fPositionX,SLIDER_BASE+13), rose);
212 
213 	AddLine(BPoint(fPositionX-2,SLIDER_BASE+16), BPoint(fPositionX+2,SLIDER_BASE+16), red);
214 	AddLine(BPoint(fPositionX-1,SLIDER_BASE+15), BPoint(fPositionX+1,SLIDER_BASE+15), red);
215 	AddLine(BPoint(fPositionX,SLIDER_BASE+14), BPoint(fPositionX,SLIDER_BASE+14), red);
216 
217 	AddLine(BPoint(fPositionX+1,SLIDER_BASE+14), BPoint(fPositionX+3,SLIDER_BASE+16), bordeau);
218 
219 	EndLineArray();
220 
221 
222 
223 	Flush();
224 }
225 
226 
227 void
228 TrackSlider::DrawCounter(bigtime_t timestamp, float position, bool isTracking)
229 {
230 	// timecounter
231 
232 	rgb_color gray = {128,128,128};
233 	rgb_color blue = {0,0,140};
234 	rgb_color blue2 = {146,146,214};
235 	rgb_color white = {255,255,255};
236 
237 	char string[12];
238 	TimeToString(timestamp, string);
239 	int32 halfwidth = ((int32)fFont.StringWidth(string)) / 2;
240 
241 	float counterX = position;
242 	if (counterX < 39)
243 		counterX = 39;
244 	if (counterX > fRight - 23)
245 		counterX = fRight - 23;
246 
247 	BeginLineArray(4);
248 	if (!isTracking) {
249 		AddLine(BPoint(counterX-halfwidth-3,SLIDER_BASE+1), BPoint(counterX+halfwidth+3,SLIDER_BASE+1), gray);
250 		AddLine(BPoint(counterX+halfwidth+4,SLIDER_BASE+1), BPoint(counterX+halfwidth+4,SLIDER_BASE-8), gray);
251 		AddLine(BPoint(counterX-halfwidth-4,SLIDER_BASE+1), BPoint(counterX-halfwidth-4,SLIDER_BASE-9), white);
252 		AddLine(BPoint(counterX-halfwidth-3,SLIDER_BASE-9), BPoint(counterX+halfwidth+4,SLIDER_BASE-9), white);
253 		SetHighColor(216,216,216);
254 	} else {
255 		AddLine(BPoint(counterX-halfwidth-3,SLIDER_BASE+1), BPoint(counterX+halfwidth+3,SLIDER_BASE+1), blue);
256 		AddLine(BPoint(counterX+halfwidth+4,SLIDER_BASE+1), BPoint(counterX+halfwidth+4,SLIDER_BASE-9), blue2);
257 		AddLine(BPoint(counterX-halfwidth-4,SLIDER_BASE+1), BPoint(counterX-halfwidth-4,SLIDER_BASE-9), blue2);
258 		AddLine(BPoint(counterX-halfwidth-3,SLIDER_BASE-9), BPoint(counterX+halfwidth+3,SLIDER_BASE-9), blue2);
259 		SetHighColor(48,48,241);
260 	}
261 	EndLineArray();
262 	FillRect(BRect(counterX-halfwidth-3,SLIDER_BASE-8,counterX+halfwidth+3,SLIDER_BASE));
263 
264 	SetDrawingMode(B_OP_COPY);
265 	if (isTracking)
266 		SetHighColor(255,255,255);
267 	else
268 		SetHighColor(0,0,0);
269 	SetLowColor(ViewColor());
270 
271 	SetFont(&fFont);
272 	DrawString(string, BPoint(counterX-halfwidth, SLIDER_BASE-1));
273 
274 }
275 
276 
277 void
278 TrackSlider::MouseMoved(BPoint point, uint32 transit, const BMessage *message)
279 {
280 	if (!IsTracking())
281 		return;
282 
283 	uint32 mouseButtons;
284 	BPoint where;
285 	GetMouse(&where, &mouseButtons, true);
286 
287 	// button not pressed, exit
288 	if (! (mouseButtons & B_PRIMARY_MOUSE_BUTTON)) {
289 		Invoke();
290 		SetTracking(false);
291 	}
292 
293 	UpdatePosition(point);
294 }
295 
296 
297 void
298 TrackSlider::MouseDown(BPoint point)
299 {
300 	if (!Bounds().InsetBySelf(2,2).Contains(point))
301 		return;
302 
303 	UpdatePosition(point);
304 	SetTracking(true);
305 	SetMouseEventMask(B_POINTER_EVENTS, B_NO_POINTER_HISTORY| B_LOCK_WINDOW_FOCUS);
306 }
307 
308 
309 void
310 TrackSlider::MouseUp(BPoint point)
311 {
312 	if (!IsTracking())
313 		return;
314 	if (Bounds().InsetBySelf(2,2).Contains(point)) {
315 		UpdatePosition(point);
316 	}
317 
318 	fLeftTracking = fRightTracking = fMainTracking = false;
319 
320 	Invoke();
321 	SetTracking(false);
322 	Draw(Bounds());
323 	Flush();
324 }
325 
326 
327 void
328 TrackSlider::UpdatePosition(BPoint point)
329 {
330 	BRect leftRect(fLeftX-9, SLIDER_BASE+3, fLeftX, SLIDER_BASE+16);
331 	BRect rightRect(fRightX, SLIDER_BASE+3, fRightX+9, SLIDER_BASE+16);
332 
333 	if (!(fRightTracking || fMainTracking) && (fLeftTracking || ((point.x < fPositionX-4) && leftRect.Contains(point)))) {
334 		if (!IsTracking())
335 			fLastX = point.x - fLeftX;
336 		fLeftX = MIN(MAX(point.x - fLastX, 15), fRight);
337 		fLeftTime = (bigtime_t)(MAX(MIN((fLeftX - 15) / (fRight - 14),1), 0) * fTotalTime);
338 		fLeftTracking = true;
339 
340 		BMessage msg = *Message();
341 		msg.AddInt64("left", fLeftTime);
342 
343 		if (fPositionX < fLeftX) {
344 			fPositionX = fLeftX + 1;
345 			fMainTime = fLeftTime;
346 			msg.AddInt64("main", fMainTime);
347 			if (fRightX < fPositionX) {
348 				fRightX = fPositionX;
349 				fRightTime = fMainTime;
350 				msg.AddInt64("right", fRightTime);
351 			}
352 		}
353 
354 		Invoke(&msg);
355 
356 		//printf("fLeftPos : %Ld\n", fLeftTime);
357 	} else if (!fMainTracking && (fRightTracking || ((point.x > fPositionX+4) && rightRect.Contains(point)))) {
358 		if (!IsTracking())
359 			fLastX = point.x - fRightX;
360 		fRightX = MIN(MAX(point.x - fLastX, 15), fRight);
361 		fRightTime = (bigtime_t)(MAX(MIN((fRightX - 15) / (fRight - 14),1), 0) * fTotalTime);
362 		fRightTracking = true;
363 
364 		BMessage msg = *Message();
365 		msg.AddInt64("right", fRightTime);
366 
367 		if (fPositionX > fRightX) {
368 			fPositionX = fRightX;
369 			fMainTime = fRightTime;
370 			msg.AddInt64("main", fMainTime);
371 			if (fLeftX > fPositionX) {
372 				fLeftX = fPositionX - 1;
373 				fLeftTime = fMainTime;
374 				msg.AddInt64("left", fLeftTime);
375 			}
376 		}
377 
378 		Invoke(&msg);
379 
380 		//printf("fRightPos : %Ld\n", fRightTime);
381 	} else {
382 		fPositionX = MIN(MAX(point.x, 15), fRight);
383 		fMainTime = (bigtime_t)(MAX(MIN((fPositionX - 15) / (fRight - 14),1), 0) * fTotalTime);
384 		fMainTracking = true;
385 
386 		BMessage msg = *Message();
387 		msg.AddInt64("main", fMainTime);
388 
389 		if (fRightX < fPositionX) {
390 			fRightX = fPositionX;
391 			fRightTime = fMainTime;
392 			msg.AddInt64("right", fRightTime);
393 		} else if (fLeftX > fPositionX) {
394 			fLeftX = fPositionX - 1;
395 			fLeftTime = fMainTime;
396 			msg.AddInt64("left", fLeftTime);
397 		}
398 
399 		Invoke(&msg);
400 		//printf("fPosition : %Ld\n", fMainTime);
401 	}
402 	Draw(Bounds());
403 	Flush();
404 }
405 
406 
407 void
408 TrackSlider::TimeToString(bigtime_t timestamp, char *string)
409 {
410 	uint32 hours = timestamp / 3600000000LL;
411 	timestamp -= hours * 3600000000LL;
412 	uint32 minutes = timestamp / 60000000LL;
413 	timestamp -= minutes * 60000000LL;
414 	uint32 seconds = timestamp / 1000000LL;
415 	timestamp -= seconds * 1000000LL;
416 	uint32 centiseconds = timestamp / 10000LL;
417 	sprintf(string, "%02ld:%02ld:%02ld:%02ld", hours, minutes, seconds, centiseconds);
418 
419 }
420 
421 
422 void
423 TrackSlider::SetMainTime(bigtime_t timestamp, bool reset)
424 {
425 	fMainTime = timestamp;
426 	fPositionX = 15 + (fRight - 14) * ((double)fMainTime / fTotalTime);
427 	if (reset) {
428 		fRightTime = fTotalTime;
429 		fLeftTime = 0;
430 		fLeftX = 14 + (fRight - 15) * ((double)fLeftTime / fTotalTime);
431 		fRightX = 15 + (fRight - 16) * ((double)fRightTime / fTotalTime);
432 	}
433 	Invalidate();
434 }
435 
436 void
437 TrackSlider::SetTotalTime(bigtime_t timestamp, bool reset)
438 {
439 	fTotalTime = timestamp;
440 	if (reset) {
441 		fMainTime = 0;
442 		fRightTime = fTotalTime;
443 		fLeftTime = 0;
444 	}
445 	fPositionX = 15 + (fRight - 14) * ((double)fMainTime / fTotalTime);
446 	fLeftX = 14 + (fRight - 15) * ((double)fLeftTime / fTotalTime);
447 	fRightX = 15 + (fRight - 16) * ((double)fRightTime / fTotalTime);
448 	Invalidate();
449 }
450 
451 
452 void
453 TrackSlider::ResetMainTime()
454 {
455 	fMainTime = fLeftTime;
456 	fPositionX = 15 + (fRight - 14) * ((double)fMainTime / fTotalTime);
457 	Invalidate();
458 }
459 
460 
461 void
462 TrackSlider::FrameResized(float width, float height)
463 {
464 	fRight = Bounds().right - kLeftRightTrackSliderWidth;
465 	fPositionX = 15 + (fRight - 14) * ((double)fMainTime / fTotalTime);
466 	fLeftX = 14 + (fRight - 15) * ((double)fLeftTime / fTotalTime);
467 	fRightX = 15 + (fRight - 16) * ((double)fRightTime / fTotalTime);
468 	Invalidate();
469 }
470