← Back to team overview

kicad-developers team mailing list archive

[PATCH] Refactor hotkeys editor dialog

 

Hi,

First step in getting the hotkey configuration inside the main 
preferences dialog is to refactor it a bit. This patch:

- Replaces the wxListCtrl with a wxTreeListCtrl, allowing expandable 
  categories in a future change.

- Cleans up the code to make HOTKEY_LIST_CTRL function a bit better on 
  its own.

- Migrates this dialog as well to TransferData{To,From}Window, use 
  matching TransferData{To,From}Control methods on HOTKEY_LIST_CTRL so 
  it is easy to embed.

Despite being replaced by a tree-type control, none of the behavior has 
been changed yet. The appearance changes slightly due to wxTreeListCtrl 
looking a bit different.

--
Chris

commit b1f867eebb7440551b8b2570fe7166348d632828
Author: Chris Pavlina <cpavlin1@xxxxxxxxxxxxxx>
Date:   Sun Jan 3 21:48:30 2016 -0500

    Refactor hotkeys editor

diff --git a/common/dialogs/dialog_hotkeys_editor.cpp b/common/dialogs/dialog_hotkeys_editor.cpp
index 0dfedf2..719acc8 100644
--- a/common/dialogs/dialog_hotkeys_editor.cpp
+++ b/common/dialogs/dialog_hotkeys_editor.cpp
@@ -1,11 +1,7 @@
-/**
- * @file dialog_hotkeys_editor.cpp
- */
-
 /*
  * This program source code file is part of KICAD, a free EDA CAD application.
  *
- * Copyright (C) 1992-2014 Kicad Developers, see CHANGELOG.TXT for contributors.
+ * Copyright (C) 1992-2016 Kicad Developers, see CHANGELOG.TXT for contributors.
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License
@@ -32,124 +28,64 @@
 #include <common.h>
 #include <confirm.h>
 
+#include <wx/dataview.h>
+
 #include <dialog_hotkeys_editor.h>
 
 
-HOTKEY_LIST_CTRL::HOTKEY_LIST_CTRL( wxWindow *aParent, struct EDA_HOTKEY_CONFIG* aSection ) :
-    wxListCtrl( aParent, wxID_ANY, wxDefaultPosition,
-                wxDefaultSize, wxLC_HRULES|wxLC_REPORT|wxLC_SINGLE_SEL|wxLC_VIRTUAL )
+class DIALOG_HOTKEY_CLIENT_DATA : public wxClientData
 {
-    m_sectionTag = aSection->m_SectionTag;
-    m_curEditingRow = -1;
+    EDA_HOTKEY m_hotkey;
+    wxString m_section_tag;
 
-    InsertColumn( 0, _( "Command" ) );
-    InsertColumn( 1, _( "Hotkey" ) );
+public:
+    DIALOG_HOTKEY_CLIENT_DATA( const EDA_HOTKEY& aHotkey, const wxString& aSectionTag )
+        : m_hotkey( aHotkey ), m_section_tag( aSectionTag ) {}
 
-    // Add a dummy hotkey_spec which is a header before each hotkey list
-    EDA_HOTKEY** hotkey_descr_list;
+    EDA_HOTKEY& GetHotkey() { return m_hotkey; }
+    wxString GetSectionTag() const { return m_section_tag; }
+};
 
-    // Add a copy of hotkeys to our list
-    for( hotkey_descr_list = aSection->m_HK_InfoList; *hotkey_descr_list; hotkey_descr_list++ )
-    {
-        EDA_HOTKEY* hotkey_descr = *hotkey_descr_list;
-        m_hotkeys.push_back( new EDA_HOTKEY( hotkey_descr ) );
-    }
 
-    // Set item count to hotkey size, this gets it to autoload the entries
-    SetItemCount( m_hotkeys.size() );
+HOTKEY_LIST_CTRL::HOTKEY_LIST_CTRL( wxWindow *aParent, const HOTKEYS_SECTIONS& aSections ) :
+    wxTreeListCtrl( aParent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTL_SINGLE ),
+    m_sections( aSections )
+{
+    AppendColumn( _( "Command" ) );
+    AppendColumn( _( "Hotkey" ) );
 
-    SetColumnWidth( 0, wxLIST_AUTOSIZE );
-    SetColumnWidth( 1, wxLIST_AUTOSIZE );
+    SetColumnWidth( 1, 100 );
 
     Bind( wxEVT_CHAR, &HOTKEY_LIST_CTRL::OnChar, this );
-    Bind( wxEVT_LIST_ITEM_SELECTED, &HOTKEY_LIST_CTRL::OnListItemSelected, this );
     Bind( wxEVT_SIZE, &HOTKEY_LIST_CTRL::OnSize, this );
 }
 
 
 void HOTKEY_LIST_CTRL::OnSize( wxSizeEvent& aEvent )
 {
-    recalculateColumns();
     aEvent.Skip();
 }
 
 
-void HOTKEY_LIST_CTRL::recalculateColumns()
-{
-    float totalLength = 0;
-    float scale = 0;
-
-    // Find max character length of first column
-    int maxInfoMsgLength = 0;
-
-    for( int i = 0; i < GetItemCount(); i++ )
-    {
-        int length = GetItemText( i, 0 ).Length();
-
-        if( length > maxInfoMsgLength )
-            maxInfoMsgLength = length;
-    }
-
-    // Find max character length of second column
-    int maxKeyCodeLength = 0;
-
-    for( int i = 0; i < GetItemCount(); i++ )
-    {
-        int length = GetItemText( i, 1 ).Length();
-        if( length > maxKeyCodeLength )
-            maxKeyCodeLength = length;
-    }
-
-    // Use the lengths of column texts to create a scale of the max list width
-    // to set the column widths
-    totalLength = maxInfoMsgLength + maxKeyCodeLength;
-
-    scale = (double) GetClientSize().x / totalLength;
-
-    SetColumnWidth( 0, int( maxInfoMsgLength*scale ) - 2 );
-    SetColumnWidth( 1, int( maxKeyCodeLength*scale ) );
-}
-
-
-void HOTKEY_LIST_CTRL::OnListItemSelected( wxListEvent& aEvent )
-{
-    m_curEditingRow = aEvent.GetIndex();
-}
-
-
 void HOTKEY_LIST_CTRL::DeselectRow( int aRow )
 {
-    SetItemState( aRow, 0, wxLIST_STATE_SELECTED );
-}
-
-
-wxString HOTKEY_LIST_CTRL::OnGetItemText( long aRow, long aColumn ) const
-{
-    EDA_HOTKEY* hotkey_descr = m_hotkeys[aRow];
-
-    if( aColumn == 0 )
-    {
-        return wxGetTranslation( hotkey_descr->m_InfoMsg );
-    }
-    else
-    {
-        return KeyNameFromKeyCode( hotkey_descr->m_KeyCode );
-    }
+    wxASSERT( aRow >= 0 && aRow < m_items.size() );
+    Unselect( m_items[aRow] );
 }
 
 
 void HOTKEY_LIST_CTRL::OnChar( wxKeyEvent& aEvent )
 {
-    if( m_curEditingRow != -1 )
+    DIALOG_HOTKEY_CLIENT_DATA* data = GetSelHKClientData();
+
+    if( data )
     {
         long key = aEvent.GetKeyCode();
 
         switch( key )
         {
         case WXK_ESCAPE:
-            // Remove selection
-            DeselectRow( m_curEditingRow );
-            m_curEditingRow = -1;
+            UnselectAll();
             break;
 
         default:
@@ -182,43 +118,185 @@ void HOTKEY_LIST_CTRL::OnChar( wxKeyEvent& aEvent )
             bool exists;
             KeyNameFromKeyCode( key, &exists );
 
-            if( exists && m_hotkeys[m_curEditingRow]->m_KeyCode != key )
+            if( exists && data->GetHotkey().m_KeyCode != key )
             {
-                bool canUpdate = ((HOTKEY_SECTION_PAGE *)m_parent)->GetDialog()->CanSetKey( key, m_sectionTag );
+                wxString tag = data->GetSectionTag();
+                HOTKEY_SECTION_PAGE* parent = static_cast<HOTKEY_SECTION_PAGE*>( m_parent );
+                bool canUpdate = parent->GetDialog()->CanSetKey( key, tag );
 
                 if( canUpdate )
                 {
-                    m_hotkeys[m_curEditingRow]->m_KeyCode = key;
-                    recalculateColumns();
+                    data->GetHotkey().m_KeyCode = key;
                 }
 
                 // Remove selection
-                DeselectRow( m_curEditingRow );
-                m_curEditingRow = -1;
+                UnselectAll();
             }
         }
     }
-    RefreshItems(0,m_hotkeys.size()-1);
+    UpdateFromClientData();
 }
 
 
-void HOTKEY_LIST_CTRL::RestoreFrom( struct EDA_HOTKEY_CONFIG* aSection )
+DIALOG_HOTKEY_CLIENT_DATA* HOTKEY_LIST_CTRL::GetSelHKClientData()
 {
-    int row = 0;
+    return GetHKClientData( GetSelection() );
+}
+
+
+DIALOG_HOTKEY_CLIENT_DATA* HOTKEY_LIST_CTRL::GetHKClientData( wxTreeListItem aItem )
+{
+    if( aItem.IsOk() )
+    {
+        wxClientData* data = GetItemData( aItem );
+        if( !data )
+            return NULL;
+
+        DIALOG_HOTKEY_CLIENT_DATA* hkdata = static_cast<DIALOG_HOTKEY_CLIENT_DATA*>( data );
+        return hkdata;
+    }
+    else
+    {
+        return NULL;
+    }
+}
+
 
+void HOTKEY_LIST_CTRL::LoadSection( struct EDA_HOTKEY_CONFIG* aSection )
+{
+    HOTKEY_LIST list;
     EDA_HOTKEY** info_ptr;
 
     for( info_ptr = aSection->m_HK_InfoList; *info_ptr; info_ptr++ )
     {
-        EDA_HOTKEY* info = *info_ptr;
-        m_hotkeys[row++]->m_KeyCode = info->m_KeyCode;
+        EDA_HOTKEY info = **info_ptr;
+        list.push_back( info );
+    }
+
+    m_hotkeys.push_back( list );
+}
+
+
+void HOTKEY_LIST_CTRL::UpdateFromClientData()
+{
+    for( wxTreeListItem i = GetFirstItem(); i.IsOk(); i = GetNextItem( i ) )
+    {
+        DIALOG_HOTKEY_CLIENT_DATA* hkdata = GetHKClientData( i );
+        if( !hkdata )
+            continue;
+
+        EDA_HOTKEY& hk = hkdata->GetHotkey();
+
+        wxString name = wxGetTranslation( hk.m_InfoMsg );
+        wxString key = KeyNameFromKeyCode( hk.m_KeyCode );
+
+        SetItemText( i, 0, name );
+        SetItemText( i, 1, key );
+    }
+}
+
+
+bool HOTKEY_LIST_CTRL::TransferDataToControl()
+{
+    Freeze();
+    DeleteAllItems();
+    m_items.clear();
+    m_hotkeys.clear();
+
+    for( size_t i_list = 0; i_list < m_sections.size(); ++i_list )
+    {
+        LoadSection( m_sections[i_list].second );
+        wxString section_tag = *( m_sections[i_list].second->m_SectionTag );
+
+        HOTKEY_LIST& each_list = m_hotkeys[i_list];
+        for( size_t i_hotkey = 0; i_hotkey < each_list.size(); ++i_hotkey )
+        {
+            EDA_HOTKEY* hotkey_descr = &each_list[i_hotkey];
+
+            wxTreeListItem item = AppendItem( GetRootItem(), wxEmptyString );
+            SetItemData( item, new DIALOG_HOTKEY_CLIENT_DATA( hotkey_descr, section_tag ) );
+            m_items.push_back( item );
+        }
+    }
+
+    UpdateFromClientData();
+    Thaw();
+
+    return true;
+}
+
+
+bool HOTKEY_LIST_CTRL::TransferDataFromControl()
+{
+    for( size_t i_sec = 0; i_sec < m_sections.size(); ++i_sec )
+    {
+        struct EDA_HOTKEY_CONFIG* section = m_sections[i_sec].second;
+        for( EDA_HOTKEY** info_ptr = section->m_HK_InfoList; *info_ptr; ++info_ptr )
+        {
+            EDA_HOTKEY* info = *info_ptr;
+            for( wxTreeListItem item = GetFirstItem(); item.IsOk(); item = GetNextItem( item ) )
+            {
+                DIALOG_HOTKEY_CLIENT_DATA* hkdata = GetHKClientData( item );
+                if( !hkdata )
+                    continue;
+
+                EDA_HOTKEY& hk = hkdata->GetHotkey();
+                if( hk.m_Idcommand == info->m_Idcommand )
+                {
+                    info->m_KeyCode = hk.m_KeyCode;
+                    break;
+                }
+            }
+        }
     }
+    return true;
+}
 
-    // Remove selection
-    DeselectRow( m_curEditingRow );
-    m_curEditingRow = -1;
 
-    RefreshItems( 0, m_hotkeys.size()-1 );
+bool HOTKEY_LIST_CTRL::CanSetKey( long aKey, const wxString& aSectionTag,
+        EDA_HOTKEY** aConfKey, EDA_HOTKEY_CONFIG** aConfSect )
+{
+    EDA_HOTKEY* conflictingKey = NULL;
+    struct EDA_HOTKEY_CONFIG* conflictingSection = NULL;
+
+    for( wxTreeListItem item = GetFirstItem(); item.IsOk(); item = GetNextItem( item ) )
+    {
+        DIALOG_HOTKEY_CLIENT_DATA* hkdata = GetHKClientData( item );
+        if( !hkdata )
+            continue;
+
+        EDA_HOTKEY& hk = hkdata->GetHotkey();
+        wxString tag = hkdata->GetSectionTag();
+
+        if( aSectionTag != g_CommonSectionTag
+                && tag != g_CommonSectionTag
+                && tag != aSectionTag )
+            continue;
+
+        if( aKey == hk.m_KeyCode )
+        {
+            conflictingKey = &hk;
+
+            // Find the section
+            HOTKEYS_SECTIONS::iterator it;
+            for( it = m_sections.begin(); it != m_sections.end(); ++it )
+            {
+                if( *it->second->m_SectionTag == tag )
+                {
+                    conflictingSection = it->second;
+                    break;
+                }
+            }
+        }
+    }
+
+    if( aConfKey )
+        *aConfKey = conflictingKey;
+
+    if( aConfSect )
+        *aConfSect = conflictingSection;
+
+    return conflictingKey == NULL;
 }
 
 
@@ -227,7 +305,6 @@ HOTKEY_SECTION_PAGE::HOTKEY_SECTION_PAGE( HOTKEYS_EDITOR_DIALOG* aDialog,
                                           const wxString& aTitle,
                                           EDA_HOTKEY_CONFIG* aSection ) :
     wxPanel( aParent, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxNO_BORDER ),
-    m_hotkeySection( aSection ),
     m_dialog( aDialog )
 {
     aParent->AddPage( this, aTitle );
@@ -235,17 +312,22 @@ HOTKEY_SECTION_PAGE::HOTKEY_SECTION_PAGE( HOTKEYS_EDITOR_DIALOG* aDialog,
 	wxBoxSizer* bMainSizer = new wxBoxSizer( wxVERTICAL );
 
 	SetSizer( bMainSizer );
-	Layout();
-	bMainSizer->Fit( this );
 
-	m_hotkeyList = new HOTKEY_LIST_CTRL( this, aSection );
+    HOTKEYS_SECTION section( aTitle, aSection );
+    HOTKEYS_SECTIONS sections;
+    sections.push_back( section );
+
+	m_hotkeyList = new HOTKEY_LIST_CTRL( this, sections );
 	bMainSizer->Add( m_hotkeyList, 1, wxALL|wxEXPAND, 5 );
+
+	Layout();
+	bMainSizer->Fit( this );
 }
 
 
 void HOTKEY_SECTION_PAGE::Restore()
 {
-    m_hotkeyList->RestoreFrom( m_hotkeySection );
+    m_hotkeyList->TransferDataToControl();
 
     Update();
 }
@@ -284,44 +366,42 @@ HOTKEYS_EDITOR_DIALOG::HOTKEYS_EDITOR_DIALOG( EDA_BASE_FRAME*    aParent,
 }
 
 
-void HOTKEYS_EDITOR_DIALOG::OnOKClicked( wxCommandEvent& event )
+bool HOTKEYS_EDITOR_DIALOG::TransferDataToWindow()
 {
-    std::vector<HOTKEY_SECTION_PAGE*>::iterator i;
+    if( !wxDialog::TransferDataToWindow() )
+        return false;
 
+    std::vector<HOTKEY_SECTION_PAGE*>::iterator i;
     for( i = m_hotkeySectionPages.begin(); i != m_hotkeySectionPages.end(); ++i )
     {
-        std::vector<EDA_HOTKEY*>& hotkey_vec = (*i)->GetHotkeys();
-        EDA_HOTKEY_CONFIG* section = (*i)->GetHotkeySection();
+        if( !(*i)->GetHotkeyCtrl()->TransferDataToControl() )
+            return false;
+    }
 
-        EDA_HOTKEY** info_ptr;
+    return true;
+}
 
-        for( info_ptr = section->m_HK_InfoList; *info_ptr; info_ptr++ )
-        {
-            EDA_HOTKEY* info = *info_ptr;
 
-            /* find the corresponding hotkey */
-            std::vector<EDA_HOTKEY*>::iterator j;
+bool HOTKEYS_EDITOR_DIALOG::TransferDataFromWindow()
+{
+    if( !wxDialog::TransferDataToWindow() )
+        return false;
 
-            for( j = hotkey_vec.begin(); j != hotkey_vec.end(); ++j )
-            {
-                if( (*j) && (*j)->m_Idcommand == info->m_Idcommand )
-                {
-                    info->m_KeyCode = (*j)->m_KeyCode;
-                    break;
-                }
-            }
-        }
+    std::vector<HOTKEY_SECTION_PAGE*>::iterator i;
+    for( i = m_hotkeySectionPages.begin(); i != m_hotkeySectionPages.end(); ++i )
+    {
+        if( !(*i)->GetHotkeyCtrl()->TransferDataFromControl() )
+            return false;
     }
 
