OSDN Git Service

1522fe4a4bf382484f52cca20c7ba9ee35dabbdb
[tortoisegit/TortoiseGitJp.git] / src / TortoiseProc / SyncDlg.cpp
1 // TortoiseGit - a Windows shell extension for easy version control\r
2 \r
3 // Copyright (C) 2008-2009 - TortoiseGit\r
4 \r
5 // This program is free software; you can redistribute it and/or\r
6 // modify it under the terms of the GNU General Public License\r
7 // as published by the Free Software Foundation; either version 2\r
8 // of the License, or (at your option) any later version.\r
9 \r
10 // This program is distributed in the hope that it will be useful,\r
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of\r
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
13 // GNU General Public License for more details.\r
14 \r
15 // You should have received a copy of the GNU General Public License\r
16 // along with this program; if not, write to the Free Software Foundation,\r
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\r
18 //\r
19 \r
20 // SyncDlg.cpp : implementation file\r
21 //\r
22 \r
23 #include "stdafx.h"\r
24 #include "TortoiseProc.h"\r
25 #include "SyncDlg.h"\r
26 #include "progressdlg.h"\r
27 \r
28 // CSyncDlg dialog\r
29 \r
30 IMPLEMENT_DYNAMIC(CSyncDlg, CResizableStandAloneDialog)\r
31 \r
32 CSyncDlg::CSyncDlg(CWnd* pParent /*=NULL*/)\r
33         : CResizableStandAloneDialog(CSyncDlg::IDD, pParent)\r
34         , m_bAutoLoadPuttyKey(FALSE)\r
35 {\r
36         m_pTooltip=&this->m_tooltips;\r
37         m_bInited=false;\r
38 }\r
39 \r
40 CSyncDlg::~CSyncDlg()\r
41 {\r
42 }\r
43 \r
44 void CSyncDlg::DoDataExchange(CDataExchange* pDX)\r
45 {\r
46         CDialog::DoDataExchange(pDX);\r
47         DDX_Check(pDX, IDC_CHECK_PUTTY_KEY, m_bAutoLoadPuttyKey);\r
48         DDX_Control(pDX, IDC_COMBOBOXEX_URL, m_ctrlURL);\r
49         DDX_Control(pDX, IDC_BUTTON_TABCTRL, m_ctrlDumyButton);\r
50         DDX_Control(pDX, IDC_BUTTON_PULL, m_ctrlPull);\r
51         DDX_Control(pDX, IDC_BUTTON_PUSH, m_ctrlPush);\r
52         DDX_Control(pDX, IDC_STATIC_STATUS, m_ctrlStatus);\r
53         DDX_Control(pDX, IDC_PROGRESS_SYNC, m_ctrlProgress);\r
54         DDX_Control(pDX, IDC_ANIMATE_SYNC, m_ctrlAnimate);\r
55 \r
56         BRANCH_COMBOX_DDX;\r
57 }\r
58 \r
59 \r
60 BEGIN_MESSAGE_MAP(CSyncDlg, CResizableStandAloneDialog)\r
61         ON_BN_CLICKED(IDC_BUTTON_PULL, &CSyncDlg::OnBnClickedButtonPull)\r
62         ON_BN_CLICKED(IDC_BUTTON_PUSH, &CSyncDlg::OnBnClickedButtonPush)\r
63         ON_BN_CLICKED(IDC_BUTTON_APPLY, &CSyncDlg::OnBnClickedButtonApply)\r
64         ON_BN_CLICKED(IDC_BUTTON_EMAIL, &CSyncDlg::OnBnClickedButtonEmail)\r
65         ON_BN_CLICKED(IDC_BUTTON_MANAGE, &CSyncDlg::OnBnClickedButtonManage)\r
66         BRANCH_COMBOX_EVENT\r
67         ON_NOTIFY(CBEN_ENDEDIT, IDC_COMBOBOXEX_URL, &CSyncDlg::OnCbenEndeditComboboxexUrl)\r
68         ON_CBN_EDITCHANGE(IDC_COMBOBOXEX_URL, &CSyncDlg::OnCbnEditchangeComboboxexUrl)\r
69         ON_MESSAGE(MSG_PROGRESSDLG_UPDATE_UI, OnProgressUpdateUI)\r
70 END_MESSAGE_MAP()\r
71 \r
72 \r
73 void CSyncDlg::EnableControlButton(bool bEnabled)\r
74 {\r
75         GetDlgItem(IDC_BUTTON_PULL)->EnableWindow(bEnabled);\r
76         GetDlgItem(IDC_BUTTON_PUSH)->EnableWindow(bEnabled);\r
77         GetDlgItem(IDC_BUTTON_APPLY)->EnableWindow(bEnabled);\r
78         GetDlgItem(IDC_BUTTON_EMAIL)->EnableWindow(bEnabled);\r
79         GetDlgItem(IDOK)->EnableWindow(bEnabled);\r
80 }\r
81 // CSyncDlg message handlers\r
82 \r
83 void CSyncDlg::OnBnClickedButtonPull()\r
84 {\r
85         // TODO: Add your control notification handler code here\r
86         this->m_regPullButton =this->m_ctrlPull.GetCurrentEntry();\r
87 }\r
88 \r
89 void CSyncDlg::OnBnClickedButtonPush()\r
90 {\r
91         // TODO: Add your control notification handler code here\r
92         this->m_regPushButton=this->m_ctrlPush.GetCurrentEntry();\r
93         this->SwitchToRun();\r
94         this->m_bAbort=false;\r
95         this->m_GitCmdList.clear();\r
96 \r
97         CString cmd;\r
98         CString tags;\r
99         CString force;\r
100         this->m_strLocalBranch = this->m_ctrlLocalBranch.GetString();\r
101         this->m_ctrlRemoteBranch.GetWindowText(this->m_strRemoteBranch);\r
102         this->m_ctrlURL.GetWindowText(this->m_strURL);\r
103         m_strRemoteBranch=m_strRemoteBranch.Trim();\r
104         \r
105         cmd.Format(_T("git.exe push %s %s \"%s\" %s"),\r
106                                 tags,force,\r
107                                 m_strURL,\r
108                                 m_strLocalBranch);\r
109 \r
110         if (!m_strRemoteBranch.IsEmpty())\r
111         {\r
112                 cmd += _T(":") + m_strRemoteBranch;\r
113         }\r
114         \r
115         m_GitCmdList.push_back(cmd);\r
116 \r
117         m_pThread = AfxBeginThread(ProgressThreadEntry, this, THREAD_PRIORITY_NORMAL,0,CREATE_SUSPENDED);\r
118         if (m_pThread==NULL)\r
119         {\r
120 //              ReportError(CString(MAKEINTRESOURCE(IDS_ERR_THREADSTARTFAILED)));\r
121         }\r
122         else\r
123         {\r
124                 m_pThread->m_bAutoDelete = TRUE;\r
125                 m_pThread->ResumeThread();\r
126         }\r
127         \r
128 }\r
129 \r
130 void CSyncDlg::OnBnClickedButtonApply()\r
131 {\r
132         // TODO: Add your control notification handler code here\r
133 }\r
134 \r
135 void CSyncDlg::OnBnClickedButtonEmail()\r
136 {\r
137         // TODO: Add your control notification handler code here\r
138 }\r
139 void CSyncDlg::ShowProgressCtrl(bool bShow)\r
140 {\r
141         int b=bShow?SW_NORMAL:SW_HIDE;\r
142         this->m_ctrlAnimate.ShowWindow(b);\r
143         this->m_ctrlProgress.ShowWindow(b);\r
144         this->m_ctrlAnimate.Open(IDR_DOWNLOAD);\r
145         if(b == SW_NORMAL)\r
146                 this->m_ctrlAnimate.Play(0,-1,-1);\r
147         else\r
148                 this->m_ctrlAnimate.Stop();\r
149 }\r
150 void CSyncDlg::ShowInputCtrl(bool bShow)\r
151 {\r
152         int b=bShow?SW_NORMAL:SW_HIDE;\r
153         this->m_ctrlURL.ShowWindow(b);\r
154         this->m_ctrlLocalBranch.ShowWindow(b);\r
155         this->m_ctrlRemoteBranch.ShowWindow(b);\r
156         this->GetDlgItem(IDC_BUTTON_LOCAL_BRANCH)->ShowWindow(b);\r
157         this->GetDlgItem(IDC_BUTTON_REMOTE_BRANCH)->ShowWindow(b);\r
158         this->GetDlgItem(IDC_STATIC_LOCAL_BRANCH)->ShowWindow(b);\r
159         this->GetDlgItem(IDC_STATIC_REMOTE_BRANCH)->ShowWindow(b);\r
160         this->GetDlgItem(IDC_BUTTON_MANAGE)->ShowWindow(b);\r
161         this->GetDlgItem(IDC_CHECK_PUTTY_KEY)->ShowWindow(b);\r
162         this->GetDlgItem(IDC_CHECK_FORCE)->ShowWindow(b);\r
163         this->GetDlgItem(IDC_STATIC_REMOTE_URL)->ShowWindow(b);\r
164         \r
165 }\r
166 BOOL CSyncDlg::OnInitDialog()\r
167 {\r
168         CResizableStandAloneDialog::OnInitDialog();\r
169 \r
170         /*\r
171         this->m_ctrlAnimate.ShowWindow(SW_NORMAL);\r
172         this->m_ctrlAnimate.Open(IDR_DOWNLOAD);\r
173         this->m_ctrlAnimate.Play(0,-1,-1);\r
174     */\r
175 \r
176         // ------------------ Create Tabctrl -----------\r
177         CWnd *pwnd=this->GetDlgItem(IDC_BUTTON_TABCTRL);\r
178         CRect rectDummy;\r
179         pwnd->GetWindowRect(&rectDummy);\r
180         this->ScreenToClient(rectDummy);\r
181 \r
182         if (!m_ctrlTabCtrl.Create(CMFCTabCtrl::STYLE_FLAT, rectDummy, this, IDC_SYNC_TAB))\r
183         {\r
184                 TRACE0("Failed to create output tab window\n");\r
185                 return FALSE;      // fail to create\r
186         }\r
187         m_ctrlTabCtrl.SetResizeMode(CMFCTabCtrl::RESIZE_NO);\r
188 \r
189         // -------------Create Command Log Ctrl ---------\r
190         DWORD dwStyle;\r
191         dwStyle= ES_MULTILINE | ES_READONLY | WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL | ES_AUTOVSCROLL |WS_VSCROLL  ;\r
192 \r
193         if( !m_ctrlCmdOut.Create(dwStyle,rectDummy,&m_ctrlTabCtrl,IDC_CMD_LOG))\r
194         {\r
195                 TRACE0("Failed to create Log commits window\n");\r
196                 return FALSE;      // fail to create\r
197         }\r
198 \r
199         m_ctrlTabCtrl.InsertTab(&m_ctrlCmdOut,_T("Log"),-1);\r
200         m_ctrlCmdOut.ReplaceSel(_T("Hello"));\r
201 \r
202         //----------  Create Commit List Ctrl---------------\r
203                         \r
204         dwStyle =LVS_REPORT | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | LVS_OWNERDATA | WS_BORDER | WS_TABSTOP | WS_CHILD | WS_VISIBLE;;\r
205 \r
206         if( !m_OutLogList.Create(dwStyle,rectDummy,&m_ctrlTabCtrl,IDC_OUT_LOGLIST))\r
207         {\r
208                 TRACE0("Failed to create output commits window\n");\r
209                 return FALSE;      // fail to create\r
210 \r
211         }\r
212 \r
213         m_ctrlTabCtrl.InsertTab(&m_OutLogList,_T("Out Commits"),-1);\r
214 \r
215         m_OutLogList.InsertGitColumn();\r
216 \r
217         //------------- Create Change File List Control ----------------\r
218 \r
219         dwStyle = LVS_REPORT | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP |LVS_SINGLESEL |WS_CHILD | WS_VISIBLE;\r
220         \r
221         if( !m_OutChangeFileList.Create(dwStyle,rectDummy,&m_ctrlTabCtrl,IDC_OUT_CHANGELIST))\r
222         {\r
223                 TRACE0("Failed to create output change files window\n");\r
224                 return FALSE;      // fail to create\r
225         }\r
226         m_ctrlTabCtrl.InsertTab(&m_OutChangeFileList,_T("Out ChangeList"),-1);\r
227 \r
228         m_OutChangeFileList.Init(SVNSLC_COLEXT | SVNSLC_COLSTATUS |SVNSLC_COLADD|SVNSLC_COLDEL , _T("OutSyncDlg"),\r
229                                     (CGitStatusListCtrl::GetContextMenuBit(CGitStatusListCtrl::IDSVNLC_COMPARETWO)|\r
230                                                         CGitStatusListCtrl::GetContextMenuBit(CGitStatusListCtrl::IDSVNLC_GNUDIFF2)),false);\r
231 \r
232         this->m_tooltips.Create(this);\r
233 \r
234         AddAnchor(IDC_SYNC_TAB,TOP_LEFT,BOTTOM_RIGHT);\r
235 \r
236         AddAnchor(IDC_GROUP_INFO,TOP_LEFT,TOP_RIGHT);\r
237         AddAnchor(IDC_COMBOBOXEX_URL,TOP_LEFT,TOP_RIGHT);\r
238         AddAnchor(IDC_BUTTON_MANAGE,TOP_RIGHT);\r
239         AddAnchor(IDC_BUTTON_PULL,BOTTOM_LEFT);\r
240         AddAnchor(IDC_BUTTON_PUSH,BOTTOM_LEFT);\r
241         AddAnchor(IDC_BUTTON_APPLY,BOTTOM_LEFT);\r
242         AddAnchor(IDC_BUTTON_EMAIL,BOTTOM_LEFT);\r
243         AddAnchor(IDC_PROGRESS_SYNC,TOP_LEFT,TOP_RIGHT);\r
244         AddAnchor(IDOK,BOTTOM_RIGHT);\r
245         AddAnchor(IDHELP,BOTTOM_RIGHT);\r
246         AddAnchor(IDC_STATIC_STATUS,BOTTOM_LEFT);\r
247         AddAnchor(IDC_ANIMATE_SYNC,TOP_LEFT);\r
248         \r
249         BRANCH_COMBOX_ADD_ANCHOR();\r
250 \r
251         CString WorkingDir=g_Git.m_CurrentDir;\r
252         WorkingDir.Replace(_T(':'),_T('_'));\r
253         m_RegKeyRemoteBranch = CString(_T("Software\\TortoiseGit\\History\\SyncBranch\\"))+WorkingDir;\r
254 \r
255 \r
256         this->AddOthersToAnchor();\r
257         // TODO:  Add extra initialization here\r
258 \r
259         this->m_ctrlPush.AddEntry(CString(_T("Push")));\r
260         this->m_ctrlPush.AddEntry(CString(_T("Push tags")));\r
261         this->m_ctrlPush.AddEntry(CString(_T("Push All")));\r
262 \r
263         this->m_ctrlPull.AddEntry(CString(_T("&Pull")));\r
264         this->m_ctrlPull.AddEntry(CString(_T("&Fetch")));\r
265         this->m_ctrlPull.AddEntry(CString(_T("Fetch&&Rebase")));\r
266 \r
267         \r
268         WorkingDir.Replace(_T(':'),_T('_'));\r
269 \r
270         CString regkey ;\r
271         regkey.Format(_T("Software\\TortoiseGit\\TortoiseProc\\Sync\\%s"),WorkingDir);\r
272 \r
273         this->m_regPullButton = CRegDWORD(regkey+_T("\\Pull"),0);\r
274         this->m_regPushButton = CRegDWORD(regkey+_T("\\Push"),0);\r
275 \r
276         this->m_ctrlPull.SetCurrentEntry(this->m_regPullButton);\r
277         this->m_ctrlPush.SetCurrentEntry(this->m_regPushButton);\r
278 \r
279         CString str;\r
280         this->GetWindowText(str);\r
281         str += _T(" - ") + g_Git.m_CurrentDir;\r
282         this->SetWindowText(str);\r
283 \r
284         EnableSaveRestore(_T("SyncDlg"));\r
285 \r
286         this->LoadBranchInfo();\r
287 \r
288         this->m_bInited=true;\r
289         FetchOutList();\r
290         \r
291         return TRUE;  // return TRUE unless you set the focus to a control\r
292         // EXCEPTION: OCX Property Pages should return FALSE\r
293 }\r
294 \r
295 void CSyncDlg::OnBnClickedButtonManage()\r
296 {\r
297         // TODO: Add your control notification handler code here\r
298         CAppUtils::LaunchRemoteSetting();\r
299 }\r
300 \r
301 BOOL CSyncDlg::PreTranslateMessage(MSG* pMsg)\r
302 {\r
303         // TODO: Add your specialized code here and/or call the base class\r
304         m_tooltips.RelayEvent(pMsg);\r
305         return __super::PreTranslateMessage(pMsg);\r
306 }\r
307 void CSyncDlg::FetchOutList()\r
308 {\r
309         if(!m_bInited)\r
310                 return;\r
311         m_OutChangeFileList.Clear();\r
312         this->m_OutLogList.Clear();\r
313 \r
314         CString remote;\r
315         this->m_ctrlURL.GetWindowText(remote);\r
316         CString remotebranch;\r
317         this->m_ctrlRemoteBranch.GetWindowText(remotebranch);\r
318         remotebranch=remote+_T("/")+remotebranch;\r
319 \r
320         if(IsURL())\r
321         {\r
322                 CString str;\r
323                 str=_T("Don't know what will push befause you enter URL");\r
324                 m_OutLogList.ShowText(str);\r
325                 this->m_ctrlTabCtrl.ShowTab(m_OutChangeFileList.GetDlgCtrlID()-1,FALSE);\r
326                 m_OutLocalBranch.Empty();\r
327                 m_OutRemoteBranch.Empty();\r
328                 return ;\r
329         \r
330         }else if(g_Git.GetHash(remotebranch).GetLength()<40)\r
331         {\r
332                 CString str;\r
333                 str.Format(_T("Don't know what will push befause unkown \"%s\""),remotebranch);\r
334                 m_OutLogList.ShowText(str);\r
335                 this->m_ctrlTabCtrl.ShowTab(m_OutChangeFileList.GetDlgCtrlID()-1,FALSE);\r
336                 m_OutLocalBranch.Empty();\r
337                 m_OutRemoteBranch.Empty();\r
338                 return ;\r
339         }\r
340         else\r
341         {\r
342                 CString localbranch;\r
343                 localbranch=this->m_ctrlLocalBranch.GetString();\r
344 \r
345                 if(localbranch != m_OutLocalBranch || m_OutRemoteBranch != remotebranch)\r
346                 {\r
347                         m_OutLogList.ClearText();\r
348                         m_OutLogList.FillGitLog(NULL,CGit::     LOG_INFO_STAT| CGit::LOG_INFO_FILESTATE | CGit::LOG_INFO_SHOW_MERGEDFILE,\r
349                                 &remotebranch,&localbranch);\r
350                         \r
351                         CString str;\r
352                         if(m_OutLogList.GetItemCount() == 0)\r
353                         {                       \r
354                                 str.Format(_T("No commits ahead \"%s\""),remotebranch);\r
355                                 m_OutLogList.ShowText(str);\r
356                                 this->m_ctrlStatus.SetWindowText(str);\r
357                                 this->m_ctrlTabCtrl.ShowTab(m_OutChangeFileList.GetDlgCtrlID()-1,FALSE);\r
358                         }\r
359                         else\r
360                         {\r
361                                 str.Format(_T("%d commits ahead \"%s\""),m_OutLogList.GetItemCount(),remotebranch);\r
362                                 this->m_ctrlStatus.SetWindowText(str);\r
363                                 g_Git.GetCommitDiffList(localbranch,remotebranch,m_arOutChangeList);\r
364                                 m_OutChangeFileList.m_Rev1=localbranch;\r
365                                 m_OutChangeFileList.m_Rev2=remotebranch;\r
366                                 m_OutChangeFileList.Show(0,this->m_arOutChangeList);\r
367                                 m_OutChangeFileList.SetEmptyString(CString(_T("No changed file")));\r
368                                 this->m_ctrlTabCtrl.ShowTab(m_OutChangeFileList.GetDlgCtrlID()-1,TRUE);\r
369                         }\r
370                 }\r
371                 this->m_OutLocalBranch=localbranch;\r
372                 this->m_OutRemoteBranch=remotebranch;\r
373         }\r
374 \r
375 }\r
376 \r
377 bool CSyncDlg::IsURL()\r
378 {\r
379         CString str;\r
380         this->m_ctrlURL.GetWindowText(str);\r
381         if(str.Find(_T('\\'))>=0 || str.Find(_T('/'))>=0)\r
382                 return true;\r
383         else\r
384                 return false;\r
385 }\r
386 void CSyncDlg::OnCbenEndeditComboboxexUrl(NMHDR *pNMHDR, LRESULT *pResult)\r
387 {\r
388         // TODO: Add your control notification handler code here\r
389         *pResult = 0;\r
390 }\r
391 \r
392 void CSyncDlg::OnCbnEditchangeComboboxexUrl()\r
393 {\r
394         this->FetchOutList();\r
395         // TODO: Add your control notification handler code here\r
396 }\r
397 \r
398 UINT CSyncDlg::ProgressThread()\r
399 {\r
400         m_GitCmdStatus=CProgressDlg::RunCmdList(this,m_GitCmdList,true,NULL,&this->m_bAbort);\r
401         return 0;\r
402 }\r
403 \r
404 \r
405 LRESULT CSyncDlg::OnProgressUpdateUI(WPARAM wParam,LPARAM lParam)\r
406 {\r
407         if(wParam == MSG_PROGRESSDLG_START)\r
408         {\r
409                 m_ctrlAnimate.Play(0,-1,-1);\r
410                 this->m_ctrlProgress.SetPos(0);\r
411         }\r
412 \r
413         if(wParam == MSG_PROGRESSDLG_END || wParam == MSG_PROGRESSDLG_FAILED)\r
414         {\r
415                 //m_bDone = true;\r
416                 m_ctrlAnimate.Stop();\r
417                 m_ctrlProgress.SetPos(100);\r
418                 //this->DialogEnableWindow(IDOK,TRUE);\r
419 \r
420                 if(wParam == MSG_PROGRESSDLG_END)\r
421                 {\r
422                         EnableControlButton(true);\r
423                         SwitchToInput();\r
424                 }\r
425         }\r
426 \r
427         if(lParam != 0)\r
428                 ParserCmdOutput((TCHAR)lParam);\r
429 \r
430         return 0;\r
431 }\r
432 \r
433 void CSyncDlg::ParserCmdOutput(TCHAR ch)\r
434 {\r
435         TRACE(_T("%c"),ch);\r
436         if( ch == _T('\r') || ch == _T('\n'))\r
437         {\r
438                 TRACE(_T("End Char %s \r\n"),ch==_T('\r')?_T("lf"):_T(""));\r
439                 TRACE(_T("End Char %s \r\n"),ch==_T('\n')?_T("cr"):_T(""));\r
440 \r
441                 int linenum = this->m_ctrlCmdOut.GetLineCount();\r
442                 int index ;\r
443                 if(ch == _T('\r'))\r
444                 {\r
445                         index = this->m_ctrlCmdOut.LineIndex(linenum-1);\r
446                         \r
447                         if(linenum == 0)\r
448                                 index = 0;\r
449                 }\r
450                 else\r
451                 {\r
452                         index=-1;\r
453                 }\r
454                 \r
455 \r
456                 this->m_ctrlCmdOut.SetSel(index,-1);\r
457                         \r
458                 this->m_ctrlCmdOut.ReplaceSel(CString(_T("\n"))+m_LogText);\r
459                 \r
460                 this->m_ctrlCmdOut.LineScroll(linenum-1);\r
461                 \r
462                 int s1=m_LogText.Find(_T(':'));\r
463                 int s2=m_LogText.Find(_T('%'));\r
464                 if(s1>0 && s2>0)\r
465                 {\r
466                 //      this->m_CurrentWork.SetWindowTextW(m_LogText.Left(s1));\r
467                         int pos=CProgressDlg::FindPercentage(m_LogText);\r
468                         TRACE(_T("Pos %d\r\n"),pos);\r
469                         if(pos>0)\r
470                                 this->m_ctrlProgress.SetPos(pos);\r
471                 }\r
472 \r
473                 m_LogText=_T("");\r
474 \r
475         }else\r
476         {\r
477                 m_LogText+=ch;\r
478         }\r
479 }