]> git.saurik.com Git - wxWidgets.git/blob - contrib/src/gizmos/multicell.cpp
simplified (hopefully:) and explained the shared lib versioning system.
[wxWidgets.git] / contrib / src / gizmos / multicell.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: multicell.cpp
3 // Purpose: provide two new classes for layout, wxMultiCellSizer and wxMultiCellCanvas
4 // Author: Jonathan Bayer
5 // Modified by:
6 // Created:
7 // RCS-ID: $Id:
8 // Copyright: (c) Jonathan Bayer
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
11
12 // This was inspired by the gbsizer class written by Alex Andruschak
13
14 #ifdef __GNUG__
15 #pragma implementation "multicell.h"
16 #endif
17
18 // For compilers that support precompilation, includes "wx.h".
19 #include "wx/wxprec.h"
20
21 #ifdef __BORLANDC__
22 #pragma hdrstop
23 #endif
24
25 // ----------------------------------------------------------------------------
26 // headers
27 // ----------------------------------------------------------------------------
28
29 #ifndef WX_PRECOMP
30 #include "wx/wx.h"
31 #endif
32
33 #include "wx/gizmos/multicell.h"
34
35
36
37
38 //---------------------------------------------------------------------------
39
40 IMPLEMENT_ABSTRACT_CLASS(wxMultiCellSizer, wxSizer);
41 IMPLEMENT_ABSTRACT_CLASS(wxMultiCellItemHandle, wxRect);
42
43 //---------------------------------------------------------------------------
44 // wxMultiCellItemHandle
45 //---------------------------------------------------------------------------
46 /*
47 *Function Name: wxMultiCellItemHandle :: wxMultiCellItemHandle
48 *
49 *Parameters: int row
50 * int column
51 * int height
52 * int width
53 * wxSize size
54 * wxResizable Style
55 * wxSize weight
56 * int align
57 *
58 *Description: This is the constructor for the class.
59 *
60 */
61
62 void wxMultiCellItemHandle :: Initialize( int row, int column, int height, int width, wxSize size, wxResizable Style, wxSize weight, int align)
63 {
64 m_column = column;
65 m_row = row;
66 m_width = width;
67 m_height = height;
68
69 m_style = Style;
70 m_fixedSize = size;
71 m_alignment = align;
72 m_weight = weight;
73 }
74 //---------------------------------------------------------------------------
75 wxMultiCellItemHandle :: wxMultiCellItemHandle( int row, int column, int height, int width, wxSize size, wxResizable Style, wxSize weight, int align)
76 {
77 Initialize(row, column,height, width, size, Style, weight, align);
78 }
79 //---------------------------------------------------------------------------
80 wxMultiCellItemHandle :: wxMultiCellItemHandle( int row, int column, wxSize size, wxResizable style, wxSize weight, int align)
81 {
82 Initialize(row, column,1, 1, size, style, weight, align);
83 }
84 //---------------------------------------------------------------------------
85 wxMultiCellItemHandle :: wxMultiCellItemHandle( int row, int column, wxResizable style, wxSize weight, int align)
86 {
87 Initialize(row, column, 1, 1, wxSize(1, 1), style, weight, align);
88 }
89 //---------------------------------------------------------------------------
90 wxMultiCellItemHandle :: wxMultiCellItemHandle( int row, int column, int align)
91 {
92 Initialize(row, column, 1, 1, wxSize(1,1), wxNOT_RESIZABLE, wxSize(1, 1), align);
93 }
94
95 //---------------------------------------------------------------------------
96 int wxMultiCellItemHandle::GetColumn()
97 {
98 return m_column;
99 }
100 //---------------------------------------------------------------------------
101 int wxMultiCellItemHandle::GetRow()
102 {
103 return m_row;
104 }
105 //---------------------------------------------------------------------------
106 int wxMultiCellItemHandle::GetWidth()
107 {
108 return m_width;
109 }
110 //---------------------------------------------------------------------------
111 int wxMultiCellItemHandle::GetHeight()
112 {
113 return m_height;
114 }
115 //---------------------------------------------------------------------------
116 wxResizable wxMultiCellItemHandle :: GetStyle()
117 {
118 return m_style;
119 };
120 //---------------------------------------------------------------------------
121 wxSize wxMultiCellItemHandle :: GetLocalSize()
122 {
123 return m_fixedSize;
124 };
125 //---------------------------------------------------------------------------
126 int wxMultiCellItemHandle :: GetAlignment()
127 {
128 return m_alignment;
129 };
130 //---------------------------------------------------------------------------
131 wxSize wxMultiCellItemHandle :: GetWeight()
132 {
133 return m_weight;
134 };
135
136
137
138 //---------------------------------------------------------------------------
139
140 //---------------------------------------------------------------------------
141 // wxMultiCellSizer
142 //---------------------------------------------------------------------------
143
144 /*
145 *Function Name: wxMultiCellSizer::Initialize
146 *
147 *Parameters: wxsize Initial size of sizer
148 *
149 *Description: This is a common function to initialize all the members of
150 * this class. It is only called from the constructors
151 *
152 */
153
154 void wxMultiCellSizer::Initialize( wxSize size )
155 {
156 m_cell_count = size;
157 m_maxHeight = (int *)malloc((1 + m_cell_count.GetHeight()) * sizeof(int));
158 m_maxWidth = (int *)malloc( (1 + m_cell_count.GetWidth()) * sizeof(int));
159 m_rowStretch = (int *)malloc( (1 + m_cell_count.GetHeight()) * sizeof(int));
160 m_colStretch = (int *)malloc((1 + m_cell_count.GetWidth()) * sizeof(int));
161
162 m_weights = (wxSize **)malloc((1 + wxMax(m_cell_count.GetHeight(), m_cell_count.GetWidth())) * sizeof(wxSize *));
163 m_minSizes = (wxSize **)malloc((1 + wxMax(m_cell_count.GetHeight(), m_cell_count.GetWidth())) * sizeof(wxSize *));
164 for (int x = 0; x < 1 + wxMax(m_cell_count.GetHeight(), m_cell_count.GetWidth()); x++)
165 {
166 m_weights[x] = new wxSize(0,0);
167 m_minSizes[x] = new wxSize(0,0);
168 }
169
170 m_maxWeights = 1 + wxMax(m_cell_count.GetHeight(), m_cell_count.GetWidth());
171 m_defaultCellSize = wxSize(5, 5);
172 m_win = NULL;
173 m_pen = wxRED_PEN;
174 }
175 //---------------------------------------------------------------------------
176 wxMultiCellSizer::wxMultiCellSizer( wxSize & size )
177 {
178 Initialize(size);
179 }
180 //---------------------------------------------------------------------------
181 wxMultiCellSizer::wxMultiCellSizer( int rows, int cols)
182 {
183 wxSize size(cols, rows);
184 Initialize(size);
185 }
186 //---------------------------------------------------------------------------
187 wxMultiCellSizer::~wxMultiCellSizer()
188 {
189 m_children.DeleteContents(TRUE);
190
191 free(m_maxHeight);
192 free(m_maxWidth);
193 free(m_rowStretch);
194 free(m_colStretch);
195
196 for (int x = 0; x < 1 + wxMax(m_cell_count.GetHeight(), m_cell_count.GetWidth()); x++)
197 {
198 delete m_weights[x];
199 delete m_minSizes[x];
200 }
201 free(m_weights);
202 free(m_minSizes);
203 }
204 //---------------------------------------------------------------------------
205 bool wxMultiCellSizer::EnableGridLines(wxWindow *win)
206 {
207 m_win = win;
208 return TRUE;
209 }
210 //---------------------------------------------------------------------------
211 bool wxMultiCellSizer::SetGridPen(wxPen *pen)
212 {
213 m_pen = pen;
214 return TRUE;
215 }
216
217 //---------------------------------------------------------------------------
218 bool wxMultiCellSizer::SetDefaultCellSize(wxSize size)
219 {
220 m_defaultCellSize = size;
221 return TRUE;
222 }
223 //---------------------------------------------------------------------------
224 bool wxMultiCellSizer::SetColumnWidth(int column, int colSize, bool expandable)
225 {
226 if (expandable)
227 {
228 m_minSizes[column]->SetWidth(-colSize);
229 }
230 else
231 {
232 m_minSizes[column]->SetWidth(colSize);
233 }
234 return TRUE;
235 }
236 //---------------------------------------------------------------------------
237 bool wxMultiCellSizer::SetRowHeight(int row, int rowSize, bool expandable)
238 {
239 if (expandable)
240 {
241 m_minSizes[row]->SetHeight(-rowSize);
242 }
243 else
244 {
245 m_minSizes[row]->SetHeight(rowSize);
246 }
247 return TRUE;
248 }
249 //---------------------------------------------------------------------------
250 void wxMultiCellSizer::RecalcSizes()
251 {
252 if (m_children.GetCount() == 0)
253 return;
254 wxSize size = GetSize();
255 wxPoint pos = GetPosition();
256
257 GetMinimums();
258
259 // We need to take the unused space and equally give it out to all the rows/columns
260 // which are stretchable
261
262 int unUsedWidth = size.GetWidth() - Sum(m_maxWidth, m_cell_count.GetWidth());
263 int unUsedHeight = size.GetHeight() - Sum(m_maxHeight, m_cell_count.GetHeight());
264 int totalWidthWeight = 0;
265 int totalHeightWeight = 0;
266 int x;
267
268 for (x = 0; x < wxMax(m_cell_count.GetHeight(), m_cell_count.GetWidth()); x++)
269 {
270 if (m_rowStretch[x])
271 {
272 totalHeightWeight += m_weights[x]->GetHeight();
273 }
274 if (x < m_cell_count.GetWidth() && m_colStretch[x])
275 {
276 totalWidthWeight += m_weights[x]->GetWidth();
277 }
278 }
279 for (x = 0; x < wxMax(m_cell_count.GetHeight(), m_cell_count.GetWidth()); x++)
280 {
281 if (x < m_cell_count.GetHeight() && m_rowStretch[x])
282 {
283 m_maxHeight[x] += unUsedHeight * m_weights[x]->GetHeight() / totalHeightWeight;
284 }
285 if (x < m_cell_count.GetWidth() && m_colStretch[x])
286 {
287 m_maxWidth[x] += unUsedWidth * m_weights[x]->GetWidth() / totalWidthWeight;
288 }
289 }
290 // We now have everything we need to figure each cell position and size
291 // The arrays m_maxHeight and m_maxWidth now contain the final widths and height of
292 // each row and column.
293
294 double cell_width = (double)size.GetWidth() / (double)m_cell_count.GetWidth();
295 double cell_height = (double)size.GetHeight() / (double)m_cell_count.GetHeight();
296 wxPoint c_point;
297 wxSize c_size;
298
299 wxNode *current;
300 current = m_children.GetFirst();
301 while (current != NULL)
302 {
303 wxSizerItem *item = (wxSizerItem*) current->Data();
304
305 wxMultiCellItemHandle *rect;
306 if (item != NULL &&
307 (rect = (wxMultiCellItemHandle *)item->GetUserData()) != NULL)
308 {
309 c_point.x = pos.x + (int)(rect->GetColumn() * cell_width);
310 c_point.y = pos.y + (int)(rect->GetRow() * cell_height);
311
312 c_point.x = pos.x + Sum(m_maxWidth, rect->GetColumn());
313 c_point.y = pos.y + Sum(m_maxHeight, rect->GetRow());
314
315
316 c_size = rect->GetLocalSize();
317 wxSize minSize( item->CalcMin() );
318 if (c_size.GetHeight() != wxDefaultSize.GetHeight() ||
319 c_size.GetWidth() != wxDefaultSize.GetWidth())
320 {
321 minSize.SetHeight(wxMax(minSize.GetHeight(), c_size.GetHeight()));
322 minSize.SetWidth(wxMax(minSize.GetWidth(), c_size.GetWidth()));
323 }
324 if (rect->GetStyle() & wxHORIZENTAL_RESIZABLE ||
325 rect->GetWidth() > 1
326 || m_minSizes[rect->GetColumn()]->GetWidth() < 0)
327 {
328 int w = 0;
329 for (int x = 0; x < rect->GetWidth(); x++)
330 {
331 w += m_maxWidth[rect->GetColumn() + x];
332 }
333 c_size.SetWidth(w);
334 }
335 else
336 {
337 c_size.SetWidth(minSize.GetWidth() );
338 }
339 if (rect->GetStyle() & wxVERTICAL_RESIZABLE ||
340 rect->GetHeight() > 1 ||
341 m_minSizes[rect->GetRow()]->GetHeight() < 0)
342 {
343 int h = 0;
344 for (int x = 0; x < rect->GetHeight(); x++)
345 {
346 h += m_maxHeight[rect->GetRow() + x];
347 }
348 c_size.SetHeight(h);
349 }
350 else
351 {
352 c_size.SetHeight(minSize.GetHeight());
353 }
354 int extraHeight = (m_maxHeight[rect->GetRow()] - c_size.GetHeight());
355 int extraWidth = (m_maxWidth[rect->GetColumn()] - c_size.GetWidth());
356
357 if (rect->GetWidth() == 1 && rect->GetAlignment() & wxALIGN_CENTER_HORIZONTAL)
358 {
359 c_point.x += extraWidth / 2;
360 }
361 if (rect->GetWidth() == 1 && rect->GetAlignment() & wxALIGN_RIGHT)
362 {
363 c_point.x += extraWidth;
364 }
365 if (rect->GetHeight() == 1 && rect->GetAlignment() & wxALIGN_CENTER_VERTICAL)
366 {
367 c_point.y += extraHeight / 2;
368 }
369 if (rect->GetHeight() == 1 && rect->GetAlignment() & wxALIGN_BOTTOM)
370 {
371 c_point.y += extraHeight;
372 }
373 item->SetDimension(c_point, c_size);
374 }
375 current = current->Next();
376 }
377 }
378 //---------------------------------------------------------------------------
379 wxSize wxMultiCellSizer::CalcMin()
380 {
381 if (m_children.GetCount() == 0)
382 return wxSize(10,10);
383
384 int m_minWidth = 0;
385 int m_minHeight = 0;
386
387 GetMinimums();
388 m_minWidth = Sum(m_maxWidth, m_cell_count.GetWidth());
389 m_minHeight = Sum(m_maxHeight, m_cell_count.GetHeight());
390 return wxSize( m_minWidth, m_minHeight );
391 }
392 //---------------------------------------------------------------------------
393 void wxMultiCellSizer :: GetMinimums()
394 {
395 // We first initial all the arrays EXCEPT for the m_minsizes array.
396
397 memset(m_maxHeight, 0, sizeof(int) * m_cell_count.GetHeight());
398 memset(m_maxWidth, 0, sizeof(int) * m_cell_count.GetWidth());
399 memset(m_rowStretch, 0, sizeof(int) * m_cell_count.GetHeight());
400 memset(m_colStretch, 0, sizeof(int) * m_cell_count.GetWidth());
401 for (int x = 0; x < 1 + wxMax(m_cell_count.GetHeight(), m_cell_count.GetWidth()); x++)
402 {
403 m_weights[x]->SetHeight(0);
404 m_weights[x]->SetWidth(0);
405 }
406
407 wxNode *node = m_children.GetFirst();
408 while (node)
409 {
410 wxSizerItem *item = (wxSizerItem*) node->Data();
411 wxMultiCellItemHandle *rect;
412 if (item != NULL &&
413 (rect = (wxMultiCellItemHandle *)item->GetUserData()) != NULL)
414 {
415 int row = rect->GetRow();
416 int col = rect->GetColumn();
417
418 // First make sure that the control knows about the max rows and columns
419
420 int changed = FALSE;
421 if (row + 1 > m_cell_count.GetHeight())
422 {
423 changed++;
424 m_maxHeight = (int *)realloc(m_maxHeight, (1 + row) * sizeof(int));
425 m_rowStretch = (int *)realloc(m_rowStretch, (1 + row) * sizeof(int));
426 for (int x = m_cell_count.GetHeight(); x < row + 1; x++)
427 {
428 m_maxHeight[x - 1] = 0;
429 m_rowStretch[x - 1] = 0;
430 }
431 m_cell_count.SetHeight(row + 1);
432 }
433 if (col + 1 > m_cell_count.GetWidth())
434 {
435 changed++;
436 m_maxWidth = (int *)realloc(m_maxWidth, (1 + col) * sizeof(int));
437 m_colStretch = (int *)realloc(m_colStretch, ( 1 + col) * sizeof(int));
438 for (int x = m_cell_count.GetWidth(); x < col + 1; x++)
439 {
440 m_maxWidth[x - 1] = 0;
441 m_colStretch[x - 1] = 0;
442 }
443 m_cell_count.SetWidth(col + 1);
444 }
445 if (changed)
446 {
447 m_weights = (wxSize **)realloc(m_weights, (1 + wxMax(m_cell_count.GetHeight(), m_cell_count.GetWidth())) * sizeof(wxSize *));
448 m_minSizes = (wxSize **)realloc(m_minSizes, (1 + wxMax(m_cell_count.GetHeight(), m_cell_count.GetWidth())) * sizeof(wxSize *));
449 for (int x = m_maxWeights; x < 1 + wxMax(m_cell_count.GetHeight(), m_cell_count.GetWidth()); x++)
450 {
451 m_weights[x - 1] = new wxSize(0,0);
452 m_minSizes[x - 1] = new wxSize(0,0);
453 }
454 m_maxWeights = 1 + wxMax(m_cell_count.GetHeight(), m_cell_count.GetWidth());
455 }
456
457 // Sum the m_weights for each row/column, but only if they are resizable
458
459 wxSize minSize( item->CalcMin() );
460 wxSize c_size = rect->GetLocalSize();
461 if (c_size.GetHeight() != wxDefaultSize.GetHeight() ||
462 c_size.GetWidth() != wxDefaultSize.GetWidth())
463 {
464 minSize.SetHeight(wxMax(minSize.GetHeight(), c_size.GetHeight()));
465 minSize.SetWidth(wxMax(minSize.GetWidth(), c_size.GetWidth()));
466 }
467
468 // For each row, calculate the max height for those fields which are not
469 // resizable in the vertical pane
470
471 if (!(rect->GetStyle() & wxVERTICAL_RESIZABLE || m_minSizes[row]->GetHeight() < 0))
472 {
473 m_maxHeight[row] = wxMax(m_maxHeight[row], minSize.GetHeight() / rect->GetHeight());
474 }
475 else
476 {
477 m_rowStretch[row] = 1;
478 if (m_minSizes[row]->GetHeight())
479 {
480 m_maxHeight[row] = abs(m_minSizes[row]->GetHeight());
481 }
482 else
483 {
484 m_maxHeight[row] = wxMax(m_maxHeight[row], m_defaultCellSize.GetHeight());
485 }
486 m_weights[row]->SetHeight(wxMax(m_weights[row]->GetHeight(), rect->GetWeight().GetHeight()));
487 }
488
489 // For each column, calculate the max width for those fields which are not
490 // resizable in the horizontal pane
491
492 if (!(rect->GetStyle() & wxHORIZENTAL_RESIZABLE || m_minSizes[col]->GetWidth() < 0))
493 {
494 if (m_minSizes[col]->GetWidth())
495 {
496 m_maxWidth[col] = abs(m_minSizes[col]->GetWidth());
497 }
498 else
499 {
500 m_maxWidth[col] = wxMax(m_maxWidth[col], minSize.GetWidth() / rect->GetWidth());
501 }
502 }
503 else
504 {
505 m_colStretch[col] = 1;
506 m_maxWidth[col] = wxMax(m_maxWidth[col], m_defaultCellSize.GetWidth());
507 m_weights[col]->SetWidth(wxMax(m_weights[col]->GetWidth(), rect->GetWeight().GetWidth()));
508 }
509 node = node->Next();
510 }
511 }
512 } // wxMultiCellSizer :: GetMinimums
513 //---------------------------------------------------------------------------
514 /*
515 *Function Name: wxMultiCellSizer :: Sum
516 *
517 *Parameters: int * pointer to array of ints
518 * int Number of cells to sum up
519 *
520 *Description: This member function sums up all the elements of the array which
521 * preceed the specified cell.
522 *
523 *Returns: int Sum
524 *
525 */
526
527 int wxMultiCellSizer :: Sum(int *array, int x)
528 {
529 int sum = 0;
530 while (x--)
531 {
532 sum += array[x];
533 }
534 return sum;
535 }
536 //---------------------------------------------------------------------------
537 /*
538 *Function Name: wxMultiCellSizer :: DrawGridLines
539 *
540 *Parameters: wxDC Device context
541 *
542 *Description: This function draws the grid lines in the specified device context.
543 *
544 */
545
546 void wxMultiCellSizer :: DrawGridLines(wxDC& dc)
547 {
548 RecalcSizes();
549 int maxW = Sum(m_maxWidth, m_cell_count.GetWidth());
550 int maxH = Sum(m_maxHeight, m_cell_count.GetHeight());
551 int x;
552
553 // Draw the columns
554 dc.SetPen(* m_pen);
555 for (x = 1; x < m_cell_count.GetWidth(); x++)
556 {
557 int colPos = Sum(m_maxWidth, x) ;
558 dc.DrawLine(colPos, 0, colPos, maxH);
559 }
560
561 // Draw the rows
562 for (x = 1; x < m_cell_count.GetHeight(); x++)
563 {
564 int rowPos = Sum(m_maxHeight, x);
565 dc.DrawLine(0, rowPos, maxW, rowPos);
566 }
567 }
568 //---------------------------------------------------------------------------
569 // Define the repainting behaviour
570 /*
571 *Function Name: wxMultiCellSizer::OnPaint
572 *
573 *Parameters: wxDC Device context
574 *
575 *Description: This function calls the DrawGridLines() member if a window
576 * has been previously specified. This functions MUST be called
577 * from an OnPaint member belonging to the window which the sizer
578 * is attached to.
579 *
580 */
581
582 void wxMultiCellSizer::OnPaint(wxDC& dc )
583 {
584 if (m_win)
585 {
586 DrawGridLines(dc);
587 }
588 }
589
590
591 //---------------------------------------------------------------------------
592 //---------------------------------------------------------------------------
593
594
595
596
597 #define CELL_LOC(row, col) ((row) * m_maxCols + col)
598
599 //---------------------------------------------------------------------------
600 // wxCell
601 //---------------------------------------------------------------------------
602 /*
603 *Function Name: wxCell : wxLayoutConstraints
604 *
605 *Description: This class is used by wxMultiCellCanvas for internal storage
606 *
607 */
608
609 class wxCell : public wxLayoutConstraints
610 {
611 public:
612 wxCell(wxWindow *win)
613 {
614 m_window = win;
615 };
616
617 wxWindow *m_window;
618 };
619
620
621
622 //---------------------------------------------------------------------------
623 // wxMultiCellCanvas
624 //---------------------------------------------------------------------------
625 wxMultiCellCanvas :: wxMultiCellCanvas(wxWindow *par, int numRows, int numCols)
626 : wxFlexGridSizer(numRows, numCols, 0, 0)
627 {
628 m_cells = (wxCell **)calloc(numRows * numCols, sizeof(wxCell *));
629
630 m_parent = par;
631 m_maxRows = numRows;
632 m_maxCols = numCols;
633 m_minCellSize = wxSize(5, 5);
634 }
635 //---------------------------------------------------------------------------
636 wxString itoa(int x)
637 {
638 char bfr[255];
639 sprintf(bfr, "%d", x);
640 return bfr;
641 }
642 //---------------------------------------------------------------------------
643 void wxMultiCellCanvas :: Add(wxWindow *win, unsigned int row, unsigned int col)
644 {
645 wxASSERT_MSG(row >= 0 && row < m_maxRows, wxString("Row ") + itoa(row) + " out of bounds (" + itoa(m_maxRows) + ")");
646 wxASSERT_MSG(col >= 0 && col < m_maxCols, wxString("Column ") + itoa(col) + " out of bounds (" + itoa(m_maxCols) + ")");
647 wxASSERT_MSG(m_cells[CELL_LOC(row, col)] == NULL, "Cell already occupied");
648
649 wxCell *newCell = new wxCell(win);
650 m_cells[CELL_LOC(row,col)] = newCell;
651 }
652 //---------------------------------------------------------------------------
653 void wxMultiCellCanvas :: CalculateConstraints()
654 {
655 unsigned int row, col;
656 for (row = 0; row < m_maxRows; row++)
657 {
658 for (col = 0; col < m_maxCols; col++)
659 {
660 if (!m_cells[CELL_LOC(row, col)])
661 {
662 // Create an empty static text field as a placeholder
663 m_cells[CELL_LOC(row, col)] = new wxCell(new wxStaticText(m_parent, -1, ""));
664 }
665 wxFlexGridSizer::Add(m_cells[CELL_LOC(row, col)]->m_window);
666 }
667 }
668 }
669
670 /*** End of File ***/