Audacity 3.2.0
NumericTextCtrl.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 NumericTextCtrl.cpp
6
7 Dominic Mazzoni
8
9
10**********************************************************************/
11
12
13
14#include "NumericTextCtrl.h"
15
16#include "SampleCount.h"
17#include "AllThemeResources.h"
18#include "AColor.h"
19#include "BasicMenu.h"
20#include "../KeyboardCapture.h"
21#include "Theme.h"
23
24#include <algorithm>
25#include <math.h>
26#include <limits>
27
28#include <wx/setup.h> // for wxUSE_* macros
29
30#include <wx/wx.h>
31#include <wx/dcbuffer.h>
32#include <wx/font.h>
33#include <wx/menu.h>
34#include <wx/stattext.h>
35#include <wx/tooltip.h>
36#include <wx/toplevel.h>
37
38#if wxUSE_ACCESSIBILITY
39#include "WindowAccessible.h"
40
41class NumericTextCtrlAx final : public WindowAccessible
42{
43public:
44 NumericTextCtrlAx(NumericTextCtrl * ctrl);
45
46 virtual ~ NumericTextCtrlAx();
47
48 // Performs the default action. childId is 0 (the action for this object)
49 // or > 0 (the action for a child).
50 // Return wxACC_NOT_SUPPORTED if there is no default action for this
51 // window (e.g. an edit control).
52 wxAccStatus DoDefaultAction(int childId) override;
53
54 // Retrieves the address of an IDispatch interface for the specified child.
55 // All objects must support this property.
56 wxAccStatus GetChild(int childId, wxAccessible **child) override;
57
58 // Gets the number of children.
59 wxAccStatus GetChildCount(int *childCount) override;
60
61 // Gets the default action for this object (0) or > 0 (the action for a child).
62 // Return wxACC_OK even if there is no action. actionName is the action, or the empty
63 // string if there is no action.
64 // The retrieved string describes the action that is performed on an object,
65 // not what the object does as a result. For example, a toolbar button that prints
66 // a document has a default action of "Press" rather than "Prints the current document."
67 wxAccStatus GetDefaultAction(int childId, wxString *actionName) override;
68
69 // Returns the description for this object or a child.
70 wxAccStatus GetDescription(int childId, wxString *description) override;
71
72 // Gets the window with the keyboard focus.
73 // If childId is 0 and child is NULL, no object in
74 // this subhierarchy has the focus.
75 // If this object has the focus, child should be 'this'.
76 wxAccStatus GetFocus(int *childId, wxAccessible **child) override;
77
78 // Returns help text for this object or a child, similar to tooltip text.
79 wxAccStatus GetHelpText(int childId, wxString *helpText) override;
80
81 // Returns the keyboard shortcut for this object or child.
82 // Return e.g. ALT+K
83 wxAccStatus GetKeyboardShortcut(int childId, wxString *shortcut) override;
84
85 // Returns the rectangle for this object (id = 0) or a child element (id > 0).
86 // rect is in screen coordinates.
87 wxAccStatus GetLocation(wxRect & rect, int elementId) override;
88
89 // Gets the name of the specified object.
90 wxAccStatus GetName(int childId, wxString *name) override;
91
92 // Returns a role constant.
93 wxAccStatus GetRole(int childId, wxAccRole *role) override;
94
95 // Gets a variant representing the selected children
96 // of this object.
97 // Acceptable values:
98 // - a null variant (IsNull() returns TRUE)
99 // - a list variant (GetType() == wxT("list"))
100 // - an integer representing the selected child element,
101 // or 0 if this object is selected (GetType() == wxT("long"))
102 // - a "void*" pointer to a wxAccessible child object
103 wxAccStatus GetSelections(wxVariant *selections) override;
104
105 // Returns a state constant.
106 wxAccStatus GetState(int childId, long *state) override;
107
108 // Returns a localized string representing the value for the object
109 // or child.
110 wxAccStatus GetValue(int childId, wxString *strValue) override;
111
112private:
113 NumericTextCtrl *mCtrl;
114 int mLastField;
115 int mLastDigit;
116 wxString mCachedName;
117 wxString mLastCtrlString;
118};
119
120#endif // wxUSE_ACCESSIBILITY
121
122#define ID_MENU 9800
123
124// Custom events
125
126DEFINE_EVENT_TYPE(EVT_TIMETEXTCTRL_UPDATED)
127DEFINE_EVENT_TYPE(EVT_FREQUENCYTEXTCTRL_UPDATED)
128DEFINE_EVENT_TYPE(EVT_BANDWIDTHTEXTCTRL_UPDATED)
129
130BEGIN_EVENT_TABLE(NumericTextCtrl, wxControl)
131 EVT_ERASE_BACKGROUND(NumericTextCtrl::OnErase)
132 EVT_PAINT(NumericTextCtrl::OnPaint)
133 EVT_CONTEXT_MENU(NumericTextCtrl::OnContext)
134 EVT_MOUSE_EVENTS(NumericTextCtrl::OnMouse)
135 EVT_KEY_DOWN(NumericTextCtrl::OnKeyDown)
136 EVT_KEY_UP(NumericTextCtrl::OnKeyUp)
137 EVT_SET_FOCUS(NumericTextCtrl::OnFocus)
138 EVT_KILL_FOCUS(NumericTextCtrl::OnFocus)
139 EVT_COMMAND(wxID_ANY, EVT_CAPTURE_KEY, NumericTextCtrl::OnCaptureKey)
141
143
144NumericTextCtrl::NumericTextCtrl(wxWindow *parent, wxWindowID id,
146 const NumericFormatSymbol &formatName,
147 double timeValue,
148 double sampleRate,
149 const Options &options,
150 const wxPoint &pos,
151 const wxSize &size):
152 wxControl(parent, id, pos, size, wxSUNKEN_BORDER | wxWANTS_CHARS),
153 NumericConverter(type, formatName, timeValue, sampleRate),
154 mBackgroundBitmap{},
155 mDigitFont{},
156 mLabelFont{},
157 mLastField(1),
158 mAutoPos(options.autoPos)
159 , mType(type)
160{
161 mAllowInvalidValue = false;
162
163 mDigitBoxW = 11;
164 mDigitBoxH = 19;
165
166 mBorderLeft = 1;
167 mBorderTop = 1;
168 mBorderRight = 1;
169 mBorderBottom = 1;
170
171 mReadOnly = options.readOnly;
172 mMenuEnabled = options.menuEnabled;
173 mButtonWidth = mMenuEnabled ? 9 : 0;
174
175 SetLayoutDirection(wxLayout_LeftToRight);
176 Layout();
177 Fit();
178 ValueToControls();
179
180 //PRL -- would this fix the following?
181 //ValueToControls();
182
183 //mchinen - aug 15 09 - this seems to put the mValue back to zero, and do nothing else.
184 //ControlsToValue();
185
186 mScrollRemainder = 0.0;
187
188#if wxUSE_ACCESSIBILITY
189 SetLabel(wxT(""));
190 SetName( {} );
191 SetAccessible(safenew NumericTextCtrlAx(this));
192#endif
193
194 if (options.hasInvalidValue)
195 SetInvalidValue( options.invalidValue );
196
197 if (!options.format.formatStr.empty())
198 SetFormatString( options.format );
199
200 if (options.hasValue)
201 SetValue( options.value );
202}
203
205{
206}
207
209{
210 wxControl::SetName( name.Translation() );
211}
212
213// Set the focus to the first (left-most) non-zero digit
214// If all digits are zero, the right-most position is focused
215// If all digits are hyphens (invalid), the left-most position is focused
217{
218 if (!mAutoPos)
219 return;
220
221 mFocusedDigit = 0;
222 while (mFocusedDigit < ((int)mDigits.size() - 1)) {
223 wxChar dgt = mValueString[mDigits[mFocusedDigit].pos];
224 if (dgt != '0') {
225 break;
226 }
228 }
229}
230
232{
233 return
235}
236
238{
239 auto result =
241 if (result) {
242 mBoxes.clear();
243 Layout();
244 Fit();
248 }
249 return result;
250}
251
252void NumericTextCtrl::SetSampleRate(double sampleRate)
253{
255 mBoxes.clear();
256 Layout();
257 Fit();
260}
261
262void NumericTextCtrl::SetValue(double newValue)
263{
267}
268
269void NumericTextCtrl::SetDigitSize(int width, int height)
270{
271 mDigitBoxW = width;
272 mDigitBoxH = height;
273 Layout();
274 Fit();
275}
276
278{
279 mReadOnly = readOnly;
280}
281
283{
284#if wxUSE_TOOLTIPS
285 wxString tip(_("(Use context menu to change format.)"));
286 if (enable)
287 SetToolTip(tip);
288 else {
289 wxToolTip *tt = GetToolTip();
290 if (tt && tt->GetTip() == tip)
291 SetToolTip(NULL);
292 }
293#endif
294 mMenuEnabled = enable;
295 mButtonWidth = enable ? 9 : 0;
296 Layout();
297 Fit();
298}
299
300void NumericTextCtrl::SetInvalidValue(double invalidValue)
301{
302 const bool wasInvalid = mAllowInvalidValue && (mValue == mInvalidValue);
303 mAllowInvalidValue = true;
304 mInvalidValue = invalidValue;
305 if (wasInvalid)
306 SetValue(invalidValue);
307}
308
309wxSize NumericTextCtrl::ComputeSizing(bool update, wxCoord boxW, wxCoord boxH)
310{
311 // Get current box size
312 if (boxW == 0) {
313 boxW = mDigitBoxW;
314 }
315
316 if (boxH == 0) {
317 boxH = mDigitBoxH;
318 }
319 boxH -= (mBorderTop + mBorderBottom);
320
321 // We can use the screen device context since we're not drawing to it
322 wxScreenDC dc;
323
324 // First calculate a rough point size
325 wxFont pf(wxSize(boxW, boxH), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
326 int fontSize = pf.GetPointSize();
327 wxCoord strW;
328 wxCoord strH;
329
330 // Now decrease it until we fit within our digit box
331 dc.SetFont(pf);
332 dc.GetTextExtent(wxT("0"), &strW, &strH);
333 while (strW > boxW || strH > boxH) {
334 dc.SetFont(wxFont(--fontSize, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL));
335 dc.GetTextExtent(wxT("0"), &strW, &strH);
336 }
337 fontSize--;
338
339 // Create the digit font with the new point size
340 if (update) {
341 mDigitFont = std::make_unique<wxFont>(fontSize, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
342 dc.SetFont(*mDigitFont);
343
344 // Remember the actual digit width and height using the new font
345 dc.GetTextExtent(wxT("0"), &mDigitW, &mDigitH);
346 }
347
348 // The label font should be a little smaller
349 std::unique_ptr<wxFont> labelFont = std::make_unique<wxFont>(fontSize - 1, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
350
351 // Use the label font for all remaining measurements since only non-digit text is left
352 dc.SetFont(*labelFont);
353
354 // Remember the pointer if updating
355 if (update) {
356 mLabelFont = std::move(labelFont);
357 }
358
359 // Get the width of the prefix, if any
360 dc.GetTextExtent(mPrefix, &strW, &strH);
361
362 // Bump x-position to the end of the prefix
363 int x = mBorderLeft + strW;
364
365 if (update) {
366 // Set the character position past the prefix
367 int pos = mPrefix.length();
368
369 // Reset digits array
370 mDigits.clear();
371 mBoxes.clear();
372
373 // Figure out the x-position of each field and label in the box
374 for (int i = 0, fcnt = mFields.size(); i < fcnt; ++i) {
375 // Get the size of the label
376 dc.GetTextExtent(mFields[i].label, &strW, &strH);
377
378 // Remember this field's x-position
379 mFields[i].fieldX = x;
380
381 // Remember metrics for each digit
382 for (int j = 0, dcnt = mFields[i].digits; j < dcnt; ++j) {
383 mDigits.push_back(DigitInfo(i, j, pos));
384 mBoxes.push_back(wxRect{ x, mBorderTop, boxW, boxH });
385 x += boxW;
386 pos++;
387 }
388
389 // Remember the label's x-position
390 mFields[i].labelX = x;
391
392 // Bump to end of label
393 x += strW;
394
395 // Remember the label's width
396 mFields[i].fieldW = x;
397
398 // Bump character position to end of label
399 pos += mFields[i].label.length();
400 }
401 }
402 else {
403 // Determine the maximum x-position (length) of the remaining fields
404 for (int i = 0, fcnt = mFields.size(); i < fcnt; ++i) {
405 // Get the size of the label
406 dc.GetTextExtent(mFields[i].label, &strW, &strH);
407
408 // Just bump to next field
409 x += (boxW * mFields[i].digits) + strW;
410 }
411 }
412
413 // Calculate the maximum dimensions
414 wxSize dim(x + mBorderRight, boxH + mBorderTop + mBorderBottom);
415
416 // Save maximumFinally, calculate the minimum dimensions
417 if (update) {
418 mWidth = dim.x;
419 mHeight = dim.y;
420 }
421
422 return wxSize(dim.x + mButtonWidth, dim.y);
423}
424
426{
428
429 wxMemoryDC memDC;
430 wxCoord strW, strH;
431 memDC.SetFont(*mLabelFont);
432 memDC.GetTextExtent(mPrefix, &strW, &strH);
433
434 int i;
435
436 // Draw the background bitmap - it contains black boxes where
437 // all of the digits go and all of the other text
438
439 wxBrush Brush;
440
441 mBackgroundBitmap = std::make_unique<wxBitmap>(mWidth + mButtonWidth, mHeight,24);
442 memDC.SelectObject(*mBackgroundBitmap);
443
444 theTheme.SetBrushColour( Brush, clrTimeHours );
445 memDC.SetBrush(Brush);
446 memDC.SetPen(*wxTRANSPARENT_PEN);
447 memDC.DrawRectangle(0, 0, mWidth + mButtonWidth, mHeight);
448
449 int numberBottom = mBorderTop + (mDigitBoxH - mDigitH)/2 + mDigitH;
450
451 memDC.GetTextExtent(wxT("0"), &strW, &strH);
452 int labelTop = numberBottom - strH;
453
454 memDC.SetTextForeground(theTheme.Colour( clrTimeFont ));
455 memDC.SetTextBackground(theTheme.Colour( clrTimeBack ));
456 memDC.DrawText(mPrefix, mBorderLeft, labelTop);
457
458 theTheme.SetBrushColour( Brush, clrTimeBack );
459 memDC.SetBrush(Brush);
460 //memDC.SetBrush(*wxLIGHT_GREY_BRUSH);
461 for(i = 0; i < mDigits.size(); i++)
462 memDC.DrawRectangle(GetBox(i));
463 memDC.SetBrush( wxNullBrush );
464
465 for(i = 0; i < mFields.size(); i++)
466 memDC.DrawText(mFields[i].label,
467 mFields[i].labelX, labelTop);
468
469 if (mMenuEnabled) {
470 wxRect r(mWidth, 0, mButtonWidth - 1, mHeight - 1);
471 AColor::Bevel(memDC, true, r);
472 memDC.SetBrush(*wxBLACK_BRUSH);
473 memDC.SetPen(*wxBLACK_PEN);
474 AColor::Arrow(memDC,
475 mWidth + 1,
476 (mHeight / 2) - 2,
477 mButtonWidth - 2);
478 }
479 return true;
480}
481
483{
484 wxSize sz = GetSize();
485 wxSize csz = GetClientSize();
486
487 sz.x = mButtonWidth + mWidth + (sz.x - csz.x);
488 sz.y = mHeight + (sz.y - csz.y);
489
490 SetInitialSize(sz);
491}
492
493void NumericTextCtrl::OnErase(wxEraseEvent & WXUNUSED(event))
494{
495 // Ignore it to prevent flashing
496}
497
498void NumericTextCtrl::OnPaint(wxPaintEvent & WXUNUSED(event))
499{
500 wxBufferedPaintDC dc(this);
501 bool focused = (FindFocus() == this);
502
503 dc.DrawBitmap(*mBackgroundBitmap, 0, 0);
504
505 wxPen Pen;
506 wxBrush Brush;
507 if (focused) {
508 theTheme.SetPenColour( Pen, clrTimeFontFocus );
509 dc.SetPen(Pen);
510 dc.SetBrush(*wxTRANSPARENT_BRUSH);
511 dc.DrawRectangle(0, 0, mWidth, mHeight);
512 dc.SetPen( wxNullPen );
513 }
514
515 dc.SetFont(*mDigitFont);
516 dc.SetTextForeground(theTheme.Colour( clrTimeFont ));
517 dc.SetTextBackground(theTheme.Colour( clrTimeBack ));
518
519 dc.SetPen(*wxTRANSPARENT_PEN);
520 theTheme.SetBrushColour( Brush , clrTimeBackFocus );
521 dc.SetBrush( Brush );
522
523 int i;
524 for(i = 0; i < (int)mDigits.size(); i++) {
525 wxRect box = GetBox(i);
526 if (focused && mFocusedDigit == i) {
527 dc.DrawRectangle(box);
528 dc.SetTextForeground(theTheme.Colour( clrTimeFontFocus ));
529 dc.SetTextBackground(theTheme.Colour( clrTimeBackFocus ));
530 }
531 int pos = mDigits[i].pos;
532 wxString digit = mValueString.Mid(pos, 1);
533 int x = box.x + (mDigitBoxW - mDigitW)/2;
534 int y = box.y + (mDigitBoxH - mDigitH)/2;
535 dc.DrawText(digit, x, y);
536 if (focused && mFocusedDigit == i) {
537 dc.SetTextForeground(theTheme.Colour( clrTimeFont ));
538 dc.SetTextBackground(theTheme.Colour( clrTimeBack ));
539 }
540 }
541 dc.SetPen( wxNullPen );
542 dc.SetBrush( wxNullBrush );
543}
544
545void NumericTextCtrl::OnContext(wxContextMenuEvent &event)
546{
547 wxMenu menu;
548 int i;
549
550 if (!mMenuEnabled) {
551 event.Skip();
552 return;
553 }
554
555 SetFocus();
556
557 int currentSelection = -1;
558 for (i = 0; i < GetNumBuiltins(); i++) {
559 menu.AppendRadioItem(ID_MENU + i, GetBuiltinName(i).Translation());
561 menu.Check(ID_MENU + i, true);
562 currentSelection = i;
563 }
564 }
565
566 menu.Bind(wxEVT_MENU, [](auto&){});
567 BasicMenu::Handle{ &menu }.Popup(
569 { 0, 0 }
570 );
571
572 // This used to be in an EVT_MENU() event handler, but GTK
573 // is sensitive to what is done within the handler if the
574 // user happens to check the first menuitem and then is
575 // moving down the menu when the ...CTRL_UPDATED event
576 // handler kicks in.
577 for (i = 0; i < GetNumBuiltins(); i++) {
578 if (menu.IsChecked(ID_MENU + i) && i != currentSelection) {
580
581 int eventType = 0;
582 switch (mType) {
584 eventType = EVT_TIMETEXTCTRL_UPDATED;
585 break;
587 eventType = EVT_FREQUENCYTEXTCTRL_UPDATED;
588 break;
590 eventType = EVT_BANDWIDTHTEXTCTRL_UPDATED;
591 break;
592 default:
593 wxASSERT(false);
594 break;
595 }
596
597 wxCommandEvent e(eventType, GetId());
598 e.SetInt(i);
599 e.SetString(GetBuiltinName(i).Internal());
600 GetParent()->GetEventHandler()->AddPendingEvent(e);
601 }
602 }
603
604}
605
606void NumericTextCtrl::OnMouse(wxMouseEvent &event)
607{
608 if (event.LeftDown() && event.GetX() >= mWidth) {
609 wxContextMenuEvent e;
610 OnContext(e);
611 }
612 else if (event.LeftDown()) {
613 SetFocus();
614
615 int bestDist = 9999;
616 unsigned int i;
617
618 mFocusedDigit = 0;
619 for(i = 0; i < mDigits.size(); i++) {
620 int dist = abs(event.m_x - (GetBox(i).x +
621 GetBox(i).width/2));
622 if (dist < bestDist) {
623 mFocusedDigit = i;
624 bestDist = dist;
625 }
626 }
627
628 Refresh(false);
629 }
630 else if (event.RightDown() && mMenuEnabled) {
631 wxContextMenuEvent e;
632 OnContext(e);
633 }
634 else if(!mReadOnly && event.m_wheelRotation != 0 ) {
635 double steps = event.m_wheelRotation /
636 (event.m_wheelDelta > 0 ? (double)event.m_wheelDelta : 120.0) +
638 mScrollRemainder = steps - floor(steps);
639 steps = floor(steps);
640
641 Adjust((int)fabs(steps), steps < 0.0 ? -1 : 1);
642 Updated();
643 }
644}
645
646void NumericTextCtrl::OnFocus(wxFocusEvent &event)
647{
648 KeyboardCapture::OnFocus( *this, event );
649
650 if (event.GetEventType() != wxEVT_KILL_FOCUS &&
651 mFocusedDigit <= 0 )
653
654 event.Skip( false ); // PRL: not sure why, but preserving old behavior
655}
656
657void NumericTextCtrl::OnCaptureKey(wxCommandEvent &event)
658{
659 wxKeyEvent *kevent = (wxKeyEvent *)event.GetEventObject();
660 int keyCode = kevent->GetKeyCode();
661
662 // Convert numeric keypad entries.
663 if ((keyCode >= WXK_NUMPAD0) && (keyCode <= WXK_NUMPAD9))
664 keyCode -= WXK_NUMPAD0 - '0';
665
666 switch (keyCode)
667 {
668 case WXK_BACK:
669 case WXK_LEFT:
670 case WXK_RIGHT:
671 case WXK_HOME:
672 case WXK_END:
673 case WXK_UP:
674 case WXK_DOWN:
675 case WXK_TAB:
676 case WXK_RETURN:
677 case WXK_NUMPAD_ENTER:
678 case WXK_DELETE:
679 return;
680
681 default:
682 if (keyCode >= '0' && keyCode <= '9' && !kevent->HasAnyModifiers())
683 return;
684 }
685
686 event.Skip();
687
688 return;
689}
690
691void NumericTextCtrl::OnKeyUp(wxKeyEvent &event)
692{
693 int keyCode = event.GetKeyCode();
694
695 event.Skip(true);
696
697 if ((keyCode >= WXK_NUMPAD0) && (keyCode <= WXK_NUMPAD9))
698 keyCode -= WXK_NUMPAD0 - '0';
699
700 if ((keyCode >= '0' && keyCode <= '9' && !event.HasAnyModifiers()) ||
701 (keyCode == WXK_DELETE) ||
702 (keyCode == WXK_BACK) ||
703 (keyCode == WXK_UP) ||
704 (keyCode == WXK_DOWN)) {
705 Updated(true);
706 }
707}
708
709void NumericTextCtrl::OnKeyDown(wxKeyEvent &event)
710{
711 if (mDigits.size() == 0)
712 {
713 mFocusedDigit = 0;
714 return;
715 }
716
717 event.Skip(false);
718
719 int keyCode = event.GetKeyCode();
720 int digit = mFocusedDigit;
721
722 if (mFocusedDigit < 0)
723 mFocusedDigit = 0;
724 if (mFocusedDigit >= (int)mDigits.size())
725 mFocusedDigit = mDigits.size() - 1;
726
727 // Convert numeric keypad entries.
728 if ((keyCode >= WXK_NUMPAD0) && (keyCode <= WXK_NUMPAD9))
729 keyCode -= WXK_NUMPAD0 - '0';
730
731 if (!mReadOnly && (keyCode >= '0' && keyCode <= '9' && !event.HasAnyModifiers())) {
732 int digitPosition = mDigits[mFocusedDigit].pos;
733 if (mValueString[digitPosition] == wxChar('-')) {
734 mValue = std::max(mMinValue, std::min(mMaxValue, 0.0));
736 // Beware relocation of the string
737 digitPosition = mDigits[mFocusedDigit].pos;
738 }
739 mValueString[digitPosition] = wxChar(keyCode);
741 Refresh();// Force an update of the control. [Bug 1497]
743 mFocusedDigit = (mFocusedDigit + 1) % (mDigits.size());
744 Updated();
745 }
746
747 else if (!mReadOnly && keyCode == WXK_DELETE) {
750 }
751
752 else if (!mReadOnly && keyCode == WXK_BACK) {
753 // Moves left, replaces that char with '0', stays there...
755 mFocusedDigit += mDigits.size();
756 mFocusedDigit %= mDigits.size();
757 wxString::reference theDigit = mValueString[mDigits[mFocusedDigit].pos];
758 if (theDigit != wxChar('-'))
759 theDigit = '0';
761 Refresh();// Force an update of the control. [Bug 1497]
763 Updated();
764 }
765
766 else if (keyCode == WXK_LEFT) {
768 mFocusedDigit += mDigits.size();
769 mFocusedDigit %= mDigits.size();
770 Refresh();
771 }
772
773 else if (keyCode == WXK_RIGHT) {
775 mFocusedDigit %= mDigits.size();
776 Refresh();
777 }
778
779 else if (keyCode == WXK_HOME) {
780 mFocusedDigit = 0;
781 Refresh();
782 }
783
784 else if (keyCode == WXK_END) {
785 mFocusedDigit = mDigits.size() - 1;
786 Refresh();
787 }
788
789 else if (!mReadOnly && keyCode == WXK_UP) {
790 Adjust(1, 1);
791 Updated();
792 }
793
794 else if (!mReadOnly && keyCode == WXK_DOWN) {
795 Adjust(1, -1);
796 Updated();
797 }
798
799 else if (keyCode == WXK_TAB) {
800#if defined(__WXMSW__)
801 // Using Navigate() on Windows, rather than the following code causes
802 // bug 1542
803 wxWindow* parent = GetParent();
804 wxNavigationKeyEvent nevent;
805 nevent.SetWindowChange(event.ControlDown());
806 nevent.SetDirection(!event.ShiftDown());
807 nevent.SetEventObject(parent);
808 nevent.SetCurrentFocus(parent);
809 GetParent()->GetEventHandler()->ProcessEvent(nevent);
810#else
811 Navigate(event.ShiftDown()
812 ? wxNavigationKeyEvent::IsBackward
813 : wxNavigationKeyEvent::IsForward);
814#endif
815 }
816
817 else if (keyCode == WXK_RETURN || keyCode == WXK_NUMPAD_ENTER) {
818 wxTopLevelWindow *tlw = wxDynamicCast(wxGetTopLevelParent(this), wxTopLevelWindow);
819 wxWindow *def = tlw->GetDefaultItem();
820 if (def && def->IsEnabled()) {
821 wxCommandEvent cevent(wxEVT_COMMAND_BUTTON_CLICKED,
822 def->GetId());
823 cevent.SetEventObject( def );
824 GetParent()->GetEventHandler()->ProcessEvent(cevent);
825 }
826 }
827
828 else {
829 event.Skip();
830 return;
831 }
832
833 if (digit != mFocusedDigit) {
835 }
836}
837
839{
840#if wxUSE_ACCESSIBILITY
841 if (mDigits.size() == 0)
842 {
843 mFocusedDigit = 0;
844 return;
845 }
846 mFocusedDigit = digit;
847 mLastField = mDigits[mFocusedDigit].field + 1;
848
849 GetAccessible()->NotifyEvent(wxACC_EVENT_OBJECT_FOCUS,
850 this,
851 wxOBJID_CLIENT,
852 mFocusedDigit + 1);
853#endif
854}
855
856void NumericTextCtrl::Updated(bool keyup /* = false */)
857{
858 wxCommandEvent event(wxEVT_COMMAND_TEXT_UPDATED, GetId());
859
860 // This will give listeners the ability to do tasks when the
861 // update has been completed, like when the UP ARROW has been
862 // held down and is finally released.
863 event.SetInt(keyup);
864 event.SetEventObject(this);
865 GetEventHandler()->ProcessEvent(event);
866
867#if wxUSE_ACCESSIBILITY
868 if (!keyup) {
869 if (mDigits.size() == 0)
870 {
871 mFocusedDigit = 0;
872 return;
873 }
874
875 // The object_focus event is only needed by Window-Eyes
876 // and can be removed when we cease to support this screen reader.
877 GetAccessible()->NotifyEvent(wxACC_EVENT_OBJECT_FOCUS,
878 this,
879 wxOBJID_CLIENT,
880 mFocusedDigit + 1);
881
882 GetAccessible()->NotifyEvent(wxACC_EVENT_OBJECT_NAMECHANGE,
883 this,
884 wxOBJID_CLIENT,
885 mFocusedDigit + 1);
886 }
887#endif
888}
889
891{
892 const wxString previousValueString = mValueString;
894 if (mValueString != previousValueString) {
895 // Doing this only when needed is an optimization.
896 // NumerixTextCtrls are used in the selection bar at the bottom
897 // of Audacity, and are updated at high frequency through
898 // SetValue() when Audacity is playing. This consumes a
899 // significant amount of CPU. Typically, when a track is
900 // playing, only one of the NumericTextCtrl actually changes
901 // (the audio position). We save CPU by updating the control
902 // only when needed.
903 Refresh(false);
904 }
905}
906
907
909{
911}
912
913wxRect NumericTextCtrl::GetBox(size_t ii) const
914{
915 if (ii < mBoxes.size())
916 return mBoxes[ii];
917 return {};
918}
919
920#if wxUSE_ACCESSIBILITY
921
922NumericTextCtrlAx::NumericTextCtrlAx(NumericTextCtrl *ctrl)
923: WindowAccessible(ctrl)
924{
925 mCtrl = ctrl;
926 mLastField = -1;
927 mLastDigit = -1;
928}
929
930NumericTextCtrlAx::~NumericTextCtrlAx()
931{
932}
933
934// Performs the default action. childId is 0 (the action for this object)
935// or > 0 (the action for a child).
936// Return wxACC_NOT_SUPPORTED if there is no default action for this
937// window (e.g. an edit control).
938wxAccStatus NumericTextCtrlAx::DoDefaultAction(int WXUNUSED(childId))
939{
940 return wxACC_NOT_SUPPORTED;
941}
942
943// Retrieves the address of an IDispatch interface for the specified child.
944// All objects must support this property.
945wxAccStatus NumericTextCtrlAx::GetChild(int childId, wxAccessible **child)
946{
947 if (childId == wxACC_SELF) {
948 *child = this;
949 }
950 else {
951 *child = NULL;
952 }
953
954 return wxACC_OK;
955}
956
957// Gets the number of children.
958wxAccStatus NumericTextCtrlAx::GetChildCount(int *childCount)
959{
960 *childCount = mCtrl->mDigits.size();
961
962 return wxACC_OK;
963}
964
965// Gets the default action for this object (0) or > 0 (the action for
966// a child). Return wxACC_OK even if there is no action. actionName
967// is the action, or the empty string if there is no action. The
968// retrieved string describes the action that is performed on an
969// object, not what the object does as a result. For example, a
970// toolbar button that prints a document has a default action of
971// "Press" rather than "Prints the current document."
972wxAccStatus NumericTextCtrlAx::GetDefaultAction(int WXUNUSED(childId), wxString *actionName)
973{
974 actionName->clear();
975
976 return wxACC_OK;
977}
978
979// Returns the description for this object or a child.
980wxAccStatus NumericTextCtrlAx::GetDescription(int WXUNUSED(childId), wxString *description)
981{
982 description->clear();
983
984 return wxACC_OK;
985}
986
987// Gets the window with the keyboard focus.
988// If childId is 0 and child is NULL, no object in
989// this subhierarchy has the focus.
990// If this object has the focus, child should be 'this'.
991wxAccStatus NumericTextCtrlAx::GetFocus(int *childId, wxAccessible **child)
992{
993 *childId = mCtrl->GetFocusedDigit();
994 *child = this;
995
996 return wxACC_OK;
997}
998
999// Returns help text for this object or a child, similar to tooltip text.
1000wxAccStatus NumericTextCtrlAx::GetHelpText(int WXUNUSED(childId), wxString *helpText)
1001{
1002// removed help text, as on balance it's more of an irritation than useful
1003#if 0 // was #if wxUSE_TOOLTIPS
1004 wxToolTip *pTip = mCtrl->GetToolTip();
1005 if (pTip) {
1006 *helpText = pTip->GetTip();
1007 }
1008
1009 return wxACC_OK;
1010#else
1011 helpText->clear();
1012
1013 return wxACC_NOT_SUPPORTED;
1014#endif
1015}
1016
1017// Returns the keyboard shortcut for this object or child.
1018// Return e.g. ALT+K
1019wxAccStatus NumericTextCtrlAx::GetKeyboardShortcut(int WXUNUSED(childId), wxString *shortcut)
1020{
1021 shortcut->clear();
1022
1023 return wxACC_OK;
1024}
1025
1026// Returns the rectangle for this object (id = 0) or a child element (id > 0).
1027// rect is in screen coordinates.
1028wxAccStatus NumericTextCtrlAx::GetLocation(wxRect & rect, int elementId)
1029{
1030 if ((elementId != wxACC_SELF) &&
1031 // We subtract 1, below, and need to avoid neg index to mDigits.
1032 (elementId > 0))
1033 {
1034// rect.x += mCtrl->mFields[elementId - 1].fieldX;
1035// rect.width = mCtrl->mFields[elementId - 1].fieldW;
1036 rect = mCtrl->GetBox(elementId - 1);
1037 rect.SetPosition(mCtrl->ClientToScreen(rect.GetPosition()));
1038 }
1039 else
1040 {
1041 rect = mCtrl->GetRect();
1042 rect.SetPosition(mCtrl->GetParent()->ClientToScreen(rect.GetPosition()));
1043 }
1044
1045 return wxACC_OK;
1046}
1047
1048static void GetFraction( wxString &label,
1049 const NumericConverter::FormatStrings &formatStrings,
1050 bool isTime, int digits )
1051{
1052 TranslatableString tr = formatStrings.fraction;
1053 if ( tr.empty() ) {
1054 wxASSERT( isTime );
1055 if (digits == 2)
1056 tr = XO("centiseconds");
1057 else if (digits == 3)
1058 tr = XO("milliseconds");
1059 }
1060 if (!tr.empty())
1061 label = tr.Translation();
1062}
1063
1064// Gets the name of the specified object.
1065wxAccStatus NumericTextCtrlAx::GetName(int childId, wxString *name)
1066{
1067 // Slightly messy trick to save us some prefixing.
1068 std::vector<NumericField> & mFields = mCtrl->mFields;
1069
1070 wxString ctrlString = mCtrl->GetString();
1071 int field = mCtrl->GetFocusedField();
1072
1073 // Return the entire string including the control label
1074 // when the requested child ID is wxACC_SELF. (Mainly when
1075 // the control gets the focus.)
1076 if ((childId == wxACC_SELF) ||
1077 // We subtract 1 from childId in the other cases below, and
1078 // need to avoid neg index to mDigits, so funnel into this clause.
1079 (childId < 1))
1080 {
1081 *name = mCtrl->GetName();
1082 if (name->empty()) {
1083 *name = mCtrl->GetLabel();
1084 }
1085
1086 *name += wxT(" ") +
1087 mCtrl->GetString();
1088 }
1089 // This case is needed because of the behaviour of Narrator, which
1090 // is different for the other Windows screen readers. After a focus event,
1091 // Narrator causes getName() to be called more than once. However, the code in
1092 // the following else statement assumes that it is executed only once
1093 // when the focus has been moved to another digit. This else if statement
1094 // ensures that this is the case, by using a cached value if nothing
1095 // has changed.
1096 else if (childId == mLastDigit && ctrlString.IsSameAs(mLastCtrlString)) {
1097 *name = mCachedName;
1098 }
1099 else {
1100 // The user has moved from one field of the time to another so
1101 // report the value of the field and the field's label.
1102 if (mLastField != field) {
1103 wxString label = mFields[field - 1].label;
1104 int cnt = mFields.size();
1105 wxString decimal = wxLocale::GetInfo(wxLOCALE_DECIMAL_POINT, wxLOCALE_CAT_NUMBER);
1106
1107 // If the NEW field is the last field, then check it to see if
1108 // it represents fractions of a second.
1109 // PRL: click a digit of the control and use left and right arrow keys
1110 // to exercise this code
1111 const bool isTime = (mCtrl->mType == NumericTextCtrl::TIME);
1112 if (field > 1 && field == cnt) {
1113 if (mFields[field - 2].label == decimal) {
1114 int digits = mFields[field - 1].digits;
1115 GetFraction( label, mCtrl->mFormatString,
1116 isTime, digits );
1117 }
1118 }
1119 // If the field following this one represents fractions of a
1120 // second then use that label instead of the decimal point.
1121 else if (label == decimal && field == cnt - 1) {
1122 label = mFields[field].label;
1123 }
1124
1125 *name = mFields[field - 1].str +
1126 wxT(" ") +
1127 label +
1128 wxT(", ") + // comma inserts a slight pause
1129 mCtrl->GetString().at(mCtrl->mDigits[childId - 1].pos);
1130 mLastField = field;
1131 mLastDigit = childId;
1132 }
1133 // The user has moved from one digit to another within a field so
1134 // just report the digit under the cursor.
1135 else if (mLastDigit != childId) {
1136 *name = mCtrl->GetString().at(mCtrl->mDigits[childId - 1].pos);
1137 mLastDigit = childId;
1138 }
1139 // The user has updated the value of a field, so report the field's
1140 // value only.
1141 else if (field > 0)
1142 {
1143 *name = mFields[field - 1].str;
1144 }
1145
1146 mCachedName = *name;
1147 mLastCtrlString = ctrlString;
1148 }
1149
1150 return wxACC_OK;
1151}
1152
1153// Returns a role constant.
1154wxAccStatus NumericTextCtrlAx::GetRole(int WXUNUSED(childId), wxAccRole *role)
1155{
1156 *role = wxROLE_SYSTEM_STATICTEXT;
1157 return wxACC_OK;
1158}
1159
1160// Gets a variant representing the selected children
1161// of this object.
1162// Acceptable values:
1163// - a null variant (IsNull() returns TRUE)
1164// - a list variant (GetType() == wxT("list"))
1165// - an integer representing the selected child element,
1166// or 0 if this object is selected (GetType() == wxT("long"))
1167// - a "void*" pointer to a wxAccessible child object
1168wxAccStatus NumericTextCtrlAx::GetSelections(wxVariant * WXUNUSED(selections))
1169{
1170 return wxACC_NOT_IMPLEMENTED;
1171}
1172
1173// Returns a state constant.
1174wxAccStatus NumericTextCtrlAx::GetState(int WXUNUSED(childId), long *state)
1175{
1176 *state = wxACC_STATE_SYSTEM_FOCUSABLE;
1177 *state |= (mCtrl == wxWindow::FindFocus() ? wxACC_STATE_SYSTEM_FOCUSED : 0);
1178
1179 return wxACC_OK;
1180}
1181
1182// Returns a localized string representing the value for the object
1183// or child.
1184wxAccStatus NumericTextCtrlAx::GetValue(int WXUNUSED(childId), wxString * WXUNUSED(strValue))
1185{
1186 return wxACC_NOT_IMPLEMENTED;
1187}
1188
1189#endif
1190
wxEVT_COMMAND_BUTTON_CLICKED
wxT("CloseDown"))
@ Internal
Indicates internal failure from Audacity.
Abstractions of menus and their items.
END_EVENT_TABLE()
int min(int a, int b)
const TranslatableString name
Definition: Distortion.cpp:76
DEFINE_EVENT_TYPE(EVT_FREQWINDOW_RECALC)
XO("Cut/Copy/Paste")
#define field(n, t)
Definition: ImportAUP.cpp:165
#define _(s)
Definition: Internat.h:73
EVT_COMMAND(wxID_ANY, EVT_FREQUENCYTEXTCTRL_UPDATED, LabelDialog::OnFreqUpdate) LabelDialog
Definition: LabelDialog.cpp:88
#define safenew
Definition: MemoryX.h:10
#define ID_MENU
wxEVT_COMMAND_TEXT_UPDATED
Definition: Nyquist.cpp:133
IMPLEMENT_CLASS(cloud::ShareAudioToolbar, ToolBar)
TranslatableString label
Definition: TagsEditor.cpp:164
THEME_API Theme theTheme
Definition: Theme.cpp:82
static void Arrow(wxDC &dc, wxCoord x, wxCoord y, int width, bool down=true)
Definition: AColor.cpp:160
static void Bevel(wxDC &dc, bool up, const wxRect &r)
Definition: AColor.cpp:266
void Popup(const BasicUI::WindowPlacement &window, const Point &pos={})
Display the menu at pos, invoke at most one action, then hide it.
Definition: BasicMenu.cpp:209
ComponentInterfaceSymbol pairs a persistent string identifier used internally with an optional,...
const wxString Translation() const
NumericConverter provides the advanced formatting control used in the selection bar of Audacity.
FormatStrings mFormatString
virtual void ControlsToValue()
virtual void ValueToControls()
std::vector< DigitInfo > mDigits
void SetSampleRate(double sampleRate)
FormatStrings GetBuiltinFormat(const int index)
bool SetFormatString(const FormatStrings &formatString)
std::vector< NumericField > mFields
void SetValue(double newValue)
NumericFormatSymbol GetBuiltinName(const int index)
void Adjust(int steps, int dir)
void EnableMenu(bool enable=true)
void OnMouse(wxMouseEvent &event)
void Fit() override
void OnContext(wxContextMenuEvent &event)
bool SetFormatString(const FormatStrings &formatString)
void OnFocus(wxFocusEvent &event)
void OnKeyDown(wxKeyEvent &event)
void SetReadOnly(bool readOnly=true)
void Updated(bool keyup=false)
std::unique_ptr< wxFont > mDigitFont
virtual ~NumericTextCtrl()
void SetInvalidValue(double invalidValue)
bool Layout() override
wxRect GetBox(size_t ii) const
void OnPaint(wxPaintEvent &event)
void SetSampleRate(double sampleRate)
void OnKeyUp(wxKeyEvent &event)
void OnErase(wxEraseEvent &event)
wxSize ComputeSizing(bool update=true, wxCoord digitW=0, wxCoord digitH=0)
void ValueToControls() override
void SetDigitSize(int width, int height)
std::vector< wxRect > mBoxes
std::unique_ptr< wxBitmap > mBackgroundBitmap
void SetName(const TranslatableString &name)
NumericConverter::Type mType
bool SetFormatName(const NumericFormatSymbol &formatName)
void ControlsToValue() override
void SetValue(double newValue)
std::unique_ptr< wxFont > mLabelFont
void OnCaptureKey(wxCommandEvent &event)
wxColour & Colour(int iIndex)
void SetBrushColour(wxBrush &Brush, int iIndex)
void SetPenColour(wxPen &Pen, int iIndex)
Holds a msgid for the translation catalog; may also bind format arguments.
wxString Translation() const
An alternative to using wxWindowAccessible, which in wxWidgets 3.1.1 contained GetParent() which was ...
void SetFocus(const WindowPlacement &focus)
Set the window that accepts keyboard input.
Definition: BasicUI.h:352
std::unique_ptr< WindowPlacement > FindFocus()
Find the window that is accepting keyboard input, if any.
Definition: BasicUI.h:343
void OnFocus(wxWindow &window, wxFocusEvent &event)
a function useful to implement a focus event handler The window releases the keyboard if the event is...
Window placement information for wxWidgetsBasicUI can be constructed from a wxWindow pointer.