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