-    /* save the hotkeys */
+    // save the hotkeys
     m_parent->WriteHotkeyConfig( m_hotkeys );
 
-    EndModal( wxID_OK );
+    return true;
 }
 
 
-
-void HOTKEYS_EDITOR_DIALOG::UndoClicked( wxCommandEvent& aEvent )
+void HOTKEYS_EDITOR_DIALOG::ResetClicked( wxCommandEvent& aEvent )
 {
     std::vector<HOTKEY_SECTION_PAGE*>::iterator i;
 
@@ -332,34 +412,22 @@ void HOTKEYS_EDITOR_DIALOG::UndoClicked( wxCommandEvent& aEvent )
 }
 
 
-bool HOTKEYS_EDITOR_DIALOG::CanSetKey( long aKey, const wxString* sectionTag )
+bool HOTKEYS_EDITOR_DIALOG::CanSetKey( long aKey, const wxString& aSectionTag )
 {
     std::vector<HOTKEY_SECTION_PAGE*>::iterator i;
 
     EDA_HOTKEY* conflictingKey = NULL;
-    HOTKEY_SECTION_PAGE* conflictingSection = NULL;
+    EDA_HOTKEY_CONFIG* conflictingSection = NULL;
+    HOTKEY_LIST_CTRL *conflictingCtrl = NULL;
 
     for( i = m_hotkeySectionPages.begin(); i != m_hotkeySectionPages.end(); ++i )
     {
-        // Any non Common section can only conflict with itself and Common
-        if( *sectionTag != g_CommonSectionTag
-                 && *((*i)->GetHotkeySection()->m_SectionTag) != g_CommonSectionTag
-                 && *((*i)->GetHotkeySection()->m_SectionTag) != *sectionTag )
-            continue;
-
-        std::vector<EDA_HOTKEY*>& hotkey_vec = (*i)->GetHotkeys();
-        /* find the corresponding hotkey */
-        std::vector<EDA_HOTKEY*>::iterator j;
+        HOTKEY_LIST_CTRL *ctrl = (*i)->GetHotkeyCtrl();
 
-        for( j = hotkey_vec.begin(); j != hotkey_vec.end(); ++j )
+        if ( !ctrl->CanSetKey( aKey, aSectionTag, &conflictingKey, &conflictingSection ) )
         {
-            if( aKey == (*j)->m_KeyCode )
-            {
-                conflictingKey = (*j);
-                conflictingSection = (*i);
-
-                break;
-            }
+            conflictingCtrl = ctrl;
+            break;
         }
     }
 
@@ -370,13 +438,14 @@ bool HOTKEYS_EDITOR_DIALOG::CanSetKey( long aKey, const wxString* sectionTag )
             _( "<%s> is already assigned to \"%s\" in section \"%s\". Are you sure you want "
                "to change its assignment?" ),
             KeyNameFromKeyCode( aKey ), GetChars( info ),
-            *(conflictingSection->GetHotkeySection()->m_Title) );
+            *(conflictingSection->m_Title) );
 
         wxMessageDialog dlg( this, msg, _( "Confirm change" ), wxYES_NO | wxNO_DEFAULT );
 
         if( dlg.ShowModal() == wxID_YES )
         {
             conflictingKey->m_KeyCode = 0;
+            conflictingCtrl->UpdateFromClientData();
             return true;
         }
         else
