KiCad PCB EDA Suite
Loading...
Searching...
No Matches
local_history_pane.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.TXT for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 3
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, you may find one here:
18 * http://www.gnu.org/licenses/gpl-3.0.html
19 * or you may search the http://www.gnu.org website for the version 3 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
24#include "local_history_pane.h"
25#include "kicad_manager_frame.h"
26
27#include <git2.h>
28#include <project.h>
29#include <wx/filename.h>
30#include <wx/intl.h>
31#include <wx/menu.h>
32
33wxDEFINE_EVENT( EVT_LOCAL_HISTORY_REFRESH, wxCommandEvent );
34
36 m_frame( aParent ), m_list( nullptr ), m_timer( this ), m_refreshTimer( this ),
37 m_hoverItem( -1 ),
38#if wxCHECK_VERSION( 3, 3, 2 )
39 m_tip()
40#else
41 m_tip( nullptr )
42#endif
43{
44 m_list = new wxListCtrl( this, wxID_ANY, wxDefaultPosition, wxDefaultSize,
45 wxLC_REPORT | wxLC_SINGLE_SEL );
46 m_list->AppendColumn( _( "Title" ) );
47 m_list->AppendColumn( _( "Time" ) );
48 m_list->SetColumnWidth( 0, FromDIP( 200 ) );
49 m_list->SetColumnWidth( 1, FromDIP( 150 ) );
50
51 wxBoxSizer* sizer = new wxBoxSizer( wxVERTICAL );
52 sizer->Add( m_list, 1, wxEXPAND );
53 SetSizer( sizer );
54
55 m_list->Bind( wxEVT_MOTION, &LOCAL_HISTORY_PANE::OnMotion, this );
56 m_list->Bind( wxEVT_LEAVE_WINDOW, &LOCAL_HISTORY_PANE::OnLeave, this );
57 m_list->Bind( wxEVT_LIST_ITEM_RIGHT_CLICK, &LOCAL_HISTORY_PANE::OnRightClick, this );
58 Bind( wxEVT_TIMER, &LOCAL_HISTORY_PANE::OnTimer, this, m_timer.GetId() );
59 Bind( wxEVT_TIMER, &LOCAL_HISTORY_PANE::OnRefreshTimer, this, m_refreshTimer.GetId() );
60 Bind( EVT_LOCAL_HISTORY_REFRESH, &LOCAL_HISTORY_PANE::OnRefreshEvent, this );
61
62 // Start refresh timer to check for updates every 10 seconds
63 m_refreshTimer.Start( 10000 );
64}
65
67{
68 m_timer.Stop();
69 m_refreshTimer.Stop();
70
71 if( m_tip )
72 {
73 m_tip->Destroy();
74 m_tip = nullptr;
75 }
76}
77
78void LOCAL_HISTORY_PANE::RefreshHistory( const wxString& aProjectPath )
79{
80 if( !IsShownOnScreen() )
81 return;
82
83 std::lock_guard<std::mutex> lock( m_mutex );
84
85 m_list->DeleteAllItems();
86 m_commits.clear();
87
88 wxFileName hist( aProjectPath, wxS( ".history" ) );
89
90 git_repository* repo = nullptr;
91 if( git_repository_open( &repo, hist.GetFullPath().mb_str().data() ) != 0 )
92 return;
93
94 git_revwalk* walk = nullptr;
95 if( git_revwalk_new( &walk, repo ) != 0 )
96 {
97 git_repository_free( repo );
98 return;
99 }
100
101 if( git_revwalk_push_head( walk ) != 0 )
102 {
103 git_revwalk_free( walk );
104 git_repository_free( repo );
105 return;
106 }
107
108 wxDateTime now = wxDateTime::Now();
109
110 git_oid oid;
111 while( git_revwalk_next( &oid, walk ) == 0 )
112 {
113 git_commit* commit = nullptr;
114 if( git_commit_lookup( &commit, repo, &oid ) != 0 )
115 continue;
116
118 info.hash = wxString::FromUTF8( git_oid_tostr_s( &oid ) );
119 info.summary = wxString::FromUTF8( git_commit_summary( commit ) );
120 info.message = wxString::FromUTF8( git_commit_message( commit ) );
121 info.date = wxDateTime( static_cast<time_t>( git_commit_time( commit ) ) );
122
123 // Calculate relative time
124 wxTimeSpan elapsed = now - info.date;
125 wxString timeStr;
126
127 if( elapsed.GetMinutes() < 1 )
128 {
129 timeStr = _( "Moments ago" );
130 }
131 else if( elapsed.GetMinutes() < 91 )
132 {
133 int minutes = elapsed.GetMinutes();
134 timeStr = wxString::Format( _( "%d minutes ago" ), minutes );
135 }
136 else if( elapsed.GetHours() < 24 )
137 {
138 int hours = elapsed.GetHours();
139 timeStr = wxString::Format( _( "%d hours ago" ), hours );
140 }
141 else
142 {
143 timeStr = info.date.Format();
144 }
145
146 wxString title = info.message.BeforeFirst( '\n' );
147 long row = m_list->InsertItem( m_list->GetItemCount(), title );
148 m_list->SetItem( row, 1, timeStr );
149
150 if( info.summary.StartsWith( wxS( "Autosave" ) ) )
151 m_list->SetItemBackgroundColour( row, wxColour( 230, 255, 230 ) );
152 else if( info.summary.StartsWith( wxS( "Backup" ) ) )
153 m_list->SetItemBackgroundColour( row, wxColour( 230, 230, 255 ) );
154
155 m_commits.emplace_back( std::move( info ) );
156 git_commit_free( commit );
157 }
158
159 git_revwalk_free( walk );
160 git_repository_free( repo );
161}
162
163void LOCAL_HISTORY_PANE::OnMotion( wxMouseEvent& aEvent )
164{
165 std::lock_guard<std::mutex> lock( m_mutex );
166
167 int flags = 0;
168 long item = m_list->HitTest( aEvent.GetPosition(), flags );
169
170 if( item != m_hoverItem )
171 {
172 if( m_hoverItem != -1 )
173 m_list->SetItemState( m_hoverItem, 0, wxLIST_STATE_SELECTED );
174
175 m_hoverItem = item;
176 m_hoverPos = aEvent.GetPosition();
177
178 if( m_hoverItem != -1 )
179 m_list->SetItemState( m_hoverItem, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED );
180
181 m_timer.StartOnce( 500 );
182 }
183
184 aEvent.Skip();
185}
186
187void LOCAL_HISTORY_PANE::OnLeave( wxMouseEvent& aEvent )
188{
189 std::lock_guard<std::mutex> lock( m_mutex );
190
191 m_timer.Stop();
192
193 if( m_hoverItem != -1 )
194 m_list->SetItemState( m_hoverItem, 0, wxLIST_STATE_SELECTED );
195
196 m_hoverItem = -1;
197
198 if( m_tip )
199 {
200 m_tip->Destroy();
201 m_tip = nullptr;
202 }
203
204 aEvent.Skip();
205}
206
207void LOCAL_HISTORY_PANE::OnTimer( wxTimerEvent& aEvent )
208{
209 std::lock_guard<std::mutex> lock( m_mutex );
210
211 if( m_hoverItem < 0 || m_hoverItem >= static_cast<long>( m_commits.size() ) )
212 return;
213
214 if( m_tip )
215 {
216 m_tip->Destroy();
217 m_tip = nullptr;
218 }
219
220 SetFocus();
221
222 wxString otherLines;
223 wxString title = m_commits[m_hoverItem].message.BeforeFirst( '\n', &otherLines );
224
225 wxString msg = title << "\n\n" << otherLines << "\n\n" << m_commits[m_hoverItem].date.FormatISOCombined();
226
227#if wxCHECK_VERSION( 3, 3, 2 )
228 m_tip = wxTipWindow::New( this, msg, FromDIP( 400 ) );
229#else
230 m_tip = new wxTipWindow( this, msg, FromDIP( 400 ) );
231 m_tip->SetTipWindowPtr( &m_tip );
232#endif
233
234 wxPoint pos = ClientToScreen( m_hoverPos ) + FromDIP( wxSize( 20, 20 ) );
235
236 if( m_tip )
237 m_tip->Position( pos, wxDefaultSize );
238}
239
240void LOCAL_HISTORY_PANE::OnRightClick( wxListEvent& aEvent )
241{
242 long item = aEvent.GetIndex();
243
244 if( item < 0 || item >= static_cast<long>( m_commits.size() ) )
245 return;
246
247 wxMenu menu;
248 wxMenuItem* restore = menu.Append( wxID_ANY, _( "Restore Commit" ) );
249 menu.Bind( wxEVT_MENU,
250 [this, item]( wxCommandEvent& )
251 {
252 m_frame->RestoreCommitFromHistory( m_commits[item].hash );
253 },
254 restore->GetId() );
255 PopupMenu( &menu );
256}
257
258
259void LOCAL_HISTORY_PANE::OnRefreshEvent( wxCommandEvent& aEvent )
260{
261 wxString projectPath = aEvent.GetString();
262
263 if( projectPath.IsEmpty() )
264 projectPath = m_frame->Prj().GetProjectPath();
265
266 RefreshHistory( projectPath );
267}
268
269
270void LOCAL_HISTORY_PANE::OnRefreshTimer( wxTimerEvent& aEvent )
271{
272 // Refresh the history to show updates from autosave
273 RefreshHistory( m_frame->Prj().GetProjectPath() );
274}
The main KiCad project manager frame.
LOCAL_HISTORY_PANE(KICAD_MANAGER_FRAME *aParent)
void OnLeave(wxMouseEvent &aEvent)
void OnRefreshTimer(wxTimerEvent &aEvent)
void OnTimer(wxTimerEvent &aEvent)
void OnRightClick(wxListEvent &aEvent)
KICAD_MANAGER_FRAME * m_frame
void RefreshHistory(const wxString &aProjectPath)
void OnRefreshEvent(wxCommandEvent &aEvent)
std::vector< LOCAL_COMMIT_INFO > m_commits
void OnMotion(wxMouseEvent &aEvent)
#define _(s)
wxDEFINE_EVENT(EVT_LOCAL_HISTORY_REFRESH, wxCommandEvent)
if(intersection)