1 /**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 Grid.cpp
6
7 Leland Lucius
8
9 *******************************************************************//**
10
11 \class Grid
12 \brief Supplies an accessible grid based on wxGrid.
13
14 *//*******************************************************************/
15
16
17 #include "Grid.h"
18
19 #include <wx/setup.h> // for wxUSE_* macros
20
21 #include <wx/defs.h>
22 #include <wx/choice.h>
23 #include <wx/clipbrd.h>
24 #include <wx/dc.h>
25 #include <wx/grid.h>
26 #include <wx/intl.h>
27 #include <wx/settings.h>
28 #include <wx/toplevel.h>
29
30 #include "SelectedRegion.h"
31
32 #if wxUSE_ACCESSIBILITY
33 #include "WindowAccessible.h"
34
35 /**********************************************************************//**
36 \class GridAx
37 \brief wxAccessible object providing grid information for Grid.
38 **************************************************************************/
39 class GridAx final : public WindowAccessible
40 {
41
42 public:
43
44 GridAx(Grid *grid);
45
46 void SetCurrentCell(int row, int col);
47 void TableUpdated();
48 bool GetRowCol(int childId, int & row, int & col);
49
50 // Retrieves the address of an IDispatch interface for the specified child.
51 // All objects must support this property.
52 wxAccStatus GetChild(int childId, wxAccessible **child) override;
53
54 // Gets the number of children.
55 wxAccStatus GetChildCount(int *childCount) override;
56
57 // Gets the default action for this object (0) or > 0 (the action for a child).
58 // Return wxACC_OK even if there is no action. actionName is the action, or the empty
59 // string if there is no action.
60 // The retrieved string describes the action that is performed on an object,
61 // not what the object does as a result. For example, a toolbar button that prints
62 // a document has a default action of "Press" rather than "Prints the current document."
63 wxAccStatus GetDefaultAction(int childId, wxString *actionName) override;
64
65 // Returns the description for this object or a child.
66 wxAccStatus GetDescription(int childId, wxString *description) override;
67
68 // Gets the window with the keyboard focus.
69 // If childId is 0 and child is NULL, no object in
70 // this subhierarchy has the focus.
71 // If this object has the focus, child should be 'this'.
72 wxAccStatus GetFocus(int *childId, wxAccessible **child) override;
73
74 // Returns help text for this object or a child, similar to tooltip text.
75 wxAccStatus GetHelpText(int childId, wxString *helpText) override;
76
77 // Returns the keyboard shortcut for this object or child.
78 // Return e.g. ALT+K
79 wxAccStatus GetKeyboardShortcut(int childId, wxString *shortcut) override;
80
81 // Returns the rectangle for this object (id = 0) or a child element (id > 0).
82 // rect is in screen coordinates.
83 wxAccStatus GetLocation(wxRect & rect, int elementId) override;
84
85 // Gets the name of the specified object.
86 wxAccStatus GetName(int childId, wxString *name) override;
87
88 // Gets the parent, or NULL.
89 wxAccStatus GetParent(wxAccessible **parent) override;
90
91 // Returns a role constant.
92 wxAccStatus GetRole(int childId, wxAccRole *role) override;
93
94 // Gets a variant representing the selected children
95 // of this object.
96 // Acceptable values:
97 // - a null variant (IsNull() returns TRUE)
98 // - a list variant (GetType() == wxT("list"))
99 // - an integer representing the selected child element,
100 // or 0 if this object is selected (GetType() == wxT("long"))
101 // - a "void*" pointer to a wxAccessible child object
102 wxAccStatus GetSelections(wxVariant *selections) override;
103
104 // Returns a state constant.
105 wxAccStatus GetState(int childId, long* state) override;
106
107 // Returns a localized string representing the value for the object
108 // or child.
109 wxAccStatus GetValue(int childId, wxString* strValue) override;
110
111 #if defined(__WXMAC__)
112 // Selects the object or child.
113 wxAccStatus Select(int childId, wxAccSelectionFlags selectFlags) override;
114 #endif
115
116 Grid *mGrid;
117 int mLastId;
118
119 };
120 #endif
121
NumericEditor(NumericConverter::Type type,const NumericFormatSymbol & format,double rate)122 NumericEditor::NumericEditor
123 (NumericConverter::Type type, const NumericFormatSymbol &format, double rate)
124 {
125 mType = type;
126 mFormat = format;
127 mRate = rate;
128 mOld = 0.0;
129 }
130
~NumericEditor()131 NumericEditor::~NumericEditor()
132 {
133 }
134
Create(wxWindow * parent,wxWindowID id,wxEvtHandler * handler)135 void NumericEditor::Create(wxWindow *parent, wxWindowID id, wxEvtHandler *handler)
136 {
137 wxASSERT(parent); // to justify safenew
138 auto control = safenew NumericTextCtrl(
139 parent, wxID_ANY,
140 mType,
141 mFormat,
142 mOld,
143 mRate,
144 NumericTextCtrl::Options{}
145 .AutoPos(true)
146 .InvalidValue(mType == NumericTextCtrl::FREQUENCY,
147 SelectedRegion::UndefinedFrequency)
148 );
149 m_control = control;
150
151 wxGridCellEditor::Create(parent, id, handler);
152 }
153
SetSize(const wxRect & rect)154 void NumericEditor::SetSize(const wxRect &rect)
155 {
156 wxSize size = m_control->GetSize();
157
158 // Always center...looks bad otherwise
159 int x = rect.x + ((rect.width / 2) - (size.x / 2)) + 1;
160 int y = rect.y + ((rect.height / 2) - (size.y / 2)) + 1;
161
162 m_control->Move(x, y);
163 }
164
BeginEdit(int row,int col,wxGrid * grid)165 void NumericEditor::BeginEdit(int row, int col, wxGrid *grid)
166 {
167 wxGridTableBase *table = grid->GetTable();
168
169 mOldString = table->GetValue(row, col);
170 mOldString.ToDouble(&mOld);
171
172 auto control = GetNumericTextControl();
173 control->SetValue(mOld);
174 control->EnableMenu();
175
176 control->SetFocus();
177 }
178
179
EndEdit(int WXUNUSED (row),int WXUNUSED (col),const wxGrid * WXUNUSED (grid),const wxString & WXUNUSED (oldval),wxString * newval)180 bool NumericEditor::EndEdit(int WXUNUSED(row), int WXUNUSED(col), const wxGrid *WXUNUSED(grid), const wxString &WXUNUSED(oldval), wxString *newval)
181 {
182 double newtime = GetNumericTextControl()->GetValue();
183 bool changed = newtime != mOld;
184
185 if (changed) {
186 mValueAsString = wxString::Format(wxT("%g"), newtime);
187 *newval = mValueAsString;
188 }
189
190 return changed;
191 }
192
ApplyEdit(int row,int col,wxGrid * grid)193 void NumericEditor::ApplyEdit(int row, int col, wxGrid *grid)
194 {
195 grid->GetTable()->SetValue(row, col, mValueAsString);
196 }
197
Reset()198 void NumericEditor::Reset()
199 {
200 GetNumericTextControl()->SetValue(mOld);
201 }
202
IsAcceptedKey(wxKeyEvent & event)203 bool NumericEditor::IsAcceptedKey(wxKeyEvent &event)
204 {
205 if (wxGridCellEditor::IsAcceptedKey(event)) {
206 if (event.GetKeyCode() == WXK_RETURN) {
207 return true;
208 }
209 }
210
211 return false;
212 }
213
214 // Clone is required by wxwidgets; implemented via copy constructor
Clone() const215 wxGridCellEditor *NumericEditor::Clone() const
216 {
217 return safenew NumericEditor{ mType, mFormat, mRate };
218 }
219
GetValue() const220 wxString NumericEditor::GetValue() const
221 {
222 return wxString::Format(wxT("%g"), GetNumericTextControl()->GetValue());
223 }
224
GetFormat() const225 NumericFormatSymbol NumericEditor::GetFormat() const
226 {
227 return mFormat;
228 }
229
GetRate() const230 double NumericEditor::GetRate() const
231 {
232 return mRate;
233 }
234
SetFormat(const NumericFormatSymbol & format)235 void NumericEditor::SetFormat(const NumericFormatSymbol &format)
236 {
237 mFormat = format;
238 }
239
SetRate(double rate)240 void NumericEditor::SetRate(double rate)
241 {
242 mRate = rate;
243 }
244
~NumericRenderer()245 NumericRenderer::~NumericRenderer()
246 {
247 }
248
Draw(wxGrid & grid,wxGridCellAttr & attr,wxDC & dc,const wxRect & rect,int row,int col,bool isSelected)249 void NumericRenderer::Draw(wxGrid &grid,
250 wxGridCellAttr &attr,
251 wxDC &dc,
252 const wxRect &rect,
253 int row,
254 int col,
255 bool isSelected)
256 {
257 wxGridCellRenderer::Draw(grid, attr, dc, rect, row, col, isSelected);
258
259 wxGridTableBase *table = grid.GetTable();
260 NumericEditor *ne =
261 static_cast<NumericEditor *>(grid.GetCellEditor(row, col));
262 wxString tstr;
263
264 if (ne) {
265 double value;
266
267 table->GetValue(row, col).ToDouble(&value);
268
269 NumericTextCtrl tt(&grid, wxID_ANY,
270 mType,
271 ne->GetFormat(),
272 value,
273 ne->GetRate(),
274 NumericTextCtrl::Options{}.AutoPos(true),
275 wxPoint(10000, 10000)); // create offscreen
276 tstr = tt.GetString();
277
278 ne->DecRef();
279 }
280
281 dc.SetBackgroundMode(wxTRANSPARENT);
282
283 if (grid.IsEnabled())
284 {
285 if (isSelected)
286 {
287 dc.SetTextBackground(grid.GetSelectionBackground());
288 dc.SetTextForeground(grid.GetSelectionForeground());
289 }
290 else
291 {
292 dc.SetTextBackground(attr.GetBackgroundColour());
293 dc.SetTextForeground(attr.GetTextColour());
294 }
295 }
296 else
297 {
298 dc.SetTextBackground(wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE));
299 dc.SetTextForeground(wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT));
300 }
301
302 dc.SetFont(attr.GetFont());
303
304 int hAlign, vAlign;
305
306 attr.GetAlignment(&hAlign, &vAlign);
307
308 grid.DrawTextRectangle(dc, tstr, rect, hAlign, vAlign);
309 }
310
GetBestSize(wxGrid & grid,wxGridCellAttr & WXUNUSED (attr),wxDC & WXUNUSED (dc),int row,int col)311 wxSize NumericRenderer::GetBestSize(wxGrid &grid,
312 wxGridCellAttr & WXUNUSED(attr),
313 wxDC & WXUNUSED(dc),
314 int row,
315 int col)
316 {
317 wxGridTableBase *table = grid.GetTable();
318 NumericEditor *ne =
319 static_cast<NumericEditor *>(grid.GetCellEditor(row, col));
320 wxSize sz;
321
322 if (ne) {
323 double value;
324 table->GetValue(row, col).ToDouble(&value);
325 NumericTextCtrl tt(&grid, wxID_ANY,
326 mType,
327 ne->GetFormat(),
328 value,
329 ne->GetRate(),
330 NumericTextCtrl::Options{}.AutoPos(true),
331 wxPoint(10000, 10000)); // create offscreen
332 sz = tt.GetSize();
333
334 ne->DecRef();
335 }
336
337 return sz;
338 }
339
340 // Clone is required by wxwidgets; implemented via copy constructor
Clone() const341 wxGridCellRenderer *NumericRenderer::Clone() const
342 {
343 return safenew NumericRenderer{ mType };
344 }
345
ChoiceEditor(size_t count,const wxString choices[])346 ChoiceEditor::ChoiceEditor(size_t count, const wxString choices[])
347 {
348 if (count) {
349 mChoices.reserve(count);
350 for (size_t n = 0; n < count; n++) {
351 mChoices.push_back(choices[n]);
352 }
353 }
354 }
355
ChoiceEditor(const wxArrayString & choices)356 ChoiceEditor::ChoiceEditor(const wxArrayString &choices)
357 {
358 mChoices = choices;
359 }
360
~ChoiceEditor()361 ChoiceEditor::~ChoiceEditor()
362 {
363 if (m_control)
364 mHandler.DisconnectEvent(m_control);
365 }
366
367 // Clone is required by wxwidgets; implemented via copy constructor
Clone() const368 wxGridCellEditor *ChoiceEditor::Clone() const
369 {
370 return safenew ChoiceEditor(mChoices);
371 }
372
Create(wxWindow * parent,wxWindowID id,wxEvtHandler * evtHandler)373 void ChoiceEditor::Create(wxWindow* parent, wxWindowID id, wxEvtHandler* evtHandler)
374 {
375 m_control = safenew wxChoice(parent,
376 id,
377 wxDefaultPosition,
378 wxDefaultSize,
379 mChoices);
380
381 wxGridCellEditor::Create(parent, id, evtHandler);
382 mHandler.ConnectEvent(m_control);
383 }
384
SetSize(const wxRect & rect)385 void ChoiceEditor::SetSize(const wxRect &rect)
386 {
387 wxSize size = m_control->GetSize();
388
389 // Always center...looks bad otherwise
390 int x = rect.x + ((rect.width / 2) - (size.x / 2)) + 1;
391 int y = rect.y + ((rect.height / 2) - (size.y / 2)) + 1;
392
393 m_control->Move(x, y);
394 }
395
BeginEdit(int row,int col,wxGrid * grid)396 void ChoiceEditor::BeginEdit(int row, int col, wxGrid* grid)
397 {
398 if (!m_control)
399 return;
400
401 mOld = grid->GetTable()->GetValue(row, col);
402
403 Choice()->Clear();
404 Choice()->Append(mChoices);
405 Choice()->SetSelection( make_iterator_range( mChoices ).index( mOld ) );
406 Choice()->SetFocus();
407 }
408
EndEdit(int row,int col,wxGrid * grid)409 bool ChoiceEditor::EndEdit(int row, int col, wxGrid *grid)
410 {
411 wxString newvalue;
412 bool changed = EndEdit(row, col, grid, mOld, &newvalue);
413 if (changed) {
414 ApplyEdit(row, col, grid);
415 }
416 return changed;
417 }
418
EndEdit(int WXUNUSED (row),int WXUNUSED (col),const wxGrid * WXUNUSED (grid),const wxString & WXUNUSED (oldval),wxString * newval)419 bool ChoiceEditor::EndEdit(int WXUNUSED(row), int WXUNUSED(col),
420 const wxGrid* WXUNUSED(grid),
421 const wxString &WXUNUSED(oldval), wxString *newval)
422 {
423 int sel = Choice()->GetSelection();
424
425 // This can happen if the wxChoice control is displayed and the list of choices get changed
426 if ((sel < 0) || (sel >= (int)(mChoices.size())))
427 {
428 return false;
429 }
430
431 wxString val = mChoices[sel];
432 bool changed = val != mOld;
433
434 if (changed)
435 {
436 mValueAsString = val;
437 *newval = val;
438 }
439
440 return changed;
441 }
442
ApplyEdit(int row,int col,wxGrid * grid)443 void ChoiceEditor::ApplyEdit(int row, int col, wxGrid *grid)
444 {
445 grid->GetTable()->SetValue(row, col, mValueAsString);
446 }
447
Reset()448 void ChoiceEditor::Reset()
449 {
450 Choice()->SetSelection( make_iterator_range( mChoices ).index( mOld ) );
451 }
452
SetChoices(const wxArrayString & choices)453 void ChoiceEditor::SetChoices(const wxArrayString &choices)
454 {
455 mChoices = choices;
456 }
457
GetValue() const458 wxString ChoiceEditor::GetValue() const
459 {
460 return mChoices[Choice()->GetSelection()];
461 }
462
463 ///
464 ///
465 ///
466
BEGIN_EVENT_TABLE(Grid,wxGrid)467 BEGIN_EVENT_TABLE(Grid, wxGrid)
468 EVT_SET_FOCUS(Grid::OnSetFocus)
469 EVT_KEY_DOWN(Grid::OnKeyDown)
470 EVT_GRID_SELECT_CELL(Grid::OnSelectCell)
471 EVT_GRID_EDITOR_SHOWN(Grid::OnEditorShown)
472 END_EVENT_TABLE()
473
474 Grid::Grid(wxWindow *parent,
475 wxWindowID id,
476 const wxPoint& pos,
477 const wxSize& size,
478 long style,
479 const wxString& name)
480 : wxGrid(parent, id, pos, size, style | wxWANTS_CHARS, name)
481 {
482 #if wxUSE_ACCESSIBILITY
483 GetGridWindow()->SetAccessible(mAx = safenew GridAx(this));
484 #endif
485
486 // RegisterDataType takes ownership of renderer and editor
487
488 RegisterDataType(GRID_VALUE_TIME,
489 safenew NumericRenderer{ NumericConverter::TIME },
490 safenew NumericEditor
491 { NumericTextCtrl::TIME,
492 NumericConverter::SecondsFormat(), 44100.0 });
493
494 RegisterDataType(GRID_VALUE_FREQUENCY,
495 safenew NumericRenderer{ NumericConverter::FREQUENCY },
496 safenew NumericEditor
497 { NumericTextCtrl::FREQUENCY,
498 NumericConverter::HertzFormat(), 44100.0 });
499
500 RegisterDataType(GRID_VALUE_CHOICE,
501 safenew wxGridCellStringRenderer,
502 safenew ChoiceEditor);
503
504 // Bug #2803:
505 // Ensure selection doesn't show up.
506 SetSelectionForeground(GetDefaultCellTextColour());
507 SetSelectionBackground(GetDefaultCellBackgroundColour());
508 }
509
~Grid()510 Grid::~Grid()
511 {
512 #if wxUSE_ACCESSIBILITY
513 int cnt = mChildren.size();
514 while (cnt--) {
515 // PRL: I found this loop destroying right-to-left.
516 // Is the sequence of destruction important?
517 mChildren.pop_back();
518 }
519 #endif
520 }
521
OnSetFocus(wxFocusEvent & event)522 void Grid::OnSetFocus(wxFocusEvent &event)
523 {
524 event.Skip();
525
526 #if wxUSE_ACCESSIBILITY
527 mAx->SetCurrentCell(GetGridCursorRow(), GetGridCursorCol());
528 #endif
529 }
530
OnSelectCell(wxGridEvent & event)531 void Grid::OnSelectCell(wxGridEvent &event)
532 {
533 event.Skip();
534
535 MakeCellVisible(event.GetRow(), event.GetCol());
536
537 #if wxUSE_ACCESSIBILITY
538 mAx->SetCurrentCell(event.GetRow(), event.GetCol());
539 #endif
540 }
541
OnEditorShown(wxGridEvent & event)542 void Grid::OnEditorShown(wxGridEvent &event)
543 {
544 event.Skip();
545
546 // Bug #2803 (comment 7):
547 // Select row whenever an editor is displayed
548 SelectRow(GetGridCursorRow());
549 }
550
OnKeyDown(wxKeyEvent & event)551 void Grid::OnKeyDown(wxKeyEvent &event)
552 {
553 auto keyCode = event.GetKeyCode();
554 int crow = GetGridCursorRow();
555 int ccol = GetGridCursorCol();
556
557 if (event.CmdDown() && crow != wxGridNoCellCoords.GetRow() && ccol != wxGridNoCellCoords.GetCol())
558 {
559 wxClipboardLocker cb;
560
561 switch (keyCode)
562 {
563 case 'C': // Copy
564 {
565 wxTextDataObject *data = safenew wxTextDataObject(GetCellValue(crow, ccol));
566 wxClipboard::Get()->SetData(data);
567 return;
568 }
569 break;
570
571 case 'X': // Cut
572 {
573 wxTextDataObject *data = safenew wxTextDataObject(GetCellValue(crow, ccol));
574 wxClipboard::Get()->SetData(data);
575 SetCellValue(crow, ccol, "" );
576 return;
577 }
578 break;
579
580 case 'V': // Paste
581 {
582 if (wxClipboard::Get()->IsSupported(wxDF_UNICODETEXT))
583 {
584 wxTextDataObject data;
585 if (wxClipboard::Get()->GetData(data))
586 {
587 SetCellValue(crow, ccol, data.GetText());
588 return;
589 }
590 }
591 }
592 break;
593 }
594 }
595
596 switch (keyCode)
597 {
598 case WXK_LEFT:
599 case WXK_RIGHT:
600 {
601 int rows = GetNumberRows();
602 int cols = GetNumberCols();
603
604 const bool has_cells = rows > 0 && cols > 0;
605
606 if (has_cells) {
607 int crow = GetGridCursorRow();
608 int ccol = GetGridCursorCol();
609
610 const bool has_no_selection = crow == wxGridNoCellCoords.GetRow() || ccol == wxGridNoCellCoords.GetCol();
611
612 if (has_no_selection) {
613 SetGridCursor(0, 0);
614 }
615 else if (event.GetKeyCode() == WXK_LEFT) {
616 if (crow == 0 && ccol == 0) {
617 // do nothing
618 }
619 else if (ccol == 0) {
620 SetGridCursor(crow - 1, cols - 1);
621 }
622 else {
623 SetGridCursor(crow, ccol - 1);
624 }
625 }
626 else {
627 if (crow == rows - 1 && ccol == cols - 1) {
628 // do nothing
629 }
630 else if (ccol == cols - 1) {
631 SetGridCursor(crow + 1, 0);
632 }
633 else {
634 SetGridCursor(crow, ccol + 1);
635 }
636 }
637 }
638
639 #if wxUSE_ACCESSIBILITY
640 // Make sure the NEW cell is made available to the screen reader
641 mAx->SetCurrentCell(GetGridCursorRow(), GetGridCursorCol());
642 #endif
643 }
644 break;
645
646 case WXK_TAB:
647 {
648 if (event.ControlDown()) {
649 int flags = wxNavigationKeyEvent::FromTab |
650 ( event.ShiftDown() ?
651 wxNavigationKeyEvent::IsBackward :
652 wxNavigationKeyEvent::IsForward );
653 Navigate(flags);
654 return;
655 }
656
657 int rows = GetNumberRows();
658 int cols = GetNumberCols();
659 int crow = GetGridCursorRow();
660 int ccol = GetGridCursorCol();
661
662 const auto is_empty = rows <= 0 || cols <= 0;
663 const auto has_no_selection = crow == wxGridNoCellCoords.GetRow() || ccol == wxGridNoCellCoords.GetCol();
664
665 if (event.ShiftDown()) {
666 if (is_empty) {
667 Navigate(wxNavigationKeyEvent::FromTab | wxNavigationKeyEvent::IsBackward);
668 return;
669 }
670
671 if (crow == 0 && ccol == 0) {
672 Navigate(wxNavigationKeyEvent::FromTab | wxNavigationKeyEvent::IsBackward);
673 return;
674 }
675
676 if (has_no_selection) {
677 SetGridCursor(rows -1, cols - 1);
678 }
679 else if (ccol == 0) {
680 SetGridCursor(crow - 1, cols - 1);
681 }
682 else {
683 SetGridCursor(crow, ccol - 1);
684 }
685 }
686 else {
687 if (is_empty) {
688 Navigate(wxNavigationKeyEvent::FromTab | wxNavigationKeyEvent::IsForward);
689 return;
690 }
691
692 if (crow == rows - 1 && ccol == cols - 1) {
693 Navigate(wxNavigationKeyEvent::FromTab | wxNavigationKeyEvent::IsForward);
694 return;
695 }
696
697 if (has_no_selection) {
698 SetGridCursor(0, 0);
699 }
700 else if (ccol == cols - 1) {
701 SetGridCursor(crow + 1, 0);
702 }
703 else {
704 SetGridCursor(crow, ccol + 1);
705 }
706 }
707
708 MakeCellVisible(GetGridCursorRow(), GetGridCursorCol());
709
710 #if wxUSE_ACCESSIBILITY
711 // Make sure the NEW cell is made available to the screen reader
712 mAx->SetCurrentCell(GetGridCursorRow(), GetGridCursorCol());
713 #endif
714 }
715 break;
716
717 case WXK_RETURN:
718 case WXK_NUMPAD_ENTER:
719 {
720 if (!IsCellEditControlShown()) {
721 wxTopLevelWindow *tlw = wxDynamicCast(wxGetTopLevelParent(this), wxTopLevelWindow);
722 wxWindow *def = tlw->GetDefaultItem();
723 if (def && def->IsEnabled()) {
724 wxCommandEvent cevent(wxEVT_COMMAND_BUTTON_CLICKED,
725 def->GetId());
726 cevent.SetEventObject( def );
727 GetParent()->GetEventHandler()->ProcessEvent(cevent);
728 }
729 }
730 else {
731 wxGrid::OnKeyDown(event);
732
733 // This looks strange, but what it does is selects the cell when
734 // enter is pressed after editing. Without it, Jaws and Window-Eyes
735 // do not speak the NEW cell contents (the one below the edited one).
736 SetGridCursor(GetGridCursorRow(), GetGridCursorCol());
737 }
738 break;
739 }
740
741 default:
742 wxGrid::OnKeyDown(event);
743 break;
744 }
745 }
746
747 #if wxUSE_ACCESSIBILITY
ClearGrid()748 void Grid::ClearGrid()
749 {
750 wxGrid::ClearGrid();
751
752 mAx->TableUpdated();
753
754 return;
755 }
756
InsertRows(int pos,int numRows,bool updateLabels)757 bool Grid::InsertRows(int pos, int numRows, bool updateLabels)
758 {
759 bool res = wxGrid::InsertRows(pos, numRows, updateLabels);
760
761 mAx->TableUpdated();
762
763 return res;
764 }
765
AppendRows(int numRows,bool updateLabels)766 bool Grid::AppendRows(int numRows, bool updateLabels)
767 {
768 bool res = wxGrid::AppendRows(numRows, updateLabels);
769
770 mAx->TableUpdated();
771
772 return res;
773 }
774
DeleteRows(int pos,int numRows,bool updateLabels)775 bool Grid::DeleteRows(int pos, int numRows, bool updateLabels)
776 {
777 bool res = wxGrid::DeleteRows(pos, numRows, updateLabels);
778
779 mAx->TableUpdated();
780
781 return res;
782 }
783
InsertCols(int pos,int numCols,bool updateLabels)784 bool Grid::InsertCols(int pos, int numCols, bool updateLabels)
785 {
786 bool res = wxGrid::InsertCols(pos, numCols, updateLabels);
787
788 mAx->TableUpdated();
789
790 return res;
791 }
792
AppendCols(int numCols,bool updateLabels)793 bool Grid::AppendCols(int numCols, bool updateLabels)
794 {
795 bool res = wxGrid::AppendCols(numCols, updateLabels);
796
797 mAx->TableUpdated();
798
799 return res;
800 }
801
DeleteCols(int pos,int numCols,bool updateLabels)802 bool Grid::DeleteCols(int pos, int numCols, bool updateLabels)
803 {
804 bool res = wxGrid::DeleteCols(pos, numCols, updateLabels);
805
806 mAx->TableUpdated();
807
808 return res;
809 }
810
GridAx(Grid * grid)811 GridAx::GridAx(Grid *grid)
812 : WindowAccessible(grid->GetGridWindow())
813 {
814 mGrid = grid;
815 mLastId = -1;
816 }
817
TableUpdated()818 void GridAx::TableUpdated()
819 {
820 NotifyEvent(wxACC_EVENT_OBJECT_REORDER,
821 mGrid->GetGridWindow(),
822 wxOBJID_CLIENT,
823 0);
824 }
825
SetCurrentCell(int row,int col)826 void GridAx::SetCurrentCell(int row, int col)
827 {
828 int id = (((row * mGrid->GetNumberCols()) + col) + 1);
829
830 if (mLastId != -1) {
831 NotifyEvent(wxACC_EVENT_OBJECT_SELECTIONREMOVE,
832 mGrid->GetGridWindow(),
833 wxOBJID_CLIENT,
834 mLastId);
835 }
836
837 if (mGrid == wxWindow::FindFocus()) {
838 NotifyEvent(wxACC_EVENT_OBJECT_FOCUS,
839 mGrid->GetGridWindow(),
840 wxOBJID_CLIENT,
841 id);
842 }
843
844 NotifyEvent(wxACC_EVENT_OBJECT_SELECTION,
845 mGrid->GetGridWindow(),
846 wxOBJID_CLIENT,
847 id);
848
849 mLastId = id;
850 }
851
GetRowCol(int childId,int & row,int & col)852 bool GridAx::GetRowCol(int childId, int & row, int & col)
853 {
854 if (childId == wxACC_SELF) {
855 return false;
856 }
857
858 int cols = mGrid->GetNumberCols();
859 int id = childId - 1;
860
861 row = id / cols;
862 col = id % cols;
863
864 return true;
865 }
866
867 // Retrieves the address of an IDispatch interface for the specified child.
868 // All objects must support this property.
GetChild(int childId,wxAccessible ** child)869 wxAccStatus GridAx::GetChild(int childId, wxAccessible** child)
870 {
871 if (childId == wxACC_SELF) {
872 *child = this;
873 }
874 else {
875 *child = NULL;
876 }
877
878 return wxACC_OK;
879 }
880
881 // Gets the number of children.
GetChildCount(int * childCount)882 wxAccStatus GridAx::GetChildCount(int *childCount)
883 {
884 *childCount = mGrid->GetNumberRows() * mGrid->GetNumberCols();
885
886 return wxACC_OK;
887 }
888
889 // Gets the default action for this object (0) or > 0 (the action for a child).
890 // Return wxACC_OK even if there is no action. actionName is the action, or the empty
891 // string if there is no action.
892 // The retrieved string describes the action that is performed on an object,
893 // not what the object does as a result. For example, a toolbar button that prints
894 // a document has a default action of "Press" rather than "Prints the current document."
GetDefaultAction(int WXUNUSED (childId),wxString * actionName)895 wxAccStatus GridAx::GetDefaultAction(int WXUNUSED(childId), wxString *actionName)
896 {
897 actionName->clear();
898
899 return wxACC_OK;
900 }
901
902 // Returns the description for this object or a child.
GetDescription(int WXUNUSED (childId),wxString * description)903 wxAccStatus GridAx::GetDescription(int WXUNUSED(childId), wxString *description)
904 {
905 description->clear();
906
907 return wxACC_OK;
908 }
909
910 // Returns help text for this object or a child, similar to tooltip text.
GetHelpText(int WXUNUSED (childId),wxString * helpText)911 wxAccStatus GridAx::GetHelpText(int WXUNUSED(childId), wxString *helpText)
912 {
913 helpText->clear();
914
915 return wxACC_OK;
916 }
917
918 // Returns the keyboard shortcut for this object or child.
919 // Return e.g. ALT+K
GetKeyboardShortcut(int WXUNUSED (childId),wxString * shortcut)920 wxAccStatus GridAx::GetKeyboardShortcut(int WXUNUSED(childId), wxString *shortcut)
921 {
922 shortcut->clear();
923
924 return wxACC_OK;
925 }
926
927 // Returns the rectangle for this object (id = 0) or a child element (id > 0).
928 // rect is in screen coordinates.
GetLocation(wxRect & rect,int elementId)929 wxAccStatus GridAx::GetLocation(wxRect & rect, int elementId)
930 {
931 wxRect r;
932 int row;
933 int col;
934
935 if (GetRowCol(elementId, row, col)) {
936 rect = mGrid->CellToRect(row, col);
937 rect.SetPosition(mGrid->CalcScrolledPosition(rect.GetPosition()));
938 rect.SetPosition(mGrid->GetGridWindow()->ClientToScreen(rect.GetPosition()));
939 }
940 else {
941 rect = mGrid->GetRect();
942 rect.SetPosition(mGrid->GetParent()->ClientToScreen(rect.GetPosition()));
943 }
944
945 return wxACC_OK;
946 }
947
948 // Gets the name of the specified object.
GetName(int childId,wxString * name)949 wxAccStatus GridAx::GetName(int childId, wxString *name)
950 {
951 int row;
952 int col;
953
954 if (GetRowCol(childId, row, col)) {
955 wxString n = mGrid->GetColLabelValue(col);
956 wxString v = mGrid->GetCellValue(row, col);
957 if (v.empty()) {
958 v = _("Empty");
959 }
960
961 // Hack to provide a more intelligible response
962 NumericEditor *dt =
963 static_cast<NumericEditor *>(mGrid->GetDefaultEditorForType(GRID_VALUE_TIME));
964 NumericEditor *df =
965 static_cast<NumericEditor *>(mGrid->GetDefaultEditorForType(GRID_VALUE_FREQUENCY));
966 NumericEditor *c =
967 static_cast<NumericEditor *>(mGrid->GetCellEditor(row, col));
968
969 if (c && dt && df && ( c == dt || c == df)) {
970 double value;
971 v.ToDouble(&value);
972 NumericConverter converter(c == dt ? NumericConverter::TIME : NumericConverter::FREQUENCY,
973 c->GetFormat(),
974 value,
975 c->GetRate() );
976
977 v = converter.GetString();
978 }
979
980 if (c)
981 c->DecRef();
982 if (dt)
983 dt->DecRef();
984 if (df)
985 df->DecRef();
986
987 *name = n + wxT(" ") + v;
988 }
989
990 return wxACC_OK;
991 }
992
GetParent(wxAccessible ** WXUNUSED (parent))993 wxAccStatus GridAx::GetParent(wxAccessible ** WXUNUSED(parent))
994 {
995 return wxACC_NOT_IMPLEMENTED;
996 }
997
998 // Returns a role constant.
GetRole(int childId,wxAccRole * role)999 wxAccStatus GridAx::GetRole(int childId, wxAccRole *role)
1000 {
1001 if (childId == wxACC_SELF) {
1002 #if defined(__WXMSW__)
1003 *role = wxROLE_SYSTEM_TABLE;
1004 #endif
1005
1006 #if defined(__WXMAC__)
1007 *role = wxROLE_SYSTEM_GROUPING;
1008 #endif
1009 }
1010 else {
1011 *role = wxROLE_SYSTEM_TEXT;
1012 }
1013
1014 return wxACC_OK;
1015 }
1016
1017 // Gets a variant representing the selected children
1018 // of this object.
1019 // Acceptable values:
1020 // - a null variant (IsNull() returns TRUE)
1021 // - a list variant (GetType() == wxT("list"))
1022 // - an integer representing the selected child element,
1023 // or 0 if this object is selected (GetType() == wxT("long"))
1024 // - a "void*" pointer to a wxAccessible child object
GetSelections(wxVariant * WXUNUSED (selections))1025 wxAccStatus GridAx::GetSelections(wxVariant * WXUNUSED(selections))
1026 {
1027 return wxACC_NOT_IMPLEMENTED;
1028 }
1029
1030 // Returns a state constant.
GetState(int childId,long * state)1031 wxAccStatus GridAx::GetState(int childId, long *state)
1032 {
1033 int flag = wxACC_STATE_SYSTEM_FOCUSABLE |
1034 wxACC_STATE_SYSTEM_SELECTABLE;
1035 int col;
1036 int row;
1037
1038 if (!GetRowCol(childId, row, col)) {
1039 *state = 0;
1040 return wxACC_FAIL;
1041 }
1042
1043 #if defined(__WXMSW__)
1044 flag |= wxACC_STATE_SYSTEM_FOCUSED |
1045 wxACC_STATE_SYSTEM_SELECTED;
1046
1047 if (mGrid->IsReadOnly(row, col)) {
1048 // It would be more logical to also include the state
1049 // wxACC_STATE_SYSTEM_FOCUSABLE, but this causes Window-Eyes to
1050 // no longer read the cell as disabled
1051 flag = wxACC_STATE_SYSTEM_UNAVAILABLE | wxACC_STATE_SYSTEM_FOCUSED;
1052 }
1053 #endif
1054
1055 #if defined(__WXMAC__)
1056 if (mGrid->IsInSelection(row, col)) {
1057 flag |= wxACC_STATE_SYSTEM_SELECTED;
1058 }
1059
1060 if (mGrid->GetGridCursorRow() == row && mGrid->GetGridCursorCol() == col) {
1061 flag |= wxACC_STATE_SYSTEM_FOCUSED;
1062 }
1063
1064 if (mGrid->IsReadOnly(row, col)) {
1065 flag |= wxACC_STATE_SYSTEM_UNAVAILABLE;
1066 }
1067 #endif
1068
1069 *state = flag;
1070
1071 return wxACC_OK;
1072 }
1073
1074 // Returns a localized string representing the value for the object
1075 // or child.
1076 #if defined(__WXMAC__)
GetValue(int childId,wxString * strValue)1077 wxAccStatus GridAx::GetValue(int childId, wxString *strValue)
1078 #else
1079 wxAccStatus GridAx::GetValue(int WXUNUSED(childId), wxString *strValue)
1080 #endif
1081 {
1082 strValue->clear();
1083
1084 #if defined(__WXMSW__)
1085 return wxACC_OK;
1086 #endif
1087
1088 #if defined(__WXMAC__)
1089 return GetName(childId, strValue);
1090 #endif
1091 }
1092
1093 #if defined(__WXMAC__)
1094 // Selects the object or child.
Select(int childId,wxAccSelectionFlags selectFlags)1095 wxAccStatus GridAx::Select(int childId, wxAccSelectionFlags selectFlags)
1096 {
1097 int row;
1098 int col;
1099
1100 if (GetRowCol(childId, row, col)) {
1101
1102 if (selectFlags & wxACC_SEL_TAKESELECTION) {
1103 mGrid->SetGridCursor(row, col);
1104 }
1105
1106 mGrid->SelectBlock(row, col, row, col, selectFlags & wxACC_SEL_ADDSELECTION);
1107 }
1108
1109 return wxACC_OK;
1110 }
1111 #endif
1112
1113 // Gets the window with the keyboard focus.
1114 // If childId is 0 and child is NULL, no object in
1115 // this subhierarchy has the focus.
1116 // If this object has the focus, child should be 'this'.
GetFocus(int * childId,wxAccessible ** child)1117 wxAccStatus GridAx::GetFocus(int * childId, wxAccessible **child)
1118 {
1119 if (mGrid == wxWindow::FindFocus()) {
1120 if (mGrid->GetNumberRows() * mGrid->GetNumberCols() == 0) {
1121 *child = this;
1122 }
1123 else {
1124 *childId = mGrid->GetGridCursorRow()*mGrid->GetNumberCols() +
1125 mGrid->GetGridCursorCol() + 1;
1126 }
1127 }
1128
1129 return wxACC_OK;
1130 }
1131
1132 #endif // wxUSE_ACCESSIBILITY
1133