diff --git a/common/dialogs/dialog_hotkeys_editor_base.cpp b/common/dialogs/dialog_hotkeys_editor_base.cpp
index ef2f7c9..d2e99ee 100644
--- a/common/dialogs/dialog_hotkeys_editor_base.cpp
+++ b/common/dialogs/dialog_hotkeys_editor_base.cpp
@@ -1,5 +1,5 @@
 ///////////////////////////////////////////////////////////////////////////
-// C++ code generated with wxFormBuilder (version Jun 17 2015)
+// C++ code generated with wxFormBuilder (version Dec 28 2015)
 // http://www.wxformbuilder.org/
 //
 // PLEASE DO "NOT" EDIT THIS FILE!
@@ -27,6 +27,12 @@ HOTKEYS_EDITOR_DIALOG_BASE::HOTKEYS_EDITOR_DIALOG_BASE( wxWindow* parent, wxWind
 	wxBoxSizer* b_buttonsSizer;
 	b_buttonsSizer = new wxBoxSizer( wxHORIZONTAL );
 	
+	m_resetButton = new wxButton( this, wxID_RESET, _("Reset"), wxDefaultPosition, wxDefaultSize, 0 );
+	b_buttonsSizer->Add( m_resetButton, 0, wxALL|wxEXPAND, 5 );
+	
+	
+	b_buttonsSizer->Add( 0, 0, 1, wxEXPAND, 5 );
+	
 	m_sdbSizer = new wxStdDialogButtonSizer();
 	m_sdbSizerOK = new wxButton( this, wxID_OK );
 	m_sdbSizer->AddButton( m_sdbSizerOK );
@@ -36,25 +42,20 @@ HOTKEYS_EDITOR_DIALOG_BASE::HOTKEYS_EDITOR_DIALOG_BASE( wxWindow* parent, wxWind
 	
 	b_buttonsSizer->Add( m_sdbSizer, 0, wxEXPAND|wxTOP|wxBOTTOM, 5 );
 	
-	m_undoButton = new wxButton( this, wxID_UNDO, _("Undo"), wxDefaultPosition, wxDefaultSize, 0 );
-	b_buttonsSizer->Add( m_undoButton, 0, wxALL|wxEXPAND, 5 );
-	
 	
-	bMainSizer->Add( b_buttonsSizer, 0, wxALIGN_RIGHT, 5 );
+	bMainSizer->Add( b_buttonsSizer, 0, wxALIGN_RIGHT|wxEXPAND, 5 );
 	
 	
 	this->SetSizer( bMainSizer );
 	this->Layout();
 	
 	// Connect Events
-	m_sdbSizerOK->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( HOTKEYS_EDITOR_DIALOG_BASE::OnOKClicked ), NULL, this );
-	m_undoButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( HOTKEYS_EDITOR_DIALOG_BASE::UndoClicked ), NULL, this );
+	m_resetButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( HOTKEYS_EDITOR_DIALOG_BASE::ResetClicked ), NULL, this );
 }
 
 HOTKEYS_EDITOR_DIALOG_BASE::~HOTKEYS_EDITOR_DIALOG_BASE()
 {
 	// Disconnect Events
-	m_sdbSizerOK->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( HOTKEYS_EDITOR_DIALOG_BASE::OnOKClicked ), NULL, this );
-	m_undoButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( HOTKEYS_EDITOR_DIALOG_BASE::UndoClicked ), NULL, this );
+	m_resetButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( HOTKEYS_EDITOR_DIALOG_BASE::ResetClicked ), NULL, this );
 	
 }
