1 /*
2  * SourceControlPreferencesPane.java
3  *
4  * Copyright (C) 2021 by RStudio, PBC
5  *
6  * Unless you have received this program directly from RStudio pursuant
7  * to the terms of a commercial license agreement with RStudio, then
8  * this program is licensed to you under the terms of version 3 of the
9  * GNU Affero General Public License. This program is distributed WITHOUT
10  * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
11  * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
12  * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
13  *
14  */
15 
16 package org.rstudio.studio.client.workbench.prefs.views;
17 
18 import com.google.gwt.event.logical.shared.ValueChangeEvent;
19 import com.google.gwt.event.logical.shared.ValueChangeHandler;
20 import com.google.gwt.resources.client.ImageResource;
21 import com.google.gwt.user.client.Command;
22 import com.google.gwt.user.client.ui.CheckBox;
23 import com.google.gwt.user.client.ui.HasHorizontalAlignment;
24 import com.google.gwt.user.client.ui.HorizontalPanel;
25 import com.google.gwt.user.client.ui.Label;
26 import com.google.inject.Inject;
27 
28 import org.rstudio.core.client.BrowseCap;
29 import org.rstudio.core.client.ElementIds;
30 import org.rstudio.core.client.prefs.PreferencesDialogBaseResources;
31 import org.rstudio.core.client.prefs.RestartRequirement;
32 import org.rstudio.core.client.resources.ImageResource2x;
33 import org.rstudio.core.client.widget.FileChooserTextBox;
34 import org.rstudio.core.client.widget.FormLabel;
35 import org.rstudio.core.client.widget.MessageDialog;
36 import org.rstudio.core.client.widget.TextBoxWithButton;
37 import org.rstudio.studio.client.application.Desktop;
38 import org.rstudio.studio.client.common.FileDialogs;
39 import org.rstudio.studio.client.common.GlobalDisplay;
40 import org.rstudio.studio.client.common.HelpLink;
41 import org.rstudio.studio.client.common.vcs.GitServerOperations;
42 import org.rstudio.studio.client.common.vcs.SshKeyWidget;
43 import org.rstudio.studio.client.common.vcs.VcsHelpLink;
44 import org.rstudio.studio.client.workbench.commands.Commands;
45 import org.rstudio.studio.client.workbench.model.RemoteFileSystemContext;
46 import org.rstudio.studio.client.workbench.model.Session;
47 import org.rstudio.studio.client.workbench.model.SessionInfo;
48 import org.rstudio.studio.client.workbench.prefs.model.UserPrefs;
49 
50 public class SourceControlPreferencesPane extends PreferencesPane
51 {
52    @Inject
SourceControlPreferencesPane(PreferencesDialogResources res, Session session, GitServerOperations server, final GlobalDisplay globalDisplay, final Commands commands, RemoteFileSystemContext fsContext, FileDialogs fileDialogs)53    public SourceControlPreferencesPane(PreferencesDialogResources res,
54                                        Session session,
55                                        GitServerOperations server,
56                                        final GlobalDisplay globalDisplay,
57                                        final Commands commands,
58                                        RemoteFileSystemContext fsContext,
59                                        FileDialogs fileDialogs)
60    {
61       res_ = res;
62 
63       chkVcsEnabled_ = new CheckBox(
64             "Enable version control interface for RStudio projects");
65       extraSpaced(chkVcsEnabled_);
66       add(chkVcsEnabled_);
67       chkVcsEnabled_.addValueChangeHandler(new ValueChangeHandler<Boolean>() {
68          @Override
69          public void onValueChange(ValueChangeEvent<Boolean> event)
70          {
71             manageControlVisibility();
72 
73             globalDisplay.showMessage(
74                MessageDialog.INFO,
75                (event.getValue() ? "Enable" : "Disable") + " Version Control",
76                "You must restart RStudio for this change to take effect.");
77          }
78       });
79 
80 
81       // git exe path chooser
82       Command onGitExePathChosen = new Command()
83       {
84          @Override
85          public void execute()
86          {
87             if (BrowseCap.isWindowsDesktop())
88             {
89                String gitExePath = gitExePathChooser_.getText();
90                if (!gitExePath.endsWith("git.exe"))
91                {
92                   String message = "The program '" + gitExePath + "'" +
93                      " is unlikely to be a valid git executable.\n" +
94                      "Please select a git executable called 'git.exe'.";
95 
96                   globalDisplay.showMessage(
97                         GlobalDisplay.MSG_WARNING,
98                         "Invalid Git Executable",
99                         message);
100                }
101             }
102          }
103       };
104 
105       gitExePathLabel_ = new FormLabel("Git executable:");
106       gitExePathChooser_ = new FileChooserTextBox(gitExePathLabel_,
107                                                   "(Not Found)",
108                                                   ElementIds.TextBoxButtonId.GIT,
109                                                   false,
110                                                   null,
111                                                   onGitExePathChosen);
112       SessionInfo sessionInfo = session.getSessionInfo();
113       if (sessionInfo.getAllowVcsExeEdit())
114          addTextBoxChooser(gitExePathLabel_, gitExePathChooser_);
115 
116       // svn exe path chooser
117       svnExePathLabel_ = new FormLabel("SVN executable:");
118       svnExePathChooser_ = new FileChooserTextBox(svnExePathLabel_,
119                                                   "(Not Found)",
120                                                   ElementIds.TextBoxButtonId.SVN,
121                                                   false,
122                                                   null,
123                                                   null);
124       if (sessionInfo.getAllowVcsExeEdit())
125          addTextBoxChooser(svnExePathLabel_, svnExePathChooser_);
126 
127       // terminal path
128       terminalPathLabel_ = new FormLabel("Terminal executable:");
129       terminalPathChooser_ = new FileChooserTextBox(terminalPathLabel_,
130                                                     "(Not Found)",
131                                                     ElementIds.TextBoxButtonId.VCS_TERMINAL,
132                                                     false,
133                                                     null,
134                                                     null);
135       if (haveTerminalPathPref())
136          addTextBoxChooser(terminalPathLabel_, terminalPathChooser_);
137 
138       // ssh key widget
139       sshKeyWidget_ = new SshKeyWidget(server, "330px");
140       sshKeyWidget_.addStyleName(res_.styles().sshKeyWidget());
141       nudgeRight(sshKeyWidget_);
142       add(sshKeyWidget_);
143 
144       HelpLink vcsHelpLink = new VcsHelpLink();
145       nudgeRight(vcsHelpLink);
146       vcsHelpLink.addStyleName(res_.styles().newSection());
147       add(vcsHelpLink);
148 
149       chkVcsEnabled_.setEnabled(false);
150       gitExePathChooser_.setEnabled(false);
151       svnExePathChooser_.setEnabled(false);
152       terminalPathChooser_.setEnabled(false);
153    }
154 
155    @Override
initialize(UserPrefs prefs)156    protected void initialize(UserPrefs prefs)
157    {
158       chkVcsEnabled_.setEnabled(true);
159       gitExePathChooser_.setEnabled(true);
160       svnExePathChooser_.setEnabled(true);
161       terminalPathChooser_.setEnabled(true);
162 
163       chkVcsEnabled_.setValue(prefs.vcsEnabled().getValue());
164       gitExePathChooser_.setText(prefs.gitExePath().getValue());
165       svnExePathChooser_.setText(prefs.svnExePath().getValue());
166       terminalPathChooser_.setText(prefs.terminalPath().getValue());
167 
168       sshKeyWidget_.setRsaSshKeyPath(prefs.rsaKeyPath().getValue(),
169                                      prefs.haveRsaKey().getValue());
170       sshKeyWidget_.setProgressIndicator(getProgressIndicator());
171 
172       manageControlVisibility();
173    }
174 
175    @Override
getIcon()176    public ImageResource getIcon()
177    {
178       return new ImageResource2x(PreferencesDialogBaseResources.INSTANCE.iconSourceControl2x());
179    }
180 
181    @Override
validate()182    public boolean validate()
183    {
184       return true;
185    }
186 
187    @Override
getName()188    public String getName()
189    {
190       return "Git/SVN";
191    }
192 
193    @Override
onApply(UserPrefs prefs)194    public RestartRequirement onApply(UserPrefs prefs)
195    {
196       RestartRequirement restartRequirement = super.onApply(prefs);
197 
198       prefs.vcsEnabled().setGlobalValue(chkVcsEnabled_.getValue());
199       prefs.gitExePath().setGlobalValue(gitExePathChooser_.getText());
200       prefs.svnExePath().setGlobalValue(svnExePathChooser_.getText());
201       prefs.terminalPath().setGlobalValue(terminalPathChooser_.getText());
202 
203       return restartRequirement;
204    }
205 
haveTerminalPathPref()206    private boolean haveTerminalPathPref()
207    {
208       return Desktop.isDesktop() && BrowseCap.isLinux();
209    }
210 
addTextBoxChooser(Label captionLabel, TextBoxWithButton chooser)211    private void addTextBoxChooser(Label captionLabel, TextBoxWithButton chooser)
212    {
213       String textWidth = "250px";
214 
215       HorizontalPanel captionPanel = new HorizontalPanel();
216       captionPanel.setWidth(textWidth);
217       nudgeRight(captionPanel);
218 
219       captionPanel.add(captionLabel);
220       captionPanel.setCellHorizontalAlignment(captionLabel,
221             HasHorizontalAlignment.ALIGN_LEFT);
222 
223       add(tight(captionPanel));
224 
225       chooser.setTextWidth(textWidth);
226       nudgeRight(chooser);
227       textBoxWithChooser(chooser);
228       spaced(chooser);
229       add(chooser);
230    }
231 
manageControlVisibility()232    private void manageControlVisibility()
233    {
234       boolean vcsEnabled = chkVcsEnabled_.getValue();
235       gitExePathLabel_.setVisible(vcsEnabled);
236       gitExePathChooser_.setVisible(vcsEnabled);
237       svnExePathLabel_.setVisible(vcsEnabled);
238       svnExePathChooser_.setVisible(vcsEnabled);
239       terminalPathLabel_.setVisible(vcsEnabled);
240       terminalPathChooser_.setVisible(vcsEnabled && haveTerminalPathPref());
241       sshKeyWidget_.setVisible(vcsEnabled);
242    }
243 
244    private final PreferencesDialogResources res_;
245 
246    private final CheckBox chkVcsEnabled_;
247 
248    private FormLabel svnExePathLabel_;
249    private FormLabel gitExePathLabel_;
250    private TextBoxWithButton gitExePathChooser_;
251    private TextBoxWithButton svnExePathChooser_;
252    private FormLabel terminalPathLabel_;
253    private TextBoxWithButton terminalPathChooser_;
254    private SshKeyWidget sshKeyWidget_;
255 }
256