In the generic version of wxDataViewCtrl, all
[wxWidgets.git] / src / generic / datavgen.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: datavgen.cpp
3 // Purpose: wxDataViewCtrl generic implementation
4 // Author: Robert Roebling
5 // Id: $Id$
6 // Copyright: (c) 1998 Robert Roebling
7 // Licence: wxWindows licence
8 /////////////////////////////////////////////////////////////////////////////
9
10 // For compilers that support precompilation, includes "wx.h".
11 #include "wx/wxprec.h"
12
13 #include "wx/defs.h"
14
15 #if wxUSE_DATAVIEWCTRL
16
17 #include "wx/dataview.h"
18
19 #ifdef wxUSE_GENERICDATAVIEWCTRL
20
21 #include "wx/stockitem.h"
22 #include "wx/dcclient.h"
23 #include "wx/calctrl.h"
24 #include "wx/popupwin.h"
25 #include "wx/sizer.h"
26 #include "wx/log.h"
27 #include "wx/renderer.h"
28
29 #ifdef __WXMSW__
30 #include <windows.h> // for DLGC_WANTARROWS
31 #include "wx/msw/winundef.h"
32 #endif
33
34 //-----------------------------------------------------------------------------
35 // classes
36 //-----------------------------------------------------------------------------
37
38 class wxDataViewCtrl;
39
40 // ---------------------------------------------------------
41 // wxGenericDataViewListModelNotifier
42 // ---------------------------------------------------------
43
44 class wxGenericDataViewListModelNotifier: public wxDataViewListModelNotifier
45 {
46 public:
47 wxGenericDataViewListModelNotifier( wxDataViewListModel *wx_model );
48
49 virtual bool RowAppended();
50 virtual bool RowPrepended();
51 virtual bool RowInserted( size_t before );
52 virtual bool RowDeleted( size_t row );
53 virtual bool RowChanged( size_t row );
54 virtual bool ValueChanged( size_t col, size_t row );
55 virtual bool RowsReordered( size_t *new_order );
56 virtual bool Cleared();
57
58 wxDataViewListModel *m_wx_model;
59 };
60
61 // ---------------------------------------------------------
62 // wxGenericDataViewListModelNotifier
63 // ---------------------------------------------------------
64
65 wxGenericDataViewListModelNotifier::wxGenericDataViewListModelNotifier(
66 wxDataViewListModel *wx_model )
67 {
68 m_wx_model = wx_model;
69 }
70
71 bool wxGenericDataViewListModelNotifier::RowAppended()
72 {
73 size_t pos = m_wx_model->GetNumberOfRows()-1;
74
75 return false;
76 }
77
78 bool wxGenericDataViewListModelNotifier::RowPrepended()
79 {
80 return false;
81 }
82
83 bool wxGenericDataViewListModelNotifier::RowInserted( size_t before )
84 {
85 return false;
86 }
87
88 bool wxGenericDataViewListModelNotifier::RowDeleted( size_t row )
89 {
90 return false;
91 }
92
93 bool wxGenericDataViewListModelNotifier::RowChanged( size_t row )
94 {
95 return true;
96 }
97
98 bool wxGenericDataViewListModelNotifier::ValueChanged( size_t model_col, size_t model_row )
99 {
100 wxNode *node = GetOwner()->m_viewingColumns.GetFirst();
101 while (node)
102 {
103 wxDataViewViewingColumn* viewing_column = (wxDataViewViewingColumn*) node->GetData();
104 if (viewing_column->m_modelColumn == model_col)
105 {
106
107 }
108
109 node = node->GetNext();
110 }
111
112 return false;
113 }
114
115 bool wxGenericDataViewListModelNotifier::RowsReordered( size_t *new_order )
116 {
117 wxNode *node = GetOwner()->m_viewingColumns.GetFirst();
118 while (node)
119 {
120 wxDataViewViewingColumn* viewing_column = (wxDataViewViewingColumn*) node->GetData();
121
122 node = node->GetNext();
123 }
124
125 return false;
126 }
127
128 bool wxGenericDataViewListModelNotifier::Cleared()
129 {
130 return false;
131 }
132
133 // ---------------------------------------------------------
134 // wxDataViewCell
135 // ---------------------------------------------------------
136
137 IMPLEMENT_ABSTRACT_CLASS(wxDataViewCell, wxDataViewCellBase)
138
139 wxDataViewCell::wxDataViewCell( const wxString &varianttype, wxDataViewCellMode mode ) :
140 wxDataViewCellBase( varianttype, mode )
141 {
142 m_dc = NULL;
143 }
144
145 wxDataViewCell::~wxDataViewCell()
146 {
147 if (m_dc)
148 delete m_dc;
149 }
150
151 wxDC *wxDataViewCell::GetDC()
152 {
153 if (m_dc == NULL)
154 {
155 if (GetOwner() == NULL)
156 return NULL;
157 if (GetOwner()->GetOwner() == NULL)
158 return NULL;
159 m_dc = new wxClientDC( GetOwner()->GetOwner() );
160 }
161
162 return m_dc;
163 }
164
165 // ---------------------------------------------------------
166 // wxDataViewCustomCell
167 // ---------------------------------------------------------
168
169 IMPLEMENT_ABSTRACT_CLASS(wxDataViewCustomCell, wxDataViewCell)
170
171 wxDataViewCustomCell::wxDataViewCustomCell( const wxString &varianttype,
172 wxDataViewCellMode mode ) :
173 wxDataViewCell( varianttype, mode )
174 {
175 }
176
177
178 // ---------------------------------------------------------
179 // wxDataViewTextCell
180 // ---------------------------------------------------------
181
182 IMPLEMENT_ABSTRACT_CLASS(wxDataViewTextCell, wxDataViewCustomCell)
183
184 wxDataViewTextCell::wxDataViewTextCell( const wxString &varianttype, wxDataViewCellMode mode ) :
185 wxDataViewCustomCell( varianttype, mode )
186 {
187 }
188
189 bool wxDataViewTextCell::SetValue( const wxVariant &value )
190 {
191 return false;
192 }
193
194 bool wxDataViewTextCell::GetValue( wxVariant &value )
195 {
196 return false;
197 }
198
199 bool wxDataViewTextCell::Render( wxRect cell, wxDC *dc, int state )
200 {
201 return false;
202 }
203
204 wxSize wxDataViewTextCell::GetSize()
205 {
206 return wxSize(80,20);
207 }
208
209 // ---------------------------------------------------------
210 // wxDataViewToggleCell
211 // ---------------------------------------------------------
212
213 IMPLEMENT_ABSTRACT_CLASS(wxDataViewToggleCell, wxDataViewCustomCell)
214
215 wxDataViewToggleCell::wxDataViewToggleCell( const wxString &varianttype,
216 wxDataViewCellMode mode ) :
217 wxDataViewCustomCell( varianttype, mode )
218 {
219 }
220
221 bool wxDataViewToggleCell::SetValue( const wxVariant &value )
222 {
223 return false;
224 }
225
226 bool wxDataViewToggleCell::GetValue( wxVariant &value )
227 {
228 return false;
229 }
230
231 bool wxDataViewToggleCell::Render( wxRect cell, wxDC *dc, int state )
232 {
233 return false;
234 }
235
236 wxSize wxDataViewToggleCell::GetSize()
237 {
238 return wxSize(20,20);
239 }
240
241 // ---------------------------------------------------------
242 // wxDataViewProgressCell
243 // ---------------------------------------------------------
244
245 IMPLEMENT_ABSTRACT_CLASS(wxDataViewProgressCell, wxDataViewCustomCell)
246
247 wxDataViewProgressCell::wxDataViewProgressCell( const wxString &label,
248 const wxString &varianttype, wxDataViewCellMode mode ) :
249 wxDataViewCustomCell( varianttype, mode )
250 {
251 m_label = label;
252 m_value = 0;
253 }
254
255 wxDataViewProgressCell::~wxDataViewProgressCell()
256 {
257 }
258
259 bool wxDataViewProgressCell::SetValue( const wxVariant &value )
260 {
261 m_value = (long) value;
262
263 if (m_value < 0) m_value = 0;
264 if (m_value > 100) m_value = 100;
265
266 return true;
267 }
268
269 bool wxDataViewProgressCell::Render( wxRect cell, wxDC *dc, int state )
270 {
271 double pct = (double)m_value / 100.0;
272 wxRect bar = cell;
273 bar.width = (int)(cell.width * pct);
274 dc->SetPen( *wxTRANSPARENT_PEN );
275 dc->SetBrush( *wxBLUE_BRUSH );
276 dc->DrawRectangle( bar );
277
278 dc->SetBrush( *wxTRANSPARENT_BRUSH );
279 dc->SetPen( *wxBLACK_PEN );
280 dc->DrawRectangle( cell );
281
282 return true;
283 }
284
285 wxSize wxDataViewProgressCell::GetSize()
286 {
287 return wxSize(40,12);
288 }
289
290 // ---------------------------------------------------------
291 // wxDataViewDateCell
292 // ---------------------------------------------------------
293
294 class wxDataViewDateCellPopupTransient: public wxPopupTransientWindow
295 {
296 public:
297 wxDataViewDateCellPopupTransient( wxWindow* parent, wxDateTime *value,
298 wxDataViewListModel *model, size_t col, size_t row ) :
299 wxPopupTransientWindow( parent, wxBORDER_SIMPLE )
300 {
301 m_model = model;
302 m_col = col;
303 m_row = row;
304 m_cal = new wxCalendarCtrl( this, -1, *value );
305 wxBoxSizer *sizer = new wxBoxSizer( wxHORIZONTAL );
306 sizer->Add( m_cal, 1, wxGROW );
307 SetSizer( sizer );
308 sizer->Fit( this );
309 }
310
311 virtual void OnDismiss()
312 {
313 }
314
315 void OnCalendar( wxCalendarEvent &event );
316
317 wxCalendarCtrl *m_cal;
318 wxDataViewListModel *m_model;
319 size_t m_col;
320 size_t m_row;
321
322 private:
323 DECLARE_EVENT_TABLE()
324 };
325
326 BEGIN_EVENT_TABLE(wxDataViewDateCellPopupTransient,wxPopupTransientWindow)
327 EVT_CALENDAR( -1, wxDataViewDateCellPopupTransient::OnCalendar )
328 END_EVENT_TABLE()
329
330 void wxDataViewDateCellPopupTransient::OnCalendar( wxCalendarEvent &event )
331 {
332 wxDateTime date = event.GetDate();
333 wxVariant value = date;
334 m_model->SetValue( value, m_col, m_row );
335 m_model->ValueChanged( m_col, m_row );
336 DismissAndNotify();
337 }
338
339 IMPLEMENT_ABSTRACT_CLASS(wxDataViewDateCell, wxDataViewCustomCell)
340
341 wxDataViewDateCell::wxDataViewDateCell( const wxString &varianttype,
342 wxDataViewCellMode mode ) :
343 wxDataViewCustomCell( varianttype, mode )
344 {
345 }
346
347 bool wxDataViewDateCell::SetValue( const wxVariant &value )
348 {
349 m_date = value.GetDateTime();
350
351 return true;
352 }
353
354 bool wxDataViewDateCell::Render( wxRect cell, wxDC *dc, int state )
355 {
356 dc->SetFont( GetOwner()->GetOwner()->GetFont() );
357 wxString tmp = m_date.FormatDate();
358 dc->DrawText( tmp, cell.x, cell.y );
359
360 return true;
361 }
362
363 wxSize wxDataViewDateCell::GetSize()
364 {
365 wxDataViewCtrl* view = GetOwner()->GetOwner();
366 wxString tmp = m_date.FormatDate();
367 wxCoord x,y,d;
368 view->GetTextExtent( tmp, &x, &y, &d );
369 return wxSize(x,y+d);
370 }
371
372 bool wxDataViewDateCell::Activate( wxRect cell, wxDataViewListModel *model, size_t col, size_t row )
373 {
374 wxVariant variant;
375 model->GetValue( variant, col, row );
376 wxDateTime value = variant.GetDateTime();
377
378 wxDataViewDateCellPopupTransient *popup = new wxDataViewDateCellPopupTransient(
379 GetOwner()->GetOwner()->GetParent(), &value, model, col, row );
380 wxPoint pos = wxGetMousePosition();
381 popup->Move( pos );
382 popup->Layout();
383 popup->Popup( popup->m_cal );
384
385 return true;
386 }
387
388 // ---------------------------------------------------------
389 // wxDataViewColumn
390 // ---------------------------------------------------------
391
392 IMPLEMENT_ABSTRACT_CLASS(wxDataViewColumn, wxDataViewColumnBase)
393
394 wxDataViewColumn::wxDataViewColumn( const wxString &title, wxDataViewCell *cell,
395 size_t model_column, int flags ) :
396 wxDataViewColumnBase( title, cell, model_column, flags )
397 {
398 m_width = 80;
399 }
400
401 wxDataViewColumn::~wxDataViewColumn()
402 {
403 }
404
405 void wxDataViewColumn::SetTitle( const wxString &title )
406 {
407 wxDataViewColumnBase::SetTitle( title );
408
409 }
410
411 //-----------------------------------------------------------------------------
412 // wxDataViewHeaderWindow
413 //-----------------------------------------------------------------------------
414
415 class wxDataViewHeaderWindow: public wxWindow
416 {
417 public:
418 wxDataViewHeaderWindow( wxDataViewCtrl *parent,
419 wxWindowID id,
420 const wxPoint &pos = wxDefaultPosition,
421 const wxSize &size = wxDefaultSize,
422 const wxString &name = wxT("wxdataviewctrlheaderwindow") );
423 ~wxDataViewHeaderWindow();
424
425 void SetOwner( wxDataViewCtrl* owner ) { m_owner = owner; }
426 wxDataViewCtrl *GetOwner() { return m_owner; }
427
428 void OnPaint( wxPaintEvent &event );
429 void OnMouse( wxMouseEvent &event );
430 void OnSetFocus( wxFocusEvent &event );
431
432 private:
433 wxDataViewCtrl *m_owner;
434 wxCursor *m_resizeCursor;
435
436 private:
437 DECLARE_DYNAMIC_CLASS(wxDataViewHeaderWindow)
438 DECLARE_EVENT_TABLE()
439 };
440
441 IMPLEMENT_ABSTRACT_CLASS(wxDataViewHeaderWindow, wxWindow)
442
443 BEGIN_EVENT_TABLE(wxDataViewHeaderWindow,wxWindow)
444 EVT_PAINT (wxDataViewHeaderWindow::OnPaint)
445 EVT_MOUSE_EVENTS (wxDataViewHeaderWindow::OnMouse)
446 EVT_SET_FOCUS (wxDataViewHeaderWindow::OnSetFocus)
447 END_EVENT_TABLE()
448
449 wxDataViewHeaderWindow::wxDataViewHeaderWindow( wxDataViewCtrl *parent, wxWindowID id,
450 const wxPoint &pos, const wxSize &size, const wxString &name ) :
451 wxWindow( parent, id, pos, size, 0, name )
452 {
453 SetOwner( parent );
454
455 m_resizeCursor = new wxCursor( wxCURSOR_SIZEWE );
456
457 wxVisualAttributes attr = wxPanel::GetClassDefaultAttributes();
458 SetOwnForegroundColour( attr.colFg );
459 SetOwnBackgroundColour( attr.colBg );
460 if (!m_hasFont)
461 SetOwnFont( attr.font );
462 }
463
464 wxDataViewHeaderWindow::~wxDataViewHeaderWindow()
465 {
466 delete m_resizeCursor;
467 }
468
469 void wxDataViewHeaderWindow::OnPaint( wxPaintEvent &event )
470 {
471 int w, h;
472 GetClientSize( &w, &h );
473
474 wxPaintDC dc( this );
475
476 int xpix;
477 m_owner->GetScrollPixelsPerUnit( &xpix, NULL );
478
479 int x;
480 m_owner->GetViewStart( &x, NULL );
481
482 // account for the horz scrollbar offset
483 dc.SetDeviceOrigin( -x * xpix, 0 );
484
485 dc.SetFont( GetFont() );
486
487 size_t cols = GetOwner()->GetNumberOfColumns();
488 size_t i;
489 int xpos = 0;
490 for (i = 0; i < cols; i++)
491 {
492 wxDataViewColumn *col = GetOwner()->GetColumn( i );
493 int width = col->GetWidth();
494
495 // the width of the rect to draw: make it smaller to fit entirely
496 // inside the column rect
497 #ifdef __WXMAC__
498 int cw = width;
499 int ch = h;
500 #else
501 int cw = width - 2;
502 int ch = h - 2;
503 #endif
504
505 wxRendererNative::Get().DrawHeaderButton
506 (
507 this,
508 dc,
509 wxRect(xpos, 0, cw, ch),
510 m_parent->IsEnabled() ? 0
511 : (int)wxCONTROL_DISABLED
512 );
513
514 dc.DrawText( col->GetTitle(), xpos+3, 3 );
515
516 xpos += width;
517 }
518 }
519
520 void wxDataViewHeaderWindow::OnMouse( wxMouseEvent &event )
521 {
522 }
523
524 void wxDataViewHeaderWindow::OnSetFocus( wxFocusEvent &event )
525 {
526 event.Skip();
527 }
528
529 //-----------------------------------------------------------------------------
530 // wxDataViewMainWindow
531 //-----------------------------------------------------------------------------
532
533 class wxDataViewMainWindow: public wxWindow
534 {
535 public:
536 wxDataViewMainWindow( wxDataViewCtrl *parent,
537 wxWindowID id,
538 const wxPoint &pos = wxDefaultPosition,
539 const wxSize &size = wxDefaultSize,
540 const wxString &name = wxT("wxdataviewctrlmainwindow") );
541 ~wxDataViewMainWindow();
542
543 void SetOwner( wxDataViewCtrl* owner ) { m_owner = owner; }
544 wxDataViewCtrl *GetOwner() { return m_owner; }
545
546 void OnPaint( wxPaintEvent &event );
547 void OnMouse( wxMouseEvent &event );
548 void OnSetFocus( wxFocusEvent &event );
549
550 void UpdateDisplay();
551 void RecalculateDisplay();
552 void OnInternalIdle();
553
554 void ScrollWindow( int dx, int dy, const wxRect *rect );
555 private:
556 wxDataViewCtrl *m_owner;
557 int m_lineHeight;
558 bool m_dirty;
559
560 private:
561 DECLARE_DYNAMIC_CLASS(wxDataViewMainWindow)
562 DECLARE_EVENT_TABLE()
563 };
564
565 IMPLEMENT_ABSTRACT_CLASS(wxDataViewMainWindow, wxWindow)
566
567 BEGIN_EVENT_TABLE(wxDataViewMainWindow,wxWindow)
568 EVT_PAINT (wxDataViewMainWindow::OnPaint)
569 EVT_MOUSE_EVENTS (wxDataViewMainWindow::OnMouse)
570 EVT_SET_FOCUS (wxDataViewMainWindow::OnSetFocus)
571 END_EVENT_TABLE()
572
573 wxDataViewMainWindow::wxDataViewMainWindow( wxDataViewCtrl *parent, wxWindowID id,
574 const wxPoint &pos, const wxSize &size, const wxString &name ) :
575 wxWindow( parent, id, pos, size, 0, name )
576 {
577 SetOwner( parent );
578
579 // We need to calculate this smartly..
580 m_lineHeight = 20;
581
582 UpdateDisplay();
583 }
584
585 wxDataViewMainWindow::~wxDataViewMainWindow()
586 {
587 }
588
589 void wxDataViewMainWindow::UpdateDisplay()
590 {
591 m_dirty = true;
592 }
593
594 void wxDataViewMainWindow::OnInternalIdle()
595 {
596 wxWindow::OnInternalIdle();
597
598 if (m_dirty)
599 {
600 RecalculateDisplay();
601 m_dirty = false;
602 }
603 }
604
605 void wxDataViewMainWindow::RecalculateDisplay()
606 {
607 wxDataViewListModel *model = GetOwner()->GetModel();
608 if (!model)
609 {
610 Refresh();
611 return;
612 }
613
614 int width = 0;
615 size_t cols = GetOwner()->GetNumberOfColumns();
616 size_t i;
617 for (i = 0; i < cols; i++)
618 {
619 wxDataViewColumn *col = GetOwner()->GetColumn( i );
620 width += col->GetWidth();
621 }
622
623 int height = model->GetNumberOfRows() * m_lineHeight;
624
625 SetVirtualSize( width, height );
626 GetOwner()->SetScrollRate( 10, m_lineHeight );
627
628 Refresh();
629 }
630
631 void wxDataViewMainWindow::ScrollWindow( int dx, int dy, const wxRect *rect )
632 {
633 wxWindow::ScrollWindow( dx, dy, rect );
634 GetOwner()->m_headerArea->ScrollWindow( dx, 0 );
635 }
636
637 void wxDataViewMainWindow::OnPaint( wxPaintEvent &event )
638 {
639 wxPaintDC dc( this );
640
641 GetOwner()->PrepareDC( dc );
642
643 dc.SetFont( GetFont() );
644
645 dc.DrawText( wxT("main window"), 5, 5 );
646 }
647
648 void wxDataViewMainWindow::OnMouse( wxMouseEvent &event )
649 {
650 event.Skip();
651 }
652
653 void wxDataViewMainWindow::OnSetFocus( wxFocusEvent &event )
654 {
655 event.Skip();
656 }
657
658 //-----------------------------------------------------------------------------
659 // wxDataViewCtrl
660 //-----------------------------------------------------------------------------
661
662 IMPLEMENT_DYNAMIC_CLASS(wxDataViewCtrl, wxDataViewCtrlBase)
663
664 BEGIN_EVENT_TABLE(wxDataViewCtrl, wxDataViewCtrlBase)
665 EVT_SIZE(wxDataViewCtrl::OnSize)
666 END_EVENT_TABLE()
667
668 wxDataViewCtrl::~wxDataViewCtrl()
669 {
670 if (m_notifier)
671 GetModel()->RemoveNotifier( m_notifier );
672 }
673
674 void wxDataViewCtrl::Init()
675 {
676 m_notifier = NULL;
677 }
678
679 bool wxDataViewCtrl::Create(wxWindow *parent, wxWindowID id,
680 const wxPoint& pos, const wxSize& size,
681 long style, const wxValidator& validator )
682 {
683 if (!wxControl::Create( parent, id, pos, size, style | wxScrolledWindowStyle|wxSUNKEN_BORDER, validator))
684 return false;
685
686 Init();
687
688 #ifdef __WXMAC__
689 MacSetClipChildren( true ) ;
690 #endif
691
692 m_clientArea = new wxDataViewMainWindow( this, -1 );
693 m_headerArea = new wxDataViewHeaderWindow( this, -1, wxDefaultPosition, wxSize(-1,25) );
694
695 SetTargetWindow( m_clientArea );
696
697 wxBoxSizer *sizer = new wxBoxSizer( wxVERTICAL );
698 sizer->Add( m_headerArea, 0, wxGROW );
699 sizer->Add( m_clientArea, 1, wxGROW );
700 SetSizer( sizer );
701
702 return true;
703 }
704
705 #ifdef __WXMSW__
706 WXLRESULT wxDataViewCtrl::MSWWindowProc(WXUINT nMsg,
707 WXWPARAM wParam,
708 WXLPARAM lParam)
709 {
710 WXLRESULT rc = wxPanel::MSWWindowProc(nMsg, wParam, lParam);
711
712 #ifndef __WXWINCE__
713 // we need to process arrows ourselves for scrolling
714 if ( nMsg == WM_GETDLGCODE )
715 {
716 rc |= DLGC_WANTARROWS;
717 }
718 #endif
719
720 return rc;
721 }
722 #endif
723
724 void wxDataViewCtrl::OnSize( wxSizeEvent &event )
725 {
726 // We need to override OnSize so that our scrolled
727 // window a) does call Layout() to use sizers for
728 // positioning the controls but b) does not query
729 // the sizer for their size and use that for setting
730 // the scrollable area as set that ourselves by
731 // calling SetScrollbar() further down.
732
733 Layout();
734
735 AdjustScrollbars();
736 }
737
738 bool wxDataViewCtrl::AssociateModel( wxDataViewListModel *model )
739 {
740 if (!wxDataViewCtrlBase::AssociateModel( model ))
741 return false;
742
743 m_notifier = new wxGenericDataViewListModelNotifier( model );
744
745 model->AddNotifier( m_notifier );
746
747 m_clientArea->UpdateDisplay();
748
749 return true;
750 }
751
752 bool wxDataViewCtrl::AppendColumn( wxDataViewColumn *col )
753 {
754 if (!wxDataViewCtrlBase::AppendColumn(col))
755 return false;
756
757 m_clientArea->UpdateDisplay();
758
759 return true;
760 }
761
762 #endif
763 // !wxUSE_GENERICDATAVIEWCTRL
764
765 #endif
766 // wxUSE_DATAVIEWCTRL
767