diff --git a/common/dialogs/dialog_hotkeys_editor_base.fbp b/common/dialogs/dialog_hotkeys_editor_base.fbp
index 7b12ee4f..8bd3ca6 100644
--- a/common/dialogs/dialog_hotkeys_editor_base.fbp
+++ b/common/dialogs/dialog_hotkeys_editor_base.fbp
@@ -262,39 +262,13 @@
                 </object>
                 <object class="sizeritem" expanded="1">
                     <property name="border">5</property>
-                    <property name="flag">wxALIGN_RIGHT</property>
+                    <property name="flag">wxALIGN_RIGHT|wxEXPAND</property>
                     <property name="proportion">0</property>
                     <object class="wxBoxSizer" expanded="1">
                         <property name="minimum_size"></property>
                         <property name="name">b_buttonsSizer</property>
                         <property name="orient">wxHORIZONTAL</property>
                         <property name="permission">none</property>
-                        <object class="sizeritem" expanded="1">
-                            <property name="border">5</property>
-                            <property name="flag">wxEXPAND|wxTOP|wxBOTTOM</property>
-                            <property name="proportion">0</property>
-                            <object class="wxStdDialogButtonSizer" expanded="1">
-                                <property name="Apply">0</property>
-                                <property name="Cancel">1</property>
-                                <property name="ContextHelp">0</property>
-                                <property name="Help">0</property>
-                                <property name="No">0</property>
-                                <property name="OK">1</property>
-                                <property name="Save">0</property>
-                                <property name="Yes">0</property>
-                                <property name="minimum_size"></property>
-                                <property name="name">m_sdbSizer</property>
-                                <property name="permission">protected</property>
-                                <event name="OnApplyButtonClick"></event>
-                                <event name="OnCancelButtonClick"></event>
-                                <event name="OnContextHelpButtonClick"></event>
-                                <event name="OnHelpButtonClick"></event>
-                                <event name="OnNoButtonClick"></event>
-                                <event name="OnOKButtonClick">OnOKClicked</event>
-                                <event name="OnSaveButtonClick"></event>
-                                <event name="OnYesButtonClick"></event>
-                            </object>
-                        </object>
                         <object class="sizeritem" expanded="0">
                             <property name="border">5</property>
                             <property name="flag">wxALL|wxEXPAND</property>
