]> git.saurik.com Git - wxWidgets.git/blame - src/generic/scrolwin.cpp
image handlers moved to separate headers (imagbmp.h etc.) This change is backward...
[wxWidgets.git] / src / generic / scrolwin.cpp
CommitLineData
c801d85f
KB
1/////////////////////////////////////////////////////////////////////////////
2// Name: scrolwin.cpp
3// Purpose: wxScrolledWindow implementation
4// Author: Julian Smart
5// Modified by:
6// Created: 01/02/97
7// RCS-ID: $Id$
8// Copyright: (c) Julian Smart and Markus Holzem
9// Licence: wxWindows license
10/////////////////////////////////////////////////////////////////////////////
11
12#ifdef __GNUG__
13#pragma implementation
14#pragma implementation "scrolwin.h"
15#endif
16
17// For compilers that support precompilation, includes "wx.h".
18#include "wx/wxprec.h"
19
20#include "wx/utils.h"
21#include "wx/dcclient.h"
22
c801d85f
KB
23#ifdef __BORLANDC__
24#pragma hdrstop
25#endif
26
27#include "wx/generic/scrolwin.h"
053f9cc1 28#include "wx/panel.h"
c801d85f 29
053f9cc1 30BEGIN_EVENT_TABLE(wxScrolledWindow, wxPanel)
c5b42c87 31 EVT_SCROLLWIN(wxScrolledWindow::OnScroll)
139adb6a
RR
32 EVT_SIZE(wxScrolledWindow::OnSize)
33 EVT_PAINT(wxScrolledWindow::OnPaint)
c801d85f
KB
34END_EVENT_TABLE()
35
053f9cc1 36IMPLEMENT_DYNAMIC_CLASS(wxScrolledWindow, wxPanel)
c801d85f 37
48d1144b
JS
38#ifdef __WXMSW__
39#include "windows.h"
40#endif
41
a91b47e8
JS
42#ifdef __WXMOTIF__
43// For wxRETAINED implementation
338dd992
JJ
44#ifdef __VMS__ //VMS's Xm.h is not (yet) compatible with C++
45 //This code switches off the compiler warnings
46# pragma message disable nosimpint
47#endif
a91b47e8 48#include <Xm/Xm.h>
338dd992
JJ
49#ifdef __VMS__
50# pragma message enable nosimpint
51#endif
a91b47e8
JS
52#endif
53
c5b42c87 54wxScrolledWindow::wxScrolledWindow()
c801d85f 55{
139adb6a
RR
56 m_xScrollPixelsPerLine = 0;
57 m_yScrollPixelsPerLine = 0;
58 m_xScrollingEnabled = TRUE;
59 m_yScrollingEnabled = TRUE;
60 m_xScrollPosition = 0;
61 m_yScrollPosition = 0;
62 m_xScrollLines = 0;
63 m_yScrollLines = 0;
64 m_xScrollLinesPerPage = 0;
65 m_yScrollLinesPerPage = 0;
66 m_scaleX = 1.0;
67 m_scaleY = 1.0;
c801d85f
KB
68}
69
debe6624 70bool wxScrolledWindow::Create(wxWindow *parent, wxWindowID id,
c801d85f
KB
71 const wxPoint& pos,
72 const wxSize& size,
debe6624 73 long style,
c801d85f
KB
74 const wxString& name)
75{
139adb6a
RR
76 m_xScrollPixelsPerLine = 0;
77 m_yScrollPixelsPerLine = 0;
78 m_xScrollingEnabled = TRUE;
79 m_yScrollingEnabled = TRUE;
80 m_xScrollPosition = 0;
81 m_yScrollPosition = 0;
82 m_xScrollLines = 0;
83 m_yScrollLines = 0;
84 m_xScrollLinesPerPage = 0;
85 m_yScrollLinesPerPage = 0;
86 m_scaleX = 1.0;
87 m_scaleY = 1.0;
ecab4dba
RR
88
89 m_targetWindow = this;
139adb6a 90
053f9cc1 91 return wxPanel::Create(parent, id, pos, size, style, name);
c801d85f
KB
92}
93
94/*
95 * pixelsPerUnitX/pixelsPerUnitY: number of pixels per unit (e.g. pixels per text line)
96 * noUnitsX/noUnitsY: : no. units per scrollbar
97 */
debe6624
JS
98void wxScrolledWindow::SetScrollbars (int pixelsPerUnitX, int pixelsPerUnitY,
99 int noUnitsX, int noUnitsY,
100 int xPos, int yPos, bool noRefresh )
c801d85f 101{
139adb6a
RR
102 bool do_refresh =
103 (
c801d85f 104 (noUnitsX != 0 && m_xScrollLines == 0) ||
ea5c6ca7 105 (noUnitsX < m_xScrollLines) ||
c801d85f 106 (noUnitsY != 0 && m_yScrollLines == 0) ||
ea5c6ca7 107 (noUnitsY < m_yScrollLines) ||
c801d85f
KB
108 (xPos != m_xScrollPosition) ||
109 (yPos != m_yScrollPosition) ||
110 (pixelsPerUnitX != m_xScrollPixelsPerLine) ||
111 (pixelsPerUnitY != m_yScrollPixelsPerLine)
139adb6a 112 );
c801d85f 113
139adb6a
RR
114 m_xScrollPixelsPerLine = pixelsPerUnitX;
115 m_yScrollPixelsPerLine = pixelsPerUnitY;
116 m_xScrollPosition = xPos;
117 m_yScrollPosition = yPos;
118 m_xScrollLines = noUnitsX;
119 m_yScrollLines = noUnitsY;
a91b47e8
JS
120
121#ifdef __WXMOTIF__
122 // Sorry, some Motif-specific code to implement a backing pixmap
123 // for the wxRETAINED style. Implementing a backing store can't
124 // be entirely generic because it relies on the wxWindowDC implementation
125 // to duplicate X drawing calls for the backing pixmap.
126
127 if ((m_windowStyle & wxRETAINED) == wxRETAINED)
128 {
129 Display* dpy = XtDisplay((Widget) GetMainWidget());
130
131 int totalPixelWidth = m_xScrollLines * m_xScrollPixelsPerLine;
132 int totalPixelHeight = m_yScrollLines * m_yScrollPixelsPerLine;
133 if (m_backingPixmap &&
134 !((m_pixmapWidth == totalPixelWidth) &&
135 (m_pixmapHeight == totalPixelHeight)))
136 {
137 XFreePixmap (dpy, (Pixmap) m_backingPixmap);
138 m_backingPixmap = (WXPixmap) 0;
139 }
140
141 if (!m_backingPixmap &&
142 (noUnitsX != 0) && (noUnitsY != 0))
143 {
144 int depth = wxDisplayDepth();
145 m_pixmapWidth = totalPixelWidth;
146 m_pixmapHeight = totalPixelHeight;
147 m_backingPixmap = (WXPixmap) XCreatePixmap (dpy, RootWindow (dpy, DefaultScreen (dpy)),
148 m_pixmapWidth, m_pixmapHeight, depth);
149 }
150
151 }
152#endif
d4c99d6f 153
139adb6a 154 AdjustScrollbars();
c801d85f 155
ea5c6ca7 156 if (do_refresh && !noRefresh)
ecab4dba 157 m_targetWindow->Refresh();
c801d85f 158
2049ba38 159#ifdef __WXMSW__
48d1144b 160 // Necessary?
7c74e7fe
SC
161 UpdateWindow ((HWND) m_targetWindow->GetHWND());
162#endif
163#ifdef __WXMAC__
164 m_targetWindow->MacUpdateImmediately() ;
c801d85f
KB
165#endif
166}
167
ecab4dba
RR
168wxScrolledWindow::~wxScrolledWindow()
169{
170}
171
172void wxScrolledWindow::SetTargetWindow( wxWindow *target )
173{
174 wxASSERT_MSG( target, wxT("target window must not be NULL") );
175 m_targetWindow = target;
176}
177
178wxWindow *wxScrolledWindow::GetTargetWindow()
179{
180 return m_targetWindow;
181}
182
c5b42c87 183void wxScrolledWindow::OnScroll(wxScrollWinEvent& event)
c801d85f 184{
139adb6a 185 int orient = event.GetOrientation();
c801d85f 186
139adb6a
RR
187 int nScrollInc = CalcScrollInc(event);
188 if (nScrollInc == 0) return;
c801d85f 189
139adb6a
RR
190 if (orient == wxHORIZONTAL)
191 {
192 int newPos = m_xScrollPosition + nScrollInc;
193 SetScrollPos(wxHORIZONTAL, newPos, TRUE );
194 }
195 else
196 {
197 int newPos = m_yScrollPosition + nScrollInc;
198 SetScrollPos(wxVERTICAL, newPos, TRUE );
199 }
c801d85f 200
139adb6a
RR
201 if (orient == wxHORIZONTAL)
202 {
203 m_xScrollPosition += nScrollInc;
204 }
c801d85f 205 else
139adb6a
RR
206 {
207 m_yScrollPosition += nScrollInc;
208 }
209
210 if (orient == wxHORIZONTAL)
211 {
212 if (m_xScrollingEnabled)
ecab4dba 213 m_targetWindow->ScrollWindow(-m_xScrollPixelsPerLine * nScrollInc, 0, (const wxRect *) NULL);
139adb6a 214 else
ecab4dba 215 m_targetWindow->Refresh();
139adb6a 216 }
c801d85f 217 else
139adb6a
RR
218 {
219 if (m_yScrollingEnabled)
ecab4dba 220 m_targetWindow->ScrollWindow(0, -m_yScrollPixelsPerLine * nScrollInc, (const wxRect *) NULL);
139adb6a 221 else
ecab4dba 222 m_targetWindow->Refresh();
c801d85f 223 }
7c74e7fe
SC
224#ifdef __WXMAC__
225 m_targetWindow->MacUpdateImmediately() ;
226#endif
c801d85f
KB
227}
228
c5b42c87 229int wxScrolledWindow::CalcScrollInc(wxScrollWinEvent& event)
c801d85f 230{
ecab4dba
RR
231 int pos = event.GetPosition();
232 int orient = event.GetOrientation();
c801d85f 233
ecab4dba
RR
234 int nScrollInc = 0;
235 switch (event.GetEventType())
c801d85f 236 {
ecab4dba
RR
237 case wxEVT_SCROLLWIN_TOP:
238 {
239 if (orient == wxHORIZONTAL)
240 nScrollInc = - m_xScrollPosition;
241 else
242 nScrollInc = - m_yScrollPosition;
243 break;
244 }
245 case wxEVT_SCROLLWIN_BOTTOM:
246 {
247 if (orient == wxHORIZONTAL)
248 nScrollInc = m_xScrollLines - m_xScrollPosition;
249 else
250 nScrollInc = m_yScrollLines - m_yScrollPosition;
251 break;
252 }
253 case wxEVT_SCROLLWIN_LINEUP:
254 {
255 nScrollInc = -1;
256 break;
257 }
258 case wxEVT_SCROLLWIN_LINEDOWN:
259 {
260 nScrollInc = 1;
261 break;
262 }
263 case wxEVT_SCROLLWIN_PAGEUP:
264 {
265 if (orient == wxHORIZONTAL)
266 nScrollInc = -GetScrollPageSize(wxHORIZONTAL);
267 else
268 nScrollInc = -GetScrollPageSize(wxVERTICAL);
269 break;
270 }
271 case wxEVT_SCROLLWIN_PAGEDOWN:
272 {
273 if (orient == wxHORIZONTAL)
274 nScrollInc = GetScrollPageSize(wxHORIZONTAL);
275 else
276 nScrollInc = GetScrollPageSize(wxVERTICAL);
277 break;
278 }
279 case wxEVT_SCROLLWIN_THUMBTRACK:
280 {
281 if (orient == wxHORIZONTAL)
282 nScrollInc = pos - m_xScrollPosition;
283 else
284 nScrollInc = pos - m_yScrollPosition;
285 break;
286 }
287 default:
288 {
289 break;
290 }
c801d85f 291 }
88150e60 292
ecab4dba
RR
293 if (orient == wxHORIZONTAL)
294 {
295 if (m_xScrollPixelsPerLine > 0)
296 {
297 int w, h;
298 m_targetWindow->GetClientSize(&w, &h);
299
300 int nMaxWidth = m_xScrollLines*m_xScrollPixelsPerLine;
301 int noPositions = (int) ( ((nMaxWidth - w)/(double)m_xScrollPixelsPerLine) + 0.5 );
302 if (noPositions < 0)
303 noPositions = 0;
304
305 if ( (m_xScrollPosition + nScrollInc) < 0 )
306 nScrollInc = -m_xScrollPosition; // As -ve as we can go
307 else if ( (m_xScrollPosition + nScrollInc) > noPositions )
308 nScrollInc = noPositions - m_xScrollPosition; // As +ve as we can go
309 }
310 else
311 m_targetWindow->Refresh();
9d9355c6
VZ
312 }
313 else
ecab4dba
RR
314 {
315 if (m_yScrollPixelsPerLine > 0)
316 {
317 int w, h;
318 m_targetWindow->GetClientSize(&w, &h);
9d9355c6 319
ecab4dba
RR
320 int nMaxHeight = m_yScrollLines*m_yScrollPixelsPerLine;
321 int noPositions = (int) ( ((nMaxHeight - h)/(double)m_yScrollPixelsPerLine) + 0.5 );
322 if (noPositions < 0)
323 noPositions = 0;
9d9355c6 324
ecab4dba
RR
325 if ( (m_yScrollPosition + nScrollInc) < 0 )
326 nScrollInc = -m_yScrollPosition; // As -ve as we can go
327 else if ( (m_yScrollPosition + nScrollInc) > noPositions )
328 nScrollInc = noPositions - m_yScrollPosition; // As +ve as we can go
329 }
330 else
331 m_targetWindow->Refresh();
9d9355c6 332 }
9d9355c6 333
ecab4dba 334 return nScrollInc;
c801d85f
KB
335}
336
337// Adjust the scrollbars - new version.
27d029c7 338void wxScrolledWindow::AdjustScrollbars()
c801d85f 339{
139adb6a 340 int w, h;
ecab4dba 341 m_targetWindow->GetClientSize(&w, &h);
27d029c7
RR
342
343 int oldXScroll = m_xScrollPosition;
344 int oldYScroll = m_yScrollPosition;
c801d85f 345
139adb6a
RR
346 if (m_xScrollLines > 0)
347 {
c801d85f
KB
348 // Calculate page size i.e. number of scroll units you get on the
349 // current client window
ecab4dba 350 int noPagePositions = (int) ( (w/(double)m_xScrollPixelsPerLine) + 0.5 );
139adb6a 351 if (noPagePositions < 1) noPagePositions = 1;
c801d85f 352
139adb6a
RR
353 // Correct position if greater than extent of canvas minus
354 // the visible portion of it or if below zero
355 m_xScrollPosition = wxMin( m_xScrollLines-noPagePositions, m_xScrollPosition);
356 m_xScrollPosition = wxMax( 0, m_xScrollPosition );
c801d85f 357
139adb6a 358 SetScrollbar(wxHORIZONTAL, m_xScrollPosition, noPagePositions, m_xScrollLines);
88150e60
JS
359 // The amount by which we scroll when paging
360 SetScrollPageSize(wxHORIZONTAL, noPagePositions);
139adb6a
RR
361 }
362 else
363 {
364 m_xScrollPosition = 0;
365 SetScrollbar (wxHORIZONTAL, 0, 0, 0, FALSE);
366 }
367
368 if (m_yScrollLines > 0)
369 {
c801d85f
KB
370 // Calculate page size i.e. number of scroll units you get on the
371 // current client window
ecab4dba 372 int noPagePositions = (int) ( (h/(double)m_yScrollPixelsPerLine) + 0.5 );
139adb6a 373 if (noPagePositions < 1) noPagePositions = 1;
c801d85f 374
139adb6a
RR
375 // Correct position if greater than extent of canvas minus
376 // the visible portion of it or if below zero
377 m_yScrollPosition = wxMin( m_yScrollLines-noPagePositions, m_yScrollPosition );
378 m_yScrollPosition = wxMax( 0, m_yScrollPosition );
379
380 SetScrollbar(wxVERTICAL, m_yScrollPosition, noPagePositions, m_yScrollLines);
88150e60
JS
381 // The amount by which we scroll when paging
382 SetScrollPageSize(wxVERTICAL, noPagePositions);
139adb6a
RR
383 }
384 else
385 {
386 m_yScrollPosition = 0;
387 SetScrollbar (wxVERTICAL, 0, 0, 0, FALSE);
388 }
27d029c7
RR
389
390 if (oldXScroll != m_xScrollPosition)
391 {
392 if (m_xScrollingEnabled)
ecab4dba 393 m_targetWindow->ScrollWindow( m_xScrollPixelsPerLine * (oldXScroll-m_xScrollPosition), 0, (const wxRect *) NULL );
27d029c7 394 else
ecab4dba 395 m_targetWindow->Refresh();
27d029c7
RR
396 }
397
398 if (oldYScroll != m_yScrollPosition)
399 {
400 if (m_yScrollingEnabled)
ecab4dba 401 m_targetWindow->ScrollWindow( 0, m_yScrollPixelsPerLine * (oldYScroll-m_yScrollPosition), (const wxRect *) NULL );
27d029c7 402 else
ecab4dba 403 m_targetWindow->Refresh();
27d029c7 404 }
c801d85f
KB
405}
406
407// Default OnSize resets scrollbars, if any
408void wxScrolledWindow::OnSize(wxSizeEvent& WXUNUSED(event))
409{
47d67540 410#if wxUSE_CONSTRAINTS
139adb6a 411 if (GetAutoLayout()) Layout();
c801d85f
KB
412#endif
413
139adb6a 414 AdjustScrollbars();
c801d85f
KB
415}
416
417// This calls OnDraw, having adjusted the origin according to the current
418// scroll position
419void wxScrolledWindow::OnPaint(wxPaintEvent& WXUNUSED(event))
420{
139adb6a
RR
421 wxPaintDC dc(this);
422 PrepareDC(dc);
c801d85f 423
139adb6a 424 OnDraw(dc);
c801d85f
KB
425}
426
427// Override this function if you don't want to have wxScrolledWindow
428// automatically change the origin according to the scroll position.
429void wxScrolledWindow::PrepareDC(wxDC& dc)
430{
139adb6a
RR
431 dc.SetDeviceOrigin( -m_xScrollPosition * m_xScrollPixelsPerLine,
432 -m_yScrollPosition * m_yScrollPixelsPerLine );
433 dc.SetUserScale( m_scaleX, m_scaleY );
c801d85f
KB
434}
435
436#if WXWIN_COMPATIBILITY
437void wxScrolledWindow::GetScrollUnitsPerPage (int *x_page, int *y_page) const
438{
439 *x_page = GetScrollPageSize(wxHORIZONTAL);
440 *y_page = GetScrollPageSize(wxVERTICAL);
441}
a0bc2c1d
VZ
442
443void wxScrolledWindow::CalcUnscrolledPosition(int x, int y, float *xx, float *yy) const
444{
445 if ( xx )
446 *xx = (float)(x + m_xScrollPosition * m_xScrollPixelsPerLine);
447 if ( yy )
448 *yy = (float)(y + m_yScrollPosition * m_yScrollPixelsPerLine);
449}
450#endif // WXWIN_COMPATIBILITY
c801d85f
KB
451
452void wxScrolledWindow::GetScrollPixelsPerUnit (int *x_unit, int *y_unit) const
453{
a0bc2c1d
VZ
454 if ( x_unit )
455 *x_unit = m_xScrollPixelsPerLine;
456 if ( y_unit )
457 *y_unit = m_yScrollPixelsPerLine;
c801d85f
KB
458}
459
460int wxScrolledWindow::GetScrollPageSize(int orient) const
461{
462 if ( orient == wxHORIZONTAL )
463 return m_xScrollLinesPerPage;
464 else
465 return m_yScrollLinesPerPage;
466}
467
468void wxScrolledWindow::SetScrollPageSize(int orient, int pageSize)
469{
470 if ( orient == wxHORIZONTAL )
471 m_xScrollLinesPerPage = pageSize;
472 else
473 m_yScrollLinesPerPage = pageSize;
474}
475
476/*
477 * Scroll to given position (scroll position, not pixel position)
478 */
139adb6a 479void wxScrolledWindow::Scroll( int x_pos, int y_pos )
c801d85f 480{
139adb6a
RR
481 if (((x_pos == -1) || (x_pos == m_xScrollPosition)) &&
482 ((y_pos == -1) || (y_pos == m_yScrollPosition))) return;
483
484 int w, h;
ecab4dba 485 m_targetWindow->GetClientSize(&w, &h);
c801d85f 486
139adb6a 487 if (x_pos != -1)
c801d85f 488 {
ed673c6a 489 int old_x = m_xScrollPosition;
139adb6a
RR
490 m_xScrollPosition = x_pos;
491
492 // Calculate page size i.e. number of scroll units you get on the
493 // current client window
ecab4dba 494 int noPagePositions = (int) ( (w/(double)m_xScrollPixelsPerLine) + 0.5 );
139adb6a
RR
495 if (noPagePositions < 1) noPagePositions = 1;
496
497 // Correct position if greater than extent of canvas minus
498 // the visible portion of it or if below zero
499 m_xScrollPosition = wxMin( m_xScrollLines-noPagePositions, m_xScrollPosition );
500 m_xScrollPosition = wxMax( 0, m_xScrollPosition );
501
ecab4dba 502 m_targetWindow->SetScrollPos( wxHORIZONTAL, m_xScrollPosition, TRUE );
ed673c6a 503
ecab4dba 504 m_targetWindow->ScrollWindow( (old_x-m_xScrollPosition)*m_xScrollPixelsPerLine, 0 );
c801d85f 505 }
139adb6a 506 if (y_pos != -1)
c801d85f 507 {
ed673c6a 508 int old_y = m_yScrollPosition;
139adb6a
RR
509 m_yScrollPosition = y_pos;
510
511 // Calculate page size i.e. number of scroll units you get on the
512 // current client window
ecab4dba 513 int noPagePositions = (int) ( (h/(double)m_yScrollPixelsPerLine) + 0.5 );
139adb6a
RR
514 if (noPagePositions < 1) noPagePositions = 1;
515
516 // Correct position if greater than extent of canvas minus
517 // the visible portion of it or if below zero
518 m_yScrollPosition = wxMin( m_yScrollLines-noPagePositions, m_yScrollPosition );
519 m_yScrollPosition = wxMax( 0, m_yScrollPosition );
520
ecab4dba 521 m_targetWindow->SetScrollPos( wxVERTICAL, m_yScrollPosition, TRUE );
ed673c6a 522
ecab4dba 523 m_targetWindow->ScrollWindow( 0, (old_y-m_yScrollPosition)*m_yScrollPixelsPerLine );
c801d85f 524 }
139adb6a 525
139adb6a 526
2049ba38 527#ifdef __WXMSW__
ed673c6a 528// ::UpdateWindow ((HWND) GetHWND());
5e014a0c 529#else
ed673c6a 530// Refresh();
c801d85f 531#endif
7c74e7fe
SC
532#ifdef __WXMAC__
533 m_targetWindow->MacUpdateImmediately() ;
534#endif
c801d85f
KB
535}
536
debe6624 537void wxScrolledWindow::EnableScrolling (bool x_scroll, bool y_scroll)
c801d85f 538{
139adb6a
RR
539 m_xScrollingEnabled = x_scroll;
540 m_yScrollingEnabled = y_scroll;
c801d85f
KB
541}
542
543void wxScrolledWindow::GetVirtualSize (int *x, int *y) const
544{
a0bc2c1d
VZ
545 if ( x )
546 *x = m_xScrollPixelsPerLine * m_xScrollLines;
547 if ( y )
548 *y = m_yScrollPixelsPerLine * m_yScrollLines;
c801d85f
KB
549}
550
551// Where the current view starts from
552void wxScrolledWindow::ViewStart (int *x, int *y) const
553{
a0bc2c1d
VZ
554 if ( x )
555 *x = m_xScrollPosition;
556 if ( y )
557 *y = m_yScrollPosition;
c801d85f
KB
558}
559
debe6624 560void wxScrolledWindow::CalcScrolledPosition(int x, int y, int *xx, int *yy) const
c801d85f 561{
a0bc2c1d
VZ
562 if ( xx )
563 *xx = x - m_xScrollPosition * m_xScrollPixelsPerLine;
564 if ( yy )
565 *yy = y - m_yScrollPosition * m_yScrollPixelsPerLine;
c801d85f
KB
566}
567
a0bc2c1d 568void wxScrolledWindow::CalcUnscrolledPosition(int x, int y, int *xx, int *yy) const
c801d85f 569{
a0bc2c1d
VZ
570 if ( xx )
571 *xx = x + m_xScrollPosition * m_xScrollPixelsPerLine;
572 if ( yy )
573 *yy = y + m_yScrollPosition * m_yScrollPixelsPerLine;
c801d85f 574}