/********************************************************************** Audacity: A Digital Audio Editor KeyView.cpp *******************************************************************//*! \class KeyView \brief Provides multiple views of keyboard shortcuts *//*********************************************************************/ #include "../Audacity.h" #include "KeyView.h" #include // for wxUSE_* macros #include #include #include #include "../AColor.h" #include "../ShuttleGui.h" #include "../commands/CommandManager.h" #include #include #if wxUSE_ACCESSIBILITY #include "WindowAccessible.h" // ---------------------------------------------------------------------------- // KeyViewAx // // wxAccessible object providing information for KeyView. // ---------------------------------------------------------------------------- class KeyViewAx final : public WindowAccessible { public: KeyViewAx(KeyView *view); void SetCurrentLine(int row); void ListUpdated(); bool LineToId(int line, int & childId); bool IdToLine(int childId, int & line); // Can return either a child object, or an integer // representing the child element, starting from 1. wxAccStatus HitTest(const wxPoint & pt, int *childId, wxAccessible **childObject) override; // Retrieves the address of an IDispatch interface for the specified child. // All objects must support this property. wxAccStatus GetChild(int childId, wxAccessible **child) override; // Gets the number of children. wxAccStatus GetChildCount(int *childCount) override; // Gets the default action for this object (0) or > 0 (the action for a child). // Return wxACC_OK even if there is no action. actionName is the action, or the empty // string if there is no action. // The retrieved string describes the action that is performed on an object, // not what the object does as a result. For example, a toolbar button that prints // a document has a default action of "Press" rather than "Prints the current document." wxAccStatus GetDefaultAction(int childId, wxString *actionName) override; // Returns the description for this object or a child. wxAccStatus GetDescription(int childId, wxString *description) override; // Gets the window with the keyboard focus. // If childId is 0 and child is NULL, no object in // this subhierarchy has the focus. // If this object has the focus, child should be 'this'. wxAccStatus GetFocus(int *childId, wxAccessible **child) override; // Returns help text for this object or a child, similar to tooltip text. wxAccStatus GetHelpText(int childId, wxString *helpText) override; // Returns the keyboard shortcut for this object or child. // Return e.g. ALT+K wxAccStatus GetKeyboardShortcut(int childId, wxString *shortcut) override; // Returns the rectangle for this object (id = 0) or a child element (id > 0). // rect is in screen coordinates. wxAccStatus GetLocation(wxRect & rect, int elementId) override; // Navigates from fromId to toId/toObject. wxAccStatus Navigate(wxNavDir navDir, int fromId, int *toId, wxAccessible **toObject) override; // Gets the name of the specified object. wxAccStatus GetName(int childId, wxString *name) override; // Gets the parent, or NULL. wxAccStatus GetParent(wxAccessible **parent) override; // Returns a role constant. wxAccStatus GetRole(int childId, wxAccRole *role) override; // Gets a variant representing the selected children // of this object. // Acceptable values: // - a null variant (IsNull() returns TRUE) // - a list variant (GetType() == wxT("list")) // - an integer representing the selected child element, // or 0 if this object is selected (GetType() == wxT("long")) // - a "void*" pointer to a wxAccessible child object wxAccStatus GetSelections(wxVariant *selections) override; // Returns a state constant. wxAccStatus GetState(int childId, long *state) override; // Returns a localized string representing the value for the object // or child. wxAccStatus GetValue(int childId, wxString *strValue) override; #if defined(__WXMAC__) // Selects the object or child. wxAccStatus Select(int childId, wxAccSelectionFlags selectFlags) override; #endif private: KeyView *mView; int mLastId; }; #endif // Various drawing constants #define KV_BITMAP_SIZE 16 #define KV_LEFT_MARGIN 2 #define KV_COLUMN_SPACER 5 #define KV_VSCROLL_WIDTH 16 /* figure this out automatically? */ // Define the KeyNode arrays // Define the event table BEGIN_EVENT_TABLE(KeyView, wxVListBox) EVT_LEFT_DOWN(KeyView::OnLeftDown) EVT_KEY_DOWN(KeyView::OnKeyDown) EVT_LISTBOX(wxID_ANY, KeyView::OnSelected) EVT_SET_FOCUS(KeyView::OnSetFocus) EVT_KILL_FOCUS(KeyView::OnKillFocus) EVT_SIZE(KeyView::OnSize) EVT_SCROLLWIN(KeyView::OnScroll) END_EVENT_TABLE(); static wxString CommandTranslated = "Command"; // ============================================================================ // KeyView class // ============================================================================ KeyView::KeyView(wxWindow *parent, wxWindowID id, const wxPoint & pos, const wxSize & size) : wxVListBox(parent, id, pos, size, wxBORDER_THEME | wxHSCROLL | wxVSCROLL), mScrollX(0), mWidth(0) { #if wxUSE_ACCESSIBILITY // Create and set accessibility object SetAccessible(mAx = safenew KeyViewAx(this)); #endif SetMinSize({-1, 150}); // The default view mViewType = ViewByTree; // Calculate measurements used for columns and scrolling RecalcExtents(); } KeyView::~KeyView() { } // // Returns the index of the selected node // int KeyView::GetSelected() const { return LineToIndex(GetSelection()); } // // Returns the name of the control // wxString KeyView::GetName() const { // Just forward request return wxVListBox::GetName(); } // // Returns the label for the given index // wxString KeyView::GetLabel(int index) const { // Make sure index is valid if (index < 0 || index >= (int) mNodes.size()) { wxASSERT(false); return wxEmptyString; } return mNodes[index].label; } // // Returns the prefix (if available) prepended to the label for the given index // wxString KeyView::GetFullLabel(int index) const { // Make sure index is valid if (index < 0 || index >= (int) mNodes.size()) { wxASSERT(false); return wxEmptyString; } // Cache the node and label const KeyNode & node = mNodes[index]; wxString label = node.label; // Prepend the prefix if available if (!node.prefix.empty()) { label = node.prefix + wxT(" - ") + label; } return label; } // // Returns the index for the given name // int KeyView::GetIndexByName(const CommandID & name) const { int cnt = (int) mNodes.size(); // Search the nodes for the key for (int i = 0; i < cnt; i++) { if (name == mNodes[i].name) { return mNodes[i].index; } } return wxNOT_FOUND; } // // Returns the command manager name for the given index // CommandID KeyView::GetName(int index) const { // Make sure index is valid if (index < 0 || index >= (int) mNodes.size()) { wxASSERT(false); return {}; } return mNodes[index].name; } // // Returns the command manager index for the given key combination // CommandID KeyView::GetNameByKey(const NormalizedKeyString & key) const { int cnt = (int) mNodes.size(); // Search the nodes for the key for (int i = 0; i < cnt; i++) { if ( key == mNodes[i].key ) { return mNodes[i].name; } } return {}; } // // Returns the index for the given key // int KeyView::GetIndexByKey(const NormalizedKeyString & key) const { int cnt = (int) mNodes.size(); // Search the nodes for the key for (int i = 0; i < cnt; i++) { if ( key == mNodes[i].key ) { return mNodes[i].index; } } return wxNOT_FOUND; } // // Returns the key for the given index // NormalizedKeyString KeyView::GetKey(int index) const { // Make sure index is valid if (index < 0 || index >= (int) mNodes.size()) { wxASSERT(false); return {}; } return mNodes[index].key; } // // Use to determine if a key can be assigned to the given index // bool KeyView::CanSetKey(int index) const { // Make sure index is valid if (index < 0 || index >= (int) mNodes.size()) { wxASSERT(false); return false; } // Parents can't be assigned keys return !mNodes[index].isparent; } // // Sets the key for the given index // bool KeyView::SetKey(int index, const NormalizedKeyString & key) { // Make sure index is valid if (index < 0 || index >= (int) mNodes.size()) { wxASSERT(false); return false; } // Cache the node KeyNode & node = mNodes[index]; // Do not allow setting keys on branches if (node.isparent) { return false; } // Set the NEW key node.key = key; // Check to see if the key column needs to be expanded int x, y; GetTextExtent(node.key.Display(), &x, &y); if (x > mKeyWidth || y > mLineHeight) { // New key is wider than column so recalc extents (will refresh view) RecalcExtents(); return true; } // Refresh the view lines RefreshAll(); return true; } // // Sets the key for the given name // bool KeyView::SetKeyByName(const CommandID & name, const NormalizedKeyString & key) { int index = GetIndexByName(name); // Bail is the name wasn't found if (index == wxNOT_FOUND) { return false; } // Go set the key return SetKey(index, key); } // // Sets the view type // void KeyView::SetView(ViewByType type) { int index = LineToIndex(GetSelection()); // Handle an existing selection if (index != wxNOT_FOUND) { // Cache the currently selected node KeyNode & node = mNodes[index]; // Expand branches if switching to Tree view and a line // is currently selected if (type == ViewByTree) { // Cache the node's depth int depth = node.depth; // Search for its parents, setting each one as open for (int i = node.index - 1; i >= 0 && depth > 1; i--) { if (mNodes[i].depth < depth) { mNodes[i].isopen = true; depth = mNodes[i].depth; } } } } // Unselect any currently selected line...do even if none selected SelectNode(-1); // Save NEW type mViewType = type; // Refresh the view lines RefreshLines(); // Reselect old node (if possible) if (index != wxNOT_FOUND) { SelectNode(index); } // ensure that a node is selected so that when the keyview is the focus, // this is indicated visually, and the Narrator screen reader reads it. if ((GetSelection() == wxNOT_FOUND)) { SelectNode(LineToIndex(0)); } return; } // // Sets the filter // void KeyView::SetFilter(const wxString & filter) { int index = LineToIndex(GetSelection()); // Unselect any currently selected line...do even if none selected SelectNode(-1); // Save the filter mFilter = filter.Lower(); // Refresh the view lines RefreshLines(); // Reselect old node (if possible) if (index != wxNOT_FOUND) { SelectNode(index); } // ensure that a node is selected so that when the keyview is the focus, // this is indicated visually, and the Narrator screen reader reads it. if ((GetSelection() == wxNOT_FOUND)) { SelectNode(LineToIndex(0)); } } // // Expand all branches // void KeyView::ExpandAll() { int cnt = (int) mNodes.size(); // Set all parent nodes to open for (int i = 0; i < cnt; i++) { KeyNode & node = mNodes[i]; if (node.isparent) { node.isopen = true; } } RefreshLines(); } // // Collapse all branches // void KeyView::CollapseAll() { int cnt = (int) mNodes.size(); // Set all parent nodes to closed for (int i = 0; i < cnt; i++) { KeyNode & node = mNodes[i]; if (node.isparent) { node.isopen = false; } } RefreshLines(); } // // Recalculate the measurements used for columns and scrolling // void KeyView::RecalcExtents() { // Reset mLineHeight = 0; mCommandWidth = 0; mKeyWidth = 0; // Examine all nodes int cnt = (int) mNodes.size(); for (int i = 0; i < cnt; i++) { KeyNode & node = mNodes[i]; int x, y; if (node.iscat) { // Measure the category GetTextExtent(node.category, &x, &y); } else if (node.ispfx) { // Measure the prefix GetTextExtent(node.prefix, &x, &y); } else { // Measure the key GetTextExtent(node.key.Display(), &x, &y); mLineHeight = wxMax(mLineHeight, y); mKeyWidth = wxMax(mKeyWidth, x); // Prepend prefix for view types other than tree wxString label = node.label; if (mViewType != ViewByTree && !node.prefix.empty()) { label = node.prefix + wxT(" - ") + label; } // Measure the label GetTextExtent(label, &x, &y); } // Finish calc for command column mLineHeight = wxMax(mLineHeight, y); mCommandWidth = wxMax(mCommandWidth, x); } // Update horizontal scrollbar UpdateHScroll(); } // // Update the horizontal scrollbar or remove it if not needed // void KeyView::UpdateHScroll() { // Get the internal dimensions of the view wxRect r = GetClientRect(); // Calculate the full line width mWidth = KV_LEFT_MARGIN + mKeyWidth + KV_COLUMN_SPACER + mCommandWidth + KV_VSCROLL_WIDTH; // Retrieve the current horizontal scroll amount mScrollX = GetScrollPos(wxHORIZONTAL); if (mWidth <= r.GetWidth()) { // Remove the scrollbar if it will fit within client width SetScrollbar(wxHORIZONTAL, 0, 0, 0); } else { // Set scrollbar metrics SetScrollbar(wxHORIZONTAL, mScrollX, r.GetWidth(), mWidth); } // Refresh the entire view RefreshAll(); } // // Process a NEW set of bindings // void KeyView::RefreshBindings(const CommandIDs & names, const TranslatableStrings & categories, const TranslatableStrings & prefixes, const TranslatableStrings & labels, const std::vector & keys, bool bSort ) { // Start clean mNodes.clear(); // Same as in RecalcExtents() but do it inline mLineHeight = 0; mKeyWidth = 0; mCommandWidth = 0; wxString lastcat; wxString lastpfx; int nodecnt = 0; int depth = 1; bool incat = false; bool inpfx = false; // lookup translation once only CommandTranslated = _("Command"); // Examine all names...all arrays passed have the same indexes int cnt = (int) names.size(); for (int i = 0; i < cnt; i++) { auto name = names[i]; int x, y; // Remove any menu code from the category and prefix wxString cat = categories[i].Translation(); wxString pfx = prefixes[i].Translation(); // Append "Menu" this node is for a menu title if (cat != CommandTranslated) { cat.Append(wxT(" ")); cat += _("Menu"); } // Process a NEW category if (cat != lastcat) { // A NEW category always finishes any current subtree if (inpfx) { // Back to category level depth--; inpfx = false; } // Only time this is not true is during the first iteration if (incat) { // Back to root level depth--; incat = false; } // Remember for next iteration lastcat = cat; // Add a NEW category node if (!cat.empty()) { KeyNode node; // Fill in the node info node.name = CommandID{}; // don't associate branches with a command node.category = cat; node.prefix = pfx; node.label = cat; node.index = nodecnt++; node.iscat = true; node.isparent = true; node.depth = depth++; node.isopen = true; // Add it to the tree mNodes.push_back(node); incat = true; // Measure category GetTextExtent(cat, &x, &y); mLineHeight = wxMax(mLineHeight, y); mCommandWidth = wxMax(mCommandWidth, x); } } // Process a NEW prefix if (pfx != lastpfx) { // Done with prefix branch if (inpfx) { depth--; inpfx = false; } // Remember for next iteration lastpfx = pfx; // Add a NEW prefix node if (!pfx.empty()) { KeyNode node; // Fill in the node info node.name = CommandID{}; // don't associate branches with a command node.category = cat; node.prefix = pfx; node.label = pfx; node.index = nodecnt++; node.ispfx = true; node.isparent = true; node.depth = depth++; node.isopen = true; // Add it to the tree mNodes.push_back(node); inpfx = true; } } // Add the key entry KeyNode node; node.category = cat; node.prefix = pfx; // Labels for undo and redo change according to the last command // which can be undone/redone, so give them a special check in order // not to confuse users if (name == wxT("Undo")) { node.label = _("Undo"); } else if (name == wxT("Redo")) { node.label = _("Redo"); } else { auto label = labels[i]; node.label = label.Strip().Translation(); } // Fill in remaining info node.name = name; node.key = keys[i]; node.index = nodecnt++; node.depth = depth; // Add it to the tree mNodes.push_back(node); // Measure key GetTextExtent(node.key.Display(), &x, &y); mLineHeight = wxMax(mLineHeight, y); mKeyWidth = wxMax(mKeyWidth, x); // Prepend prefix for all view types to determine maximum // column widths wxString label = node.label; if (!node.prefix.empty()) { label = node.prefix + wxT(" - ") + label; } // Measure label GetTextExtent(label, &x, &y); mLineHeight = wxMax(mLineHeight, y); mCommandWidth = wxMax(mCommandWidth, x); } #if 0 // For debugging for (int j = 0; j < mNodes.size(); j++) { KeyNode & node = mNodes[j]; wxLogDebug(wxT("NODE line %4d index %4d depth %1d open %1d parent %1d cat %1d pfx %1d name %s STR %s | %s | %s"), node.line, node.index, node.depth, node.isopen, node.isparent, node.iscat, node.ispfx, node.name, node.category, node.prefix, node.label); } #endif // Update horizontal scrollbar UpdateHScroll(); // Refresh the view lines RefreshLines(bSort); // Set the selected node if we've just reprepared the list and nothing was selected. if ((GetSelection()==wxNOT_FOUND) && bSort ) { SelectNode(LineToIndex(0)); } } // // Refresh the list of lines within the current view // void KeyView::RefreshLines(bool bSort) { int cnt = (int) mNodes.size(); int linecnt = 0; mLines.clear(); // Process a filter if one is set if (!mFilter.empty()) { // Examine all nodes for (int i = 0; i < cnt; i++) { KeyNode & node = mNodes[i]; // Reset line number node.line = wxNOT_FOUND; // Search columns based on view type wxString searchit; switch (mViewType) { // The x"01" separator is used to prevent finding a // match comprising the end of the label and beginning // of the key. It was chosen since it's not very likely // to appear in the filter itself. case ViewByTree: searchit = node.label.Lower() + wxT("\01x") + node.key.Display().Lower(); break; case ViewByName: searchit = node.label.Lower(); break; case ViewByKey: searchit = node.key.Display().Lower(); break; } if (searchit.Find(mFilter) == wxNOT_FOUND) { // Not found so continue to next node continue; } // For the Key View, if the filter is a single character, // then it has to be the last character in the searchit string, // and be preceded by nothing or +. if ((mViewType == ViewByKey) && (mFilter.length() == 1) && (mFilter != searchit.Last() || ((searchit.length() > 1) && ((wxString)(searchit.GetChar(searchit.length() - 2)) != wxT("+"))))) { // Not suitable so continue to next node continue; } // For tree view, we must make sure all parent nodes are included // whether they match the filter or not. if (mViewType == ViewByTree) { std::vector queue; int depth = node.depth; // This node is a category or prefix node, so always mark them // as open. // // What this is really doing is resolving a situation where the // the filter matches a parent node and nothing underneath. In // this case, the node would never be marked as open. if (node.isparent) { node.isopen = true; } // Examine siblings until a parent is found. for (int j = node.index - 1; j >= 0 && depth > 0; j--) { // Found a parent if (mNodes[j].depth < depth) { // Examine all previously added nodes to see if this nodes // ancestors need to be added prior to adding this node. bool found = false; for (int k = (int) mLines.size() - 1; k >= 0; k--) { // The node indexes match, so we've found the parent of the // child node. if (mLines[k]->index == mNodes[j].index) { found = true; break; } } // The parent wasn't found so remember it for later // addition. Can't add directory to mLines here since // they will wind up in reverse order. if (!found) { queue.push_back(&mNodes[j]); } // Traverse up the tree depth = mNodes[j].depth; } } // Add any queues nodes to list. This will all be // parent nodes, so mark them as open. for (int j = (int) queue.size() - 1; j >= 0; j--) { queue[j]->isopen = true; queue[j]->line = linecnt++; mLines.push_back(queue[j]); } } // Finally add the child node node.line = linecnt++; mLines.push_back(&node); } } else { // Examine all nodes - non-filtered for (int i = 0; i < cnt; i++) { KeyNode & node = mNodes[i]; // Reset line number node.line = wxNOT_FOUND; // Node is either a category or prefix if (node.isparent) { // Only need to do this for tree views if (mViewType != ViewByTree) { continue; } // Add the node node.line = linecnt++; mLines.push_back(&node); // If this node is not open, then skip all of its descendants if (!node.isopen) { bool iscat = node.iscat; bool ispfx = node.ispfx; // Skip nodes until we find a node that has a different // category or prefix while (i < cnt) { KeyNode & skip = mNodes[i]; if ((iscat && skip.category != node.category) || (ispfx && skip.prefix != node.prefix)) { break; } // Bump to next node i++; } // Index is pointing to the node that was different or // past the end, so back off to last node of this branch. i--; } continue; } // Add child node to list node.line = linecnt++; mLines.push_back(&node); } } // Sorting is costly. If bSort is false, we do not have to sort. // bSort false means we know that the list will be updated again before // the user needs to see it. if( bSort ) { //To see how many lines are being sorted (and how often). //wxLogDebug("Sorting %i lines for type %i", mLines.size(), mViewType); // Speed up the comparison function used in sorting // by only translating this string once. CommandTranslated = _("Command"); // Sort list based on type switch (mViewType) { case ViewByTree: std::sort(mLines.begin(), mLines.end(), CmpKeyNodeByTree); break; case ViewByName: std::sort(mLines.begin(), mLines.end(), CmpKeyNodeByName); break; case ViewByKey: std::sort(mLines.begin(), mLines.end(), CmpKeyNodeByKey); break; } } // Now, reassign the line numbers for (int i = 0; i < (int) mLines.size(); i++) { mLines[i]->line = i; } #if 0 // For debugging for (int j = 0; j < mLines.size(); j++) { KeyNode & node = *mLines[j]; wxLogDebug(wxT("LINE line %4d index %4d depth %1d open %1d parent %1d cat %1d pfx %1d name %s STR %s | %s | %s"), node.line, node.index, node.depth, node.isopen, node.isparent, node.iscat, node.ispfx, node.name, node.category, node.prefix, node.label); } #endif // Tell listbox the NEW count and refresh the entire view SetItemCount(mLines.size()); RefreshAll(); #if wxUSE_ACCESSIBILITY // Let accessibility know that the list has changed if( bSort ) mAx->ListUpdated(); #endif } // // Select a node // // Parameter can be wxNOT_FOUND to clear selection // void KeyView::SelectNode(int index) { int line = IndexToLine(index); // Tell the listbox to select the line SetSelection(line); #if wxUSE_ACCESSIBILITY // And accessibility mAx->SetCurrentLine(line); #endif // Always send an event to let parent know of selection change // // Must do this ourselves because we want to send notifications // even if there isn't an item selected and SendSelectedEvent() // doesn't allow sending an event for indexes not in the listbox. wxCommandEvent event(wxEVT_COMMAND_LISTBOX_SELECTED, GetId()); event.SetEventObject(this); event.SetInt(line); (void)GetEventHandler()->ProcessEvent(event); } // // Converts a line index to a node index // int KeyView::LineToIndex(int line) const { if (line < 0 || line >= (int) mLines.size()) { return wxNOT_FOUND; } return mLines[line]->index; } // // Converts a node index to a line index // int KeyView::IndexToLine(int index) const { if (index < 0 || index >= (int) mNodes.size()) { return wxNOT_FOUND; } return mNodes[index].line; } // // Draw the background for a given line // // This is called by the listbox when it needs to redraw the view. // void KeyView::OnDrawBackground(wxDC & dc, const wxRect & rect, size_t line) const { const KeyNode *node = mLines[line]; wxRect r = rect; wxRect r2 = rect; // for just the key shortcut. wxCoord indent = 0; // When in tree view mode, each younger branch gets indented by the // width of the open/close bitmaps if (mViewType == ViewByTree) { indent += mKeyWidth + KV_COLUMN_SPACER + node->depth * KV_BITMAP_SIZE; } // Offset left side by the indentation (if any) and scroll amounts r.x = indent - mScrollX; r2.x = -mScrollX; // If the line width is less than the client width, then we want to // extend the background to the right edge of the client view. Otherwise, // go all the way to the end of the line width...this will draw past the // right edge, but that's what we want. r.width = wxMax(mWidth, r.width); r2.width = mKeyWidth; // Selected lines get a solid background if (IsSelected(line)) { if (FindFocus() == this) { // Focused lines get highlighted background dc.SetPen(*wxTRANSPARENT_PEN); dc.SetBrush(wxBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT))); dc.DrawRectangle(r); // and they also get a dotted focus rect. This could just be left out. // The focus rect does very little for us, as it is the same size as the // rectangle itself. Consequently for themes that have black text it // disappears. But on HiContrast you do get a dotted green border which // may have some utility. AColor::DrawFocus(dc, r); if (mViewType == ViewByTree){ dc.DrawRectangle(r2); AColor::DrawFocus(dc, r2); } } else { // Non focused lines get a light background dc.SetPen(*wxTRANSPARENT_PEN); dc.SetBrush(wxBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE))); dc.DrawRectangle(r); if (mViewType == ViewByTree) dc.DrawRectangle(r2); } } else { // Non-selected lines get a thin bottom border dc.SetPen(wxColour(240, 240, 240)); AColor::Line(dc, r.GetLeft(), r.GetBottom(), r.GetRight(), r.GetBottom()); if (mViewType == ViewByTree ) AColor::Line(dc, r2.GetLeft(), r2.GetBottom(), r2.GetRight(), r2.GetBottom()); } } // // Draw a line // // This is called by the listbox when it needs to redraw the view. // void KeyView::OnDrawItem(wxDC & dc, const wxRect & rect, size_t line) const { const KeyNode *node = mLines[line]; wxString label = node->label; // Make sure the DC has a valid font dc.SetFont(GetFont()); // Set the text color based on selection and focus if (IsSelected(line) && FindFocus() == this) { dc.SetTextForeground(wxSystemSettings::GetColour(wxSYS_COLOUR_LISTBOXHIGHLIGHTTEXT)); } else { dc.SetTextForeground(wxSystemSettings::GetColour(wxSYS_COLOUR_LISTBOXTEXT)); } // Tree views get bitmaps if (mViewType == ViewByTree) { // Adjust left edge to account for scrolling wxCoord x = rect.x - mScrollX; if (node->iscat || node->ispfx) { wxCoord bx = x + mKeyWidth + KV_COLUMN_SPACER; wxCoord by = rect.y; if (node->ispfx) { bx += KV_BITMAP_SIZE; } dc.SetBrush(*wxTRANSPARENT_BRUSH); dc.SetPen(*wxBLACK_PEN); dc.DrawRectangle(bx + 3, by + 4, 9, 9); if (node->isopen) { AColor::Line(dc, bx + 5, by + 8, bx + 9, by + 8); } else { AColor::Line(dc, bx + 7, by + 6, bx + 7, by + 10); AColor::Line(dc, bx + 5, by + 8, bx + 9, by + 8); } } // Indent text x += KV_LEFT_MARGIN; // Draw the key and command columns dc.DrawText(node->key.Display(), x , rect.y); dc.DrawText(label, x + mKeyWidth + KV_COLUMN_SPACER + node->depth * KV_BITMAP_SIZE, rect.y); } else { // Adjust left edge by margin and account for scrolling wxCoord x = rect.x + KV_LEFT_MARGIN - mScrollX; // Prepend prefix if available if (!node->prefix.empty()) { label = node->prefix + wxT(" - ") + label; } // don't swap the columns based on view type if((mViewType == ViewByName) || (mViewType == ViewByKey)) { // Draw key columnd and then command column dc.DrawText(node->key.Display(), x, rect.y); dc.DrawText(label, x + mKeyWidth + KV_COLUMN_SPACER, rect.y); } } return; } // // Provide the height of the given line // // This is called by the listbox when it needs to redraw the view. // wxCoord KeyView::OnMeasureItem(size_t WXUNUSED(line)) const { // All lines are of equal height // // (add a magic 1 for decenders...looks better...not required) return mLineHeight + 1; } // // Handle the wxEVT_LISTBOX event // void KeyView::OnSelected(wxCommandEvent & event) { // Allow further processing event.Skip(); #if wxUSE_ACCESSIBILITY // Tell accessibility of the change mAx->SetCurrentLine(event.GetInt()); #endif } // // Handle the wxEVT_SET_FOCUS event // void KeyView::OnSetFocus(wxFocusEvent & event) { // Allow further processing event.Skip(); // Refresh the selected line to pull in any changes while // focus was away...like when setting a NEW key value. This // will also refresh the visual (highlighted) state. if (GetSelection() != wxNOT_FOUND) { RefreshRow(GetSelection()); } #if wxUSE_ACCESSIBILITY // Tell accessibility of the change mAx->SetCurrentLine(GetSelection()); #endif } // // Handle the wxEVT_KILL_FOCUS event // void KeyView::OnKillFocus(wxFocusEvent & event) { // Allow further processing event.Skip(); // Refresh the selected line to adjust visual highlighting. if (GetSelection() != wxNOT_FOUND) { RefreshRow(GetSelection()); } } // // Handle the wxEVT_SIZE event // void KeyView::OnSize(wxSizeEvent & WXUNUSED(event)) { // Update horizontal scrollbar UpdateHScroll(); } // // Handle the wxEVT_SCROLL event // void KeyView::OnScroll(wxScrollWinEvent & event) { // We only care bout the horizontal scrollbar. if (event.GetOrientation() != wxHORIZONTAL) { // Allow further processing event.Skip(); return; } // Get NEW scroll position and scroll the view mScrollX = event.GetPosition(); SetScrollPos(wxHORIZONTAL, mScrollX); // Refresh the entire view RefreshAll(); } // // Handle the wxEVT_KEY_DOWN event // void KeyView::OnKeyDown(wxKeyEvent & event) { int line = GetSelection(); int keycode = event.GetKeyCode(); switch (keycode) { // The LEFT key moves selection to parent or collapses selected // node if it is expanded. case WXK_LEFT: { // Nothing selected...nothing to do if (line == wxNOT_FOUND) { // Allow further processing event.Skip(); break; } KeyNode *node = mLines[line]; // Collapse the node if it is open if (node->isopen) { // No longer open node->isopen = false; // Don't want the view to scroll vertically, so remember the current // top line. size_t topline = GetVisibleBegin(); // Refresh the view now that the number of lines have changed RefreshLines(); // Reset the original top line ScrollToRow(topline); // And make sure current line is still selected SelectNode(LineToIndex(line)); } else { // Move selection to the parent of this node for (int i = line - 1; i >= 0; i--) { // Found the parent if (mLines[i]->depth < node->depth) { // So select it SelectNode(LineToIndex(i)); break; } } } // Further processing of the event is not wanted // (we didn't call event.Skip() } break; // The RIGHT key moves the selection to the first child or expands // the node if it is a parent. case WXK_RIGHT: { // Nothing selected...nothing to do if (line == wxNOT_FOUND) { // Allow further processing event.Skip(); break; } KeyNode *node = mLines[line]; // Only want parent nodes if (node->isparent) { // It is open so move select to first child if (node->isopen) { // But only if there is one if (line < (int) mLines.size() - 1) { SelectNode(LineToIndex(line + 1)); } } else { // Node is now open node->isopen = true; // Don't want the view to scroll vertically, so remember the current // top line. size_t topline = GetVisibleBegin(); // Refresh the view now that the number of lines have changed RefreshLines(); // Reset the original top line ScrollToRow(topline); // And make sure current line is still selected SelectNode(LineToIndex(line)); } } // Further processing of the event is not wanted // (we didn't call event.Skip() } break; // Move selection to next node whose 1st character matches // the keycode default: { int cnt = (int) mLines.size(); bool found = false; // Search the entire list if none is currently selected if (line == wxNOT_FOUND) { line = cnt; } else { // Search from the node following the current one for (int i = line + 1; i < cnt; i++) { wxString label; // Get the string to search based on view type if (mViewType == ViewByTree) { label = GetLabel(LineToIndex(i)); } else if (mViewType == ViewByName) { label = GetFullLabel(LineToIndex(i)); } else if (mViewType == ViewByKey) { label = GetKey(LineToIndex(i)).Display(); } // Move selection if they match if (label.Left(1).IsSameAs(keycode, false)) { SelectNode(LineToIndex(i)); found = true; break; } } } // A match wasn't found if (!found) { // So scan from the start of the list to the current node for (int i = 0; i < line; i++) { wxString label; // Get the string to search based on view type if (mViewType == ViewByTree) { label = GetLabel(LineToIndex(i)); } else if (mViewType == ViewByName) { label = GetFullLabel(LineToIndex(i)); } else if (mViewType == ViewByKey) { label = GetKey(LineToIndex(i)).Display(); } // Move selection if they match if (label.Left(1).IsSameAs(keycode, false)) { SelectNode(LineToIndex(i)); found = true; break; } } } // A node wasn't found so allow further processing if (!found) { event.Skip(); } // Otherwise, further processing of the event is not wanted // (we didn't call event.Skip() } } } // // Handle the wxEVT_LEFT_DOWN event // void KeyView::OnLeftDown(wxMouseEvent & event) { // Only check if for tree view if (mViewType != ViewByTree) { // Allow further processing (important for focus handling) event.Skip(); return; } // Get the mouse position when the button was pressed wxPoint pos = event.GetPosition(); // And see if it was on a line within the view int line = VirtualHitTest(pos.y); // It was on a line if (line != wxNOT_FOUND) { KeyNode *node = mLines[line]; // Toggle the open state if this is a parent node if (node->isparent) { // Toggle state node->isopen = !node->isopen; // Don't want the view to scroll vertically, so remember the current // top line. size_t topline = GetVisibleBegin(); // Refresh the view now that the number of lines have changed RefreshLines(); // Reset the original top line ScrollToRow(topline); // And make sure current line is still selected SelectNode(LineToIndex(line)); // If a node is closed near the bottom of the tree, // the node may move down, and no longer be at the // mouse pointer position. So don't allow further processing as this // selects the line at the mouse position. Bug 1723. // So we need to set the focus. SetFocus(); return; } } // Allow further processing (important for focus handling) event.Skip(); } // // Sort compare function for tree view // // We want to leave the "menu" nodes alone as they are in the // order as they appear in the menus. But, we want to sort the // "command" nodes. // // To accomplish this, we prepend each label with its line number // (in hex) for "menu" nodes. This ensures they will remain in // their original order. // // We prefix all "command" nodes with "ffffffff" (highest hex value) // to allow the sort to reorder them as needed. // bool KeyView::CmpKeyNodeByTree(KeyNode *t1, KeyNode *t2) { unsigned int k1UInt= 0xffffffff; unsigned int k2UInt= 0xffffffff; // This is a "command" node if its category is "Command" // and it is a child of the "Command" category. This latter // test ensures that the "Command" parent will be handled // as a "menu" node and remain at the bottom of the list. if (t1->category != CommandTranslated || t1->isparent) k1UInt = (unsigned int) t1->line; // See above for explanation if (t2->category != CommandTranslated || t2->isparent) k2UInt = (unsigned int) t2->line; if( k1UInt < k2UInt ) return true; if( k1UInt > k2UInt ) return false; return ( t1->label < t2->label ); } // // Sort compare function for command view // // Nothing special here, just a standard ascending sort. // bool KeyView::CmpKeyNodeByName(KeyNode *t1, KeyNode *t2) { wxString k1 = t1->label; wxString k2 = t2->label; // Prepend prefix if available if (!t1->prefix.empty()) { k1 = t1->prefix + wxT(" - ") + k1; } // Prepend prefix if available if (!t2->prefix.empty()) { k2 = t2->prefix + wxT(" - ") + k2; } return (k1 < k2); } // // Sort compare function for key view // // We want all nodes with key assignments to appear in ascending order // at the top of the list and all nodes without assignment to appear in // ascending order at the bottom of the list. // // We accomplish this by prefixing all non-assigned entries with 0xff. // This will force them to the end, but still allow them to be sorted in // ascending order. // // The assigned entries simply get sorted as normal. // bool KeyView::CmpKeyNodeByKey(KeyNode *t1, KeyNode *t2) { wxString k1 = t1->key.Display(); wxString k2 = t2->key.Display(); // Left node is unassigned, so prefix it if(k1.empty()) { k1 = wxT("\xff"); } // Right node is unassigned, so prefix it if(k2.empty()) { k2 = wxT("\xff"); } // Add prefix if available if (!t1->prefix.empty()) { k1 += t1->prefix + wxT(" - "); } // Add prefix if available if (!t2->prefix.empty()) { k2 += t2->prefix + wxT(" - "); } // Add labels k1 += t1->label; k2 += t2->label; return (k1 < k2); } #if wxUSE_ACCESSIBILITY // // Return parenthood state of line // bool KeyView::HasChildren(int line) { // Make sure line is valid if (line < 0 || line >= (int) mLines.size()) { wxASSERT(false); return false; } return mLines[line]->isparent; } // // Returns espanded/collapsed state of line // bool KeyView::IsExpanded(int line) { // Make sure line is valid if (line < 0 || line >= (int) mLines.size()) { wxASSERT(false); return false; } return mLines[line]->isopen; } // // Returns the height of the line // wxCoord KeyView::GetLineHeight(int line) { // Make sure line is valid if (line < 0 || line >= (int) mLines.size()) { wxASSERT(false); return 0; } return OnGetRowHeight(line); } // // Returns the value to be presented to accessibility // // Currently, the command and key are both provided. // wxString KeyView::GetValue(int line) { // Make sure line is valid if (line < 0 || line >= (int) mLines.size()) { wxASSERT(false); return wxEmptyString; } int index = LineToIndex(line); // Get the label and key values wxString value; if (mViewType == ViewByTree) { value = GetLabel(index); } else { value = GetFullLabel(index); } wxString key = GetKey(index).Display(); // Add the key if it isn't empty if (!key.empty()) { if (mViewType == ViewByKey) { value = key + wxT(" ") + value; } else { value = value + wxT(" ") + key; } } return value; } // // Returns the current view type // ViewByType KeyView::GetViewType() { return mViewType; } // ============================================================================ // Accessibility provider for the KeyView class // ============================================================================ KeyViewAx::KeyViewAx(KeyView *view) : WindowAccessible(view) { mView = view; mLastId = -1; } // // Send an event notification to accessibility that the view // has changed. // void KeyViewAx::ListUpdated() { NotifyEvent(wxACC_EVENT_OBJECT_REORDER, mView, wxOBJID_CLIENT, 0); } // // Inform accessibility a NEW line has been selected and/or a previously // selected line is being unselected // void KeyViewAx::SetCurrentLine(int line) { // Only send selection remove notification if a line was // previously selected if (mLastId != -1) { NotifyEvent(wxACC_EVENT_OBJECT_SELECTIONREMOVE, mView, wxOBJID_CLIENT, mLastId); } // Nothing is selected now mLastId = -1; // Just clearing selection if (line != wxNOT_FOUND) { // Convert line number to childId LineToId(line, mLastId); // Send notifications that the line has focus if (mView == wxWindow::FindFocus()) { NotifyEvent(wxACC_EVENT_OBJECT_FOCUS, mView, wxOBJID_CLIENT, mLastId); } // And is selected NotifyEvent(wxACC_EVENT_OBJECT_SELECTION, mView, wxOBJID_CLIENT, mLastId); } } // // Convert the childId to a line number and return FALSE if it // represents a child or TRUE if it a line // bool KeyViewAx::IdToLine(int childId, int & line) { if (childId == wxACC_SELF) { return false; } // Convert to line line = childId - 1; // Make sure id is valid if (line < 0 || line >= (int) mView->GetItemCount()) { // Indicate the control itself in this case return false; } return true; } // // Convert the line number to a childId. // bool KeyViewAx::LineToId(int line, int & childId) { // Make sure line is valid if (line < 0 || line >= (int) mView->GetItemCount()) { // Indicate the control itself in this case childId = wxACC_SELF; return false; } // Convert to line childId = line + 1; return true; } // Can return either a child object, or an integer // representing the child element, starting from 1. wxAccStatus KeyViewAx::HitTest(const wxPoint & pt, int *childId, wxAccessible **childObject) { // Just to be safe *childObject = NULL; wxPoint pos = mView->ScreenToClient(pt); // See if it's on a line within the view int line = mView->HitTest(pos); // It was on a line if (line != wxNOT_FOUND) { LineToId(line, *childId); return wxACC_OK; } // Let the base class handle it return wxACC_NOT_IMPLEMENTED; } // Retrieves the address of an IDispatch interface for the specified child. // All objects must support this property. wxAccStatus KeyViewAx::GetChild(int childId, wxAccessible** child) { if (childId == wxACC_SELF) { *child = this; } else { *child = NULL; } return wxACC_OK; } // Gets the number of children. wxAccStatus KeyViewAx::GetChildCount(int *childCount) { *childCount = (int) mView->GetItemCount(); return wxACC_OK; } // Gets the default action for this object (0) or > 0 (the action for a child). // Return wxACC_OK even if there is no action. actionName is the action, or the empty // string if there is no action. // The retrieved string describes the action that is performed on an object, // not what the object does as a result. For example, a toolbar button that prints // a document has a default action of "Press" rather than "Prints the current document." wxAccStatus KeyViewAx::GetDefaultAction(int WXUNUSED(childId), wxString *actionName) { actionName->clear(); return wxACC_OK; } // Returns the description for this object or a child. wxAccStatus KeyViewAx::GetDescription(int WXUNUSED(childId), wxString *description) { description->clear(); return wxACC_OK; } // Returns help text for this object or a child, similar to tooltip text. wxAccStatus KeyViewAx::GetHelpText(int WXUNUSED(childId), wxString *helpText) { helpText->clear(); return wxACC_OK; } // Returns the keyboard shortcut for this object or child. // Return e.g. ALT+K wxAccStatus KeyViewAx::GetKeyboardShortcut(int WXUNUSED(childId), wxString *shortcut) { shortcut->clear(); return wxACC_OK; } // Returns the rectangle for this object (id = 0) or a child element (id > 0). // rect is in screen coordinates. wxAccStatus KeyViewAx::GetLocation(wxRect & rect, int elementId) { int line; if (IdToLine(elementId, line)) { if (!mView->IsVisible(line)) { return wxACC_FAIL; } wxRect rectLine; rectLine.width = mView->GetClientSize().GetWidth(); // iterate over all visible lines for (int i = (int) mView->GetVisibleBegin(); i <= line; i++) { wxCoord hLine = mView->GetLineHeight(i); rectLine.height = hLine; rect = rectLine; wxPoint margins = mView->GetMargins(); rect.Deflate(margins.x, margins.y); rectLine.y += hLine; } rect.SetPosition(mView->ClientToScreen(rect.GetPosition())); } else { rect = mView->GetRect(); rect.SetPosition(mView->GetParent()->ClientToScreen(rect.GetPosition())); } return wxACC_OK; } wxAccStatus KeyViewAx::Navigate(wxNavDir WXUNUSED(navDir), int WXUNUSED(fromId), int *WXUNUSED(toId), wxAccessible **WXUNUSED(toObject)) { return wxACC_NOT_IMPLEMENTED; } // Gets the name of the specified object. wxAccStatus KeyViewAx::GetName(int childId, wxString *name) { int line; if (!IdToLine(childId, line)) { *name = mView->GetName(); } else { if (IdToLine(childId, line)) { *name = mView->GetValue(line); } } return wxACC_OK; } wxAccStatus KeyViewAx::GetParent(wxAccessible ** WXUNUSED(parent)) { return wxACC_NOT_IMPLEMENTED; } // Returns a role constant. wxAccStatus KeyViewAx::GetRole(int childId, wxAccRole *role) { if (childId == wxACC_SELF) { #if defined(__WXMSW__) *role = mView->GetViewType() == ViewByTree ? wxROLE_SYSTEM_OUTLINE : wxROLE_SYSTEM_LIST; #endif #if defined(__WXMAC__) *role = wxROLE_SYSTEM_GROUPING; #endif } else { #if defined(__WXMAC__) *role = wxROLE_SYSTEM_TEXT; #else *role = mView->GetViewType() == ViewByTree ? wxROLE_SYSTEM_OUTLINEITEM : wxROLE_SYSTEM_LISTITEM; #endif } return wxACC_OK; } // Gets a variant representing the selected children // of this object. // Acceptable values: // - a null variant (IsNull() returns TRUE) // - a list variant (GetType() == wxT("list")) // - an integer representing the selected child element, // or 0 if this object is selected (GetType() == wxT("long")) // - a "void*" pointer to a wxAccessible child object wxAccStatus KeyViewAx::GetSelections(wxVariant *selections) { int id; LineToId(mView->GetSelection(), id); *selections = (long) id; return wxACC_OK; } // Returns a state constant. wxAccStatus KeyViewAx::GetState(int childId, long *state) { int flag = wxACC_STATE_SYSTEM_FOCUSABLE; int line; if (!IdToLine(childId, line)) { *state = wxACC_STATE_SYSTEM_FOCUSABLE; // | //mView->FindFocus() == mView ? wxACC_STATE_SYSTEM_FOCUSED : 0; return wxACC_OK; } #if defined(__WXMSW__) int selected = mView->GetSelection(); flag |= wxACC_STATE_SYSTEM_SELECTABLE; if (line == selected) { flag |= wxACC_STATE_SYSTEM_FOCUSED | wxACC_STATE_SYSTEM_SELECTED; } if (mView->HasChildren(line)) { flag |= mView->IsExpanded(line) ? wxACC_STATE_SYSTEM_EXPANDED : wxACC_STATE_SYSTEM_COLLAPSED; } #endif #if defined(__WXMAC__1) if (mGrid->IsInSelection(row, col)) { flag |= wxACC_STATE_SYSTEM_SELECTED; } if (mGrid->GetGridCursorRow() == row && mGrid->GetGridCursorCol() == col) { flag |= wxACC_STATE_SYSTEM_FOCUSED; } if (mGrid->IsReadOnly(row, col)) { flag |= wxACC_STATE_SYSTEM_UNAVAILABLE; } #endif *state = flag; return wxACC_OK; } // Returns a localized string representing the value for the object // or child. wxAccStatus KeyViewAx::GetValue(int childId, wxString *strValue) { int line; strValue->clear(); if (!IdToLine(childId, line)) { return wxACC_NOT_IMPLEMENTED; } #if defined(__WXMSW__) if (mView->GetViewType() == ViewByTree) { KeyNode *node = mView->mLines[line]; strValue->Printf(wxT("%d"), node->depth - 1); } // Don't set a value for the other view types return wxACC_NOT_IMPLEMENTED; #endif #if defined(__WXMAC__) return GetName(childId, strValue); #endif } #if defined(__WXMAC__) // Selects the object or child. wxAccStatus KeyViewAx::Select(int childId, wxAccSelectionFlags selectFlags) { #if 0 int row; int col; if (GetRowCol(childId, row, col)) { if (selectFlags & wxACC_SEL_TAKESELECTION) { mGrid->SetGridCursor(row, col); } mGrid->SelectBlock(row, col, row, col, selectFlags & wxACC_SEL_ADDSELECTION); } #endif return wxACC_OK; } #endif // Gets the window with the keyboard focus. // If childId is 0 and child is NULL, no object in // this subhierarchy has the focus. // If this object has the focus, child should be 'this'. wxAccStatus KeyViewAx::GetFocus(int * WXUNUSED(childId), wxAccessible **child) { *child = this; return wxACC_OK; } #endif // wxUSE_ACCESSIBILITY