@@ -327,8 +301,8 @@
                                 <property name="font"></property>
                                 <property name="gripper">0</property>
                                 <property name="hidden">0</property>
-                                <property name="id">wxID_UNDO</property>
-                                <property name="label">Undo</property>
+                                <property name="id">wxID_RESET</property>
+                                <property name="label">Reset</property>
                                 <property name="max_size"></property>
                                 <property name="maximize_button">0</property>
                                 <property name="maximum_size"></property>
@@ -336,7 +310,7 @@
                                 <property name="minimize_button">0</property>
                                 <property name="minimum_size"></property>
                                 <property name="moveable">1</property>
-                                <property name="name">m_undoButton</property>
+                                <property name="name">m_resetButton</property>
                                 <property name="pane_border">1</property>
                                 <property name="pane_position"></property>
                                 <property name="pane_size"></property>
@@ -357,7 +331,7 @@
                                 <property name="window_extra_style"></property>
                                 <property name="window_name"></property>
                                 <property name="window_style"></property>
-                                <event name="OnButtonClick">UndoClicked</event>
+                                <event name="OnButtonClick">ResetClicked</event>
                                 <event name="OnChar"></event>
                                 <event name="OnEnterWindow"></event>
                                 <event name="OnEraseBackground"></event>
@@ -383,6 +357,42 @@
                                 <event name="OnUpdateUI"></event>
                             </object>
                         </object>
+                        <object class="sizeritem" expanded="1">
+                            <property name="border">5</property>
+                            <property name="flag">wxEXPAND</property>
+                            <property name="proportion">1</property>
+                            <object class="spacer" expanded="1">
+                                <property name="height">0</property>
+                                <property name="permission">protected</property>
+                                <property name="width">0</property>
+                            </object>
+                        </object>
+                        <object class="sizeritem" expanded="1">
+                            <property name="border">5</property>
+                            <property name="flag">wxEXPAND|wxTOP|wxBOTTOM</property>
+                            <property name="proportion">0</property>
+                            <object class="wxStdDialogButtonSizer" expanded="1">
+                                <property name="Apply">0</property>
+                                <property name="Cancel">1</property>
+                                <property name="ContextHelp">0</property>
+                                <property name="Help">0</property>
+                                <property name="No">0</property>
+                                <property name="OK">1</property>
+                                <property name="Save">0</property>
+                                <property name="Yes">0</property>
+                                <property name="minimum_size"></property>
+                                <property name="name">m_sdbSizer</property>
+                                <property name="permission">protected</property>
+                                <event name="OnApplyButtonClick"></event>
+                                <event name="OnCancelButtonClick"></event>
+                                <event name="OnContextHelpButtonClick"></event>
+                                <event name="OnHelpButtonClick"></event>
+                                <event name="OnNoButtonClick"></event>
+                                <event name="OnOKButtonClick"></event>
+                                <event name="OnSaveButtonClick"></event>
+                                <event name="OnYesButtonClick"></event>
+                            </object>
+                        </object>
                     </object>
                 </object>
             </object>
diff --git a/common/dialogs/dialog_hotkeys_editor_base.h b/common/dialogs/dialog_hotkeys_editor_base.h
index f0d4d9e..cfd6528 100644
--- a/common/dialogs/dialog_hotkeys_editor_base.h
+++ b/common/dialogs/dialog_hotkeys_editor_base.h
@@ -1,5 +1,5 @@
 ///////////////////////////////////////////////////////////////////////////
-// C++ code generated with wxFormBuilder (version Jun 17 2015)
+// C++ code generated with wxFormBuilder (version Dec 28 2015)
 // http://www.wxformbuilder.org/
 //
 // PLEASE DO "NOT" EDIT THIS FILE!
@@ -21,8 +21,8 @@ class DIALOG_SHIM;
 #include <wx/colour.h>
 #include <wx/settings.h>
 #include <wx/notebook.h>
-#include <wx/sizer.h>
 #include <wx/button.h>
+#include <wx/sizer.h>
 #include <wx/dialog.h>
 
 ///////////////////////////////////////////////////////////////////////////
@@ -38,14 +38,13 @@ class HOTKEYS_EDITOR_DIALOG_BASE : public DIALOG_SHIM
 	protected:
 		wxStaticText* m_staticText1;
 		wxNotebook* m_hotkeySections;
+		wxButton* m_resetButton;
 		wxStdDialogButtonSizer* m_sdbSizer;
 		wxButton* m_sdbSizerOK;
 		wxButton* m_sdbSizerCancel;
-		wxButton* m_undoButton;
 		
 		// Virtual event handlers, overide them in your derived class
-		virtual void OnOKClicked( wxCommandEvent& event ) { event.Skip(); }
-		virtual void UndoClicked( wxCommandEvent& event ) { event.Skip(); }
+		virtual void ResetClicked( wxCommandEvent& event ) { event.Skip(); }
 		
 	
 	public:
diff --git a/include/dialog_hotkeys_editor.h b/include/dialog_hotkeys_editor.h
index 55951c2..30aae16 100644
--- a/include/dialog_hotkeys_editor.h
+++ b/include/dialog_hotkeys_editor.h
@@ -1,7 +1,7 @@
 /*
  * This program source code file is part of KiCad, a free EDA CAD application.
  *
- * Copyright (C) 2004-2014 KiCad Developers, see CHANGELOG.TXT for contributors.
+ * Copyright (C) 2004-2016 KiCad Developers, see CHANGELOG.TXT for contributors.
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License
@@ -38,24 +38,33 @@
 #include <wx/textctrl.h>
 #include <wx/stattext.h>
 #include <wx/button.h>
-#include <wx/listctrl.h>
+#include <wx/treelist.h>
 #include <wx/dialog.h>
 #include <wx/grid.h>
 
+#include <vector>
+#include <utility>
+
 #include <hotkeys_basic.h>
 #include <draw_frame.h>
 #include <../common/dialogs/dialog_hotkeys_editor_base.h>
 
+typedef std::pair<wxString, struct EDA_HOTKEY_CONFIG*> HOTKEYS_SECTION;
+typedef std::vector<HOTKEYS_SECTION> HOTKEYS_SECTIONS;
+
+typedef std::vector<EDA_HOTKEY> HOTKEY_LIST;
+
 class HOTKEYS_EDITOR_DIALOG;
+class DIALOG_HOTKEY_CLIENT_DATA;
 
 /**
  * Class HOTKEY_LIST_CTRL
  * is a class to contain the contents of a hotkey editor tab page.
  */
-class HOTKEY_LIST_CTRL : public wxListCtrl
+class HOTKEY_LIST_CTRL : public wxTreeListCtrl
 {
 public:
-    HOTKEY_LIST_CTRL( wxWindow* aParent, struct EDA_HOTKEY_CONFIG* aSection );
+    HOTKEY_LIST_CTRL( wxWindow* aParent, const HOTKEYS_SECTIONS& aSections );
     ~HOTKEY_LIST_CTRL() {};
 
     /**
@@ -67,36 +76,65 @@ public:
     void DeselectRow( int aRow );
 
     /**
-     * Function GetHotkeys
-     * Access to return the vector used for the list control data. This will contain the
-     * "live" state of the user's configuration.
-     *
-     * @return Pointer to vector of hotkey settings
+     * Function TransferDataToControl
+     * Load the hotkey data into the control.
+     * @return true iff the operation was successful
      */
-    std::vector< EDA_HOTKEY* >& GetHotkeys() { return m_hotkeys; }
+    bool TransferDataToControl();
 
     /**
-     * Function RestoreFrom
-     * Restores list control hotkey keycodes to the keycodes present in the
-     * given hotkey configuration array.
+     * Function TransferDataFromControl
+     * Save the hotkey data from the control.
+     * @return true iff the operation was successful
+     */
+    bool TransferDataFromControl();
+
+    /**
+     * Function CanSetKey
+     * Check whether the given key conflicts with anything in this HOTKEY_LIST_CTRL.
      *
-     * @param aSection is a pointer to the hotkey configuration array
+     * @param aKey - key to check
+     * @param aSectionTag - section tag of the key
+     * @param aConfKey - if not NULL, outparam holding the key this one conflicts with
+     * @param aConfSect - if not NULL, outparam holding the section this one conflicts with
      */
-    void RestoreFrom( struct EDA_HOTKEY_CONFIG* aSection );
+    bool CanSetKey( long aKey, const wxString& aSectionTag,
+            EDA_HOTKEY** aConfKey, EDA_HOTKEY_CONFIG** aConfSect );
+
+    /**
+     * Function UpdateFromClientData
+     * Update all visible items from the data stored in their client data objects.
+     */
+    void UpdateFromClientData();
 
 private:
-    int m_curEditingRow;
-    wxString* m_sectionTag;
-    std::vector< EDA_HOTKEY* > m_hotkeys;
+    HOTKEYS_SECTIONS m_sections;
+    std::vector< HOTKEY_LIST > m_hotkeys;
+    std::vector< wxTreeListItem > m_items;
 
     /**
-     * Function recalculateColumns
-     * Adjusts the width of grid columns in proportion of the max text length of both
+     * Function GetSelHKClientData
+     * Return the DIALOG_HOTKEY_CLIENT_DATA for the item being edited, or NULL if none is selected.
      */
-    void recalculateColumns();
+    DIALOG_HOTKEY_CLIENT_DATA* GetSelHKClientData();
+
+    /**
+     * Function GetHKClientData
+     * Return the DIALOG_HOTKEY_CLIENT_DATA for the given item, or NULL if invalid.
+     */
+    DIALOG_HOTKEY_CLIENT_DATA* GetHKClientData( wxTreeListItem aItem );
 
 protected:
     /**
+     * Function LoadSection
+     * Generates a HOTKEY_LIST from the given hotkey configuration array and
+     * pushes it to m_hotkeys.
+     *
+     * @param aSection is a pointer to the hotkey configuration array
+     */
+    void LoadSection( struct EDA_HOTKEY_CONFIG* aSection );
+
+    /**
      * Function OnGetItemText
      * Returns the requested row, column data to the list control.
      *
@@ -116,21 +154,8 @@ protected:
     void OnChar( wxKeyEvent& aEvent );
 
     /**
-     * Function OnListItemSelected
-     * Item selection handler which is used to record what index is selected to alter
-     * update with the key press
-     *
-     * @param aEvent is the button press event, unused
-     */
-    void OnListItemSelected( wxListEvent& aEvent );
-
-    /**
      * Function OnSize
-     * Sizing update handler to recompute the column widths dynamically and maximize them.
-     * Due to wxWidget's broken autosizing support (it's completely inconsistent across
-     * platforms), we just do it based on a scale of
-     *
-     * @param aEvent is the button press event, unused
+     * Sizing update handler to recompute the column widths dynamically.
      */
     void OnSize( wxSizeEvent& aEvent );
 };
@@ -143,7 +168,6 @@ class HOTKEY_SECTION_PAGE : public wxPanel
 {
 public:
 private:
-    EDA_HOTKEY_CONFIG*  m_hotkeySection;
     HOTKEY_LIST_CTRL *m_hotkeyList;
     HOTKEYS_EDITOR_DIALOG* m_dialog;
 
@@ -166,20 +190,12 @@ public:
     void Restore();
 
     /**
-     * Function GetHotkeys
-     * Accessor to retrieve hotkeys list from list control
+     * Function GetHotkeyCtrl
+     * Accessor to retrieve hotkey configuration control assigned to a tab control page
      *
-     * @return Pointer to vector used for list control data
+     * @return Pointer to hotkey configuration control
      */
-    std::vector< EDA_HOTKEY* >& GetHotkeys() { return m_hotkeyList->GetHotkeys(); }
-
-    /**
-     * Function GetHotkeySection
-     * Accessor to retrieve hotkey configuration array assigned to a tab control page
-     *
-     * @return Pointer to hotkey configuration array
-     */
-    EDA_HOTKEY_CONFIG* GetHotkeySection() { return m_hotkeySection; }
+    HOTKEY_LIST_CTRL* GetHotkeyCtrl() { return m_hotkeyList; }
 
     /**
      * Function GetDialog
@@ -203,6 +219,9 @@ protected:
 
     std::vector<HOTKEY_SECTION_PAGE*> m_hotkeySectionPages;
 
+    bool TransferDataToWindow();
+    bool TransferDataFromWindow();
+
 public:
     HOTKEYS_EDITOR_DIALOG( EDA_BASE_FRAME* aParent, EDA_HOTKEY_CONFIG* aHotkeys );
 
@@ -224,24 +243,17 @@ public:
      *
      * @return True if the user accepted the overwrite or no conflict existed
      */
-    bool CanSetKey( long aKey, const wxString* aSectionTag );
+    bool CanSetKey( long aKey, const wxString& aSectionTag );
 
 private:
-    /**
-     * Function OnOKClicked
-     * Close the dialog and make save all changes to hotkeys
-     *
-     * @param aEvent is the button press event, unused
-     */
-    void OnOKClicked( wxCommandEvent& aEvent );
 
     /**
-     * Function UndoClicked
+     * Function ResetClicked
      * Reinit the hotkeys to the initial state (removes all pending changes)
      *
      * @param aEvent is the button press event, unused
      */
-    void UndoClicked( wxCommandEvent& aEvent );
+    void ResetClicked( wxCommandEvent& aEvent );
 };
 
 /**

Follow ups