1 /*
2  * RMarkdownPreferencesPane.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 package org.rstudio.studio.client.workbench.prefs.views;
16 
17 import com.google.gwt.dom.client.Style.Unit;
18 import com.google.gwt.resources.client.ImageResource;
19 import com.google.gwt.user.client.Command;
20 import com.google.gwt.user.client.ui.CheckBox;
21 import com.google.gwt.user.client.ui.Label;
22 import com.google.gwt.user.client.ui.VerticalPanel;
23 import com.google.inject.Inject;
24 
25 import elemental2.core.JsObject;
26 import jsinterop.base.Js;
27 
28 import org.rstudio.core.client.Debug;
29 import org.rstudio.core.client.ElementIds;
30 import org.rstudio.core.client.MessageDisplay;
31 import org.rstudio.core.client.prefs.RestartRequirement;
32 import org.rstudio.core.client.resources.ImageResource2x;
33 import org.rstudio.core.client.theme.DialogTabLayoutPanel;
34 import org.rstudio.core.client.theme.VerticalTabPanel;
35 import org.rstudio.core.client.widget.DirectoryChooserTextBox;
36 import org.rstudio.core.client.widget.HelpButton;
37 import org.rstudio.core.client.widget.NumericValueWidget;
38 import org.rstudio.core.client.widget.Operation;
39 import org.rstudio.core.client.widget.SelectWidget;
40 import org.rstudio.studio.client.RStudioGinjector;
41 import org.rstudio.studio.client.common.FileDialogs;
42 import org.rstudio.studio.client.common.HelpLink;
43 import org.rstudio.studio.client.panmirror.server.PanmirrorZoteroLocalConfig;
44 import org.rstudio.studio.client.panmirror.server.PanmirrorZoteroServerOperations;
45 import org.rstudio.studio.client.server.ServerError;
46 import org.rstudio.studio.client.server.ServerRequestCallback;
47 import org.rstudio.studio.client.workbench.model.RemoteFileSystemContext;
48 import org.rstudio.studio.client.workbench.model.Session;
49 import org.rstudio.studio.client.workbench.prefs.model.UserPrefs;
50 import org.rstudio.studio.client.workbench.prefs.model.UserPrefsAccessor;
51 import org.rstudio.studio.client.workbench.prefs.model.UserState;
52 import org.rstudio.studio.client.workbench.prefs.model.UserStateAccessor;
53 import org.rstudio.studio.client.workbench.prefs.views.zotero.ZoteroApiKeyWidget;
54 import org.rstudio.studio.client.workbench.prefs.views.zotero.ZoteroConnectionWidget;
55 import org.rstudio.studio.client.workbench.prefs.views.zotero.ZoteroLibrariesWidget;
56 
57 public class RMarkdownPreferencesPane extends PreferencesPane
58 {
59    @Inject
RMarkdownPreferencesPane(UserPrefs prefs, UserState state, PreferencesDialogResources res, Session session, RemoteFileSystemContext fsContext, FileDialogs fileDialogs, PanmirrorZoteroServerOperations zoteroServer)60    public RMarkdownPreferencesPane(UserPrefs prefs,
61                                    UserState state,
62                                    PreferencesDialogResources res,
63                                    Session session,
64                                    RemoteFileSystemContext fsContext,
65                                    FileDialogs fileDialogs,
66                                    PanmirrorZoteroServerOperations zoteroServer)
67    {
68       prefs_ = prefs;
69       state_ = state;
70       res_ = res;
71 
72       VerticalTabPanel basic = new VerticalTabPanel(ElementIds.RMARKDOWN_BASIC_PREFS);
73 
74       basic.add(headerLabel("R Markdown"));
75 
76       basic.add(checkboxPref("Show document outline by default", prefs_.showDocOutlineRmd()));
77       basic.add(checkboxPref("Soft-wrap R Markdown files", prefs_.softWrapRmdFiles()));
78 
79       docOutlineDisplay_ = new SelectWidget(
80             "Show in document outline: ",
81             new String[] {
82                   "Sections Only",
83                   "Sections and Named Chunks",
84                   "Sections and All Chunks"
85             },
86             new String[] {
87                  UserPrefs.DOC_OUTLINE_SHOW_SECTIONS_ONLY,
88                  UserPrefs.DOC_OUTLINE_SHOW_SECTIONS_AND_CHUNKS,
89                  UserPrefs.DOC_OUTLINE_SHOW_ALL
90             },
91             false,
92             true,
93             false);
94       basic.add(docOutlineDisplay_);
95 
96       rmdViewerMode_ = new SelectWidget(
97             "Show output preview in: ",
98             new String[] {
99                   "Window",
100                   "Viewer Pane",
101                   "(None)"
102             },
103             new String[] {
104                   UserPrefs.RMD_VIEWER_TYPE_WINDOW,
105                   UserPrefs.RMD_VIEWER_TYPE_PANE,
106                   UserPrefs.RMD_VIEWER_TYPE_NONE
107             },
108             false,
109             true,
110             false);
111       basic.add(rmdViewerMode_);
112 
113 
114       // show output inline for all Rmds
115       final CheckBox rmdInlineOutput = checkboxPref(
116             "Show output inline for all R Markdown documents",
117             prefs_.rmdChunkOutputInline());
118       basic.add(rmdInlineOutput);
119 
120       // behavior for latex and image preview popups
121       latexPreviewWidget_ = new SelectWidget(
122             "Show equation and image previews: ",
123             new String[] {
124                   "Never",
125                   "In a popup",
126                   "Inline"
127             },
128             new String[] {
129                   UserPrefs.LATEX_PREVIEW_ON_CURSOR_IDLE_NEVER,
130                   UserPrefs.LATEX_PREVIEW_ON_CURSOR_IDLE_INLINE_ONLY,
131                   UserPrefs.LATEX_PREVIEW_ON_CURSOR_IDLE_ALWAYS
132             },
133             false,
134             true,
135             false);
136       basic.add(latexPreviewWidget_);
137 
138       if (session.getSessionInfo().getKnitWorkingDirAvailable())
139       {
140          knitWorkingDir_ = new SelectWidget(
141                "Evaluate chunks in directory: ",
142                new String[] {
143                      "Document",
144                      "Current",
145                      "Project"
146                },
147                new String[] {
148                      UserPrefs.KNIT_WORKING_DIR_DEFAULT,
149                      UserPrefs.KNIT_WORKING_DIR_CURRENT,
150                      UserPrefs.KNIT_WORKING_DIR_PROJECT
151                },
152                false,
153                true,
154                false);
155          basic.add(knitWorkingDir_);
156       }
157       else
158       {
159          knitWorkingDir_ = null;
160       }
161 
162       basic.add(spacedBefore(headerLabel("R Notebooks")));
163 
164       // auto-execute the setup chunk
165       final CheckBox autoExecuteSetupChunk = checkboxPref(
166             "Execute setup chunk automatically in notebooks",
167             prefs_.autoRunSetupChunk());
168       basic.add(autoExecuteSetupChunk);
169 
170       // hide console when executing notebook chunks
171       final CheckBox notebookHideConsole = checkboxPref(
172             "Hide console automatically when executing " +
173             "notebook chunks",
174             prefs_.hideConsoleOnChunkExecute());
175       basic.add(notebookHideConsole);
176 
177       basic.add(spacedBefore(new HelpLink("Using R Notebooks", "using_notebooks")));
178 
179       VerticalTabPanel advanced = new VerticalTabPanel(ElementIds.RMARKDOWN_ADVANCED_PREFS);
180       advanced.add(headerLabel("Display"));
181       advanced.add(checkboxPref("Enable chunk background highlight", prefs_.highlightCodeChunks()));
182       advanced.add(checkboxPref("Show inline toolbar for R code chunks", prefs_.showInlineToolbarForRCodeChunks()));
183       final CheckBox showRmdRenderCommand = checkboxPref( "Display render command in R Markdown tab",
184             prefs_.showRmdRenderCommand());
185       advanced.add(showRmdRenderCommand);
186 
187       VerticalTabPanel visualMode = new VerticalTabPanel(ElementIds.RMARKDOWN_VISUAL_MODE_PREFS);
188 
189       visualMode.add(headerLabel("General"));
190 
191       CheckBox visualMarkdownIsDefault = checkboxPref(
192             "Use visual editor by default for new documents",
193             prefs_.visualMarkdownEditingIsDefault());
194       visualMarkdownIsDefault.getElement().getStyle().setMarginBottom(10, Unit.PX);
195       visualMode.add(visualMarkdownIsDefault);
196 
197       HelpLink visualModeHelpLink = new HelpLink(
198          "Learn more about visual editing mode",
199          "visual_markdown_editing",
200          false // no version info
201       );
202       nudgeRight(visualModeHelpLink);
203       spaced(visualModeHelpLink);
204       visualMode.add(visualModeHelpLink);
205 
206 
207       visualMode.add(headerLabel("Display"));
208 
209 
210       VerticalPanel visualModeOptions = new VerticalPanel();
211 
212 
213 
214       // show outline
215       CheckBox visualEditorShowOutline = checkboxPref(
216             "Show document outline by default",
217             prefs_.visualMarkdownEditingShowDocOutline(),
218             false);
219       lessSpaced(visualEditorShowOutline);
220       visualModeOptions.add(visualEditorShowOutline);
221 
222       // show margin
223       CheckBox visualEditorShowMargin = checkboxPref(
224             "Show margin column indicator in code blocks",
225             prefs_.visualMarkdownEditingShowMargin(),
226             false);
227       lessSpaced(visualEditorShowMargin);
228       visualModeOptions.add(visualEditorShowMargin);
229 
230 
231       // content width
232       visualModeContentWidth_ = numericPref(
233             "Editor content width (px):",
234             100,
235             NumericValueWidget.NoMaximum,
236             prefs_.visualMarkdownEditingMaxContentWidth(),
237             false
238          );
239       visualModeContentWidth_.setWidth("42px");
240       visualModeContentWidth_.setLimits(100, NumericValueWidget.NoMaximum);
241       lessSpaced(visualModeContentWidth_);
242       visualModeOptions.add(nudgeRightPlus(visualModeContentWidth_));
243 
244       // font size
245       final String kDefault = "(Default)";
246       String[] labels = {kDefault, "8", "9", "10", "11", "12",};
247       String[] values = new String[labels.length];
248       for (int i = 0; i < labels.length; i++)
249       {
250          if (labels[i].equals(kDefault))
251             values[i] = "0";
252          else
253             values[i] = Double.parseDouble(labels[i]) + "";
254       }
255       visualModeFontSize_ = new SelectWidget("Editor font size:", labels, values, false, true, false);
256       if (!visualModeFontSize_.setValue(prefs_.visualMarkdownEditingFontSizePoints().getGlobalValue() + ""))
257          visualModeFontSize_.getListBox().setSelectedIndex(0);
258       visualModeFontSize_.getElement().getStyle().setMarginTop(3, Unit.PX);
259       visualModeFontSize_.getElement().getStyle().setMarginBottom(15, Unit.PX);
260       visualModeOptions.add(visualModeFontSize_);
261 
262 
263       visualModeOptions.add(headerLabel("Markdown"));
264 
265 
266       // list spacing
267       String[] listSpacingValues = {
268          UserPrefsAccessor.VISUAL_MARKDOWN_EDITING_LIST_SPACING_TIGHT,
269          UserPrefsAccessor.VISUAL_MARKDOWN_EDITING_LIST_SPACING_SPACED
270       };
271       visualModeListSpacing_ = new SelectWidget("Default spacing between list items: ",
272             listSpacingValues, listSpacingValues,
273             false, true, false);
274       visualModeListSpacing_.getElement().getStyle().setMarginBottom(10, Unit.PX);
275       if (!visualModeListSpacing_.setValue(prefs_.visualMarkdownEditingListSpacing().getGlobalValue()))
276          visualModeListSpacing_.getListBox().setSelectedIndex(0);
277       visualModeOptions.add(visualModeListSpacing_);
278 
279       // auto wrap
280       String[] wrapValues = {
281          UserPrefsAccessor.VISUAL_MARKDOWN_EDITING_WRAP_NONE,
282          UserPrefsAccessor.VISUAL_MARKDOWN_EDITING_WRAP_COLUMN,
283          UserPrefsAccessor.VISUAL_MARKDOWN_EDITING_WRAP_SENTENCE
284       };
285       visualModeWrap_ = new SelectWidget("Automatic text wrapping (line breaks): ", wrapValues, wrapValues, false, true, false);
286       if (!visualModeWrap_.setValue(prefs_.visualMarkdownEditingWrap().getGlobalValue()))
287          visualModeWrap_.getListBox().setSelectedIndex(0);
288       HelpButton.addHelpButton(visualModeWrap_, "visual_markdown_editing-line-wrapping", "Learn more about automatic line wrapping", 0);
289       visualModeWrap_.addStyleName(res.styles().visualModeWrapSelectWidget());
290       visualModeOptions.add(visualModeWrap_);
291 
292       visualModeOptions.add(indent(visualModeWrapColumn_ = numericPref(
293           "Wrap at column:", 1, UserPrefs.MAX_WRAP_COLUMN,
294           prefs.visualMarkdownEditingWrapAtColumn()
295       )));
296       visualModeWrapColumn_.getElement().getStyle().setMarginBottom(8, Unit.PX);
297       visualModeWrapColumn_.setWidth("36px");
298       Command manageWrapColumn = () -> {
299          boolean wrapAtColumn = visualModeWrap_.getValue().equals(UserPrefsAccessor.VISUAL_MARKDOWN_EDITING_WRAP_COLUMN);
300          visualModeWrapColumn_.setVisible(wrapAtColumn);
301          visualModeWrap_.getElement().getStyle().setMarginBottom(wrapAtColumn ? 2 : 8, Unit.PX);
302       };
303       manageWrapColumn.execute();
304       visualModeWrap_.addChangeHandler((arg) -> {
305          manageWrapColumn.execute();
306       });
307       lessSpaced(visualModeWrapColumn_);
308 
309       // references
310       String[] referencesValues = {
311          UserPrefsAccessor.VISUAL_MARKDOWN_EDITING_REFERENCES_LOCATION_BLOCK,
312          UserPrefsAccessor.VISUAL_MARKDOWN_EDITING_REFERENCES_LOCATION_SECTION,
313          UserPrefsAccessor.VISUAL_MARKDOWN_EDITING_REFERENCES_LOCATION_DOCUMENT
314       };
315       visualModeReferences_ = new SelectWidget("Write references at end of current: ", referencesValues, referencesValues, false, true, false);
316       if (!visualModeReferences_.setValue(prefs_.visualMarkdownEditingReferencesLocation().getGlobalValue()))
317          visualModeReferences_.getListBox().setSelectedIndex(0);
318       spaced(visualModeReferences_);
319       visualModeOptions.add(visualModeReferences_);
320 
321       // canonical mode
322       CheckBox visualModeCanonical = checkboxPref(
323             "Write canonical visual mode markdown in source mode",
324             prefs_.visualMarkdownEditingCanonical(),
325             false);
326       spaced(visualModeCanonical);
327       visualModeOptions.add(visualModeCanonical);
328       visualModeCanonical.addValueChangeHandler(value -> {
329          if (value.getValue())
330          {
331             RStudioGinjector.INSTANCE.getGlobalDisplay().showYesNoMessage(
332                MessageDisplay.MSG_WARNING,
333                "Visual Mode Preferences",
334                "Are you sure you want to write canonical markdown from source mode for all R Markdown files?\n\n" +
335                "This preference should generally only be used at a project level (to prevent " +
336                "re-writing of markdown source that you or others don't intend to use with visual mode).\n\n" +
337                "Change this preference now?",
338                false,
339                new Operation() {
340                   @Override
341                   public void execute()
342                   {
343 
344                   }
345                },
346                () -> {
347                   visualModeCanonical.setValue(false);
348                },
349                false);
350          }
351       });
352 
353       // help on per-file markdown options
354       HelpLink markdownPerFileOptions = new HelpLink(
355          "Learn more about markdown writer options",
356          "visual_markdown_editing-writer-options",
357          false // no version info
358       );
359       nudgeRight(markdownPerFileOptions);
360       spaced(markdownPerFileOptions);
361       visualModeOptions.add(markdownPerFileOptions);
362 
363       visualMode.add(visualModeOptions);
364 
365       VerticalTabPanel citations = new VerticalTabPanel(ElementIds.RMARKDOWN_CITATIONS_PREFS);
366 
367       Label citationsLabel = new Label("Citation features are available within R Markdown visual mode.");
368       spaced(citationsLabel);
369       citations.add(citationsLabel);
370 
371       // help on per-file markdown options
372       HelpLink citationsHelpLink = new HelpLink(
373          "Learn more about using citations with visual editing mode",
374          "visual_markdown_editing-citations",
375          false // no version info
376       );
377       spaced(citationsHelpLink);
378       citations.add(citationsHelpLink);
379 
380       citations.add(headerLabel("Zotero"));
381 
382       zoteroConnection_ = new ZoteroConnectionWidget(res, false);
383       spaced(zoteroConnection_);
384       citations.add(zoteroConnection_);
385 
386       zoteroApiKey_ = new ZoteroApiKeyWidget(zoteroServer, "240px");
387       zoteroApiKey_.getElement().getStyle().setMarginLeft(4, Unit.PX);
388       spaced(zoteroApiKey_);
389       zoteroApiKey_.setKey(state_.zoteroApiKey().getValue());
390       citations.add(zoteroApiKey_);
391 
392       zoteroDataDir_ = new DirectoryChooserTextBox(
393          "Zotero Data Directory:",
394          "(None Detected)",
395          ElementIds.TextBoxButtonId.ZOTERO_DATA_DIRECTORY,
396          null,
397          fileDialogs,
398          fsContext
399       );
400       spaced(zoteroDataDir_);
401       nudgeRight(zoteroDataDir_);
402       textBoxWithChooser(zoteroDataDir_);
403       zoteroDataDir_.getTextBox().addStyleName(res.styles().smallerText());
404       String dataDir = state_.zoteroDataDir().getValue();
405       if (!dataDir.isEmpty())
406          zoteroDataDir_.setText(dataDir);
407       citations.add(zoteroDataDir_);
408 
409 
410       zoteroLibs_ = new ZoteroLibrariesWidget(zoteroServer);
411       lessSpaced(zoteroLibs_);
412       citations.add(zoteroLibs_);
413 
414       zoteroUseBetterBibtex_ = checkboxPref(
415          "Use Better BibTeX for citation keys and BibTeX export",
416          state_.zoteroUseBetterBibtex(),
417          false);
418       spaced(zoteroUseBetterBibtex_);
419       citations.add(zoteroUseBetterBibtex_);
420 
421       // kickoff query for detected zotero data directory
422       zoteroServer.zoteroDetectLocalConfig(new ServerRequestCallback<JsObject>() {
423 
424          @Override
425          public void onResponseReceived(JsObject response)
426          {
427             zoteroLocalConfig_ = Js.uncheckedCast(response);
428 
429             if (zoteroDataDir_.getText().isEmpty())
430                zoteroDataDir_.setText(zoteroLocalConfig_.dataDirectory);
431 
432             // resolve 'auto'
433             String connectionType = state_.zoteroConnectionType().getValue();
434             zoteroIsAuto_ = connectionType.equals(UserStateAccessor.ZOTERO_CONNECTION_TYPE_AUTO);
435             if (zoteroIsAuto_)
436             {
437                if (!zoteroDataDir_.getText().isEmpty())
438                   connectionType = UserStateAccessor.ZOTERO_CONNECTION_TYPE_LOCAL;
439                else
440                   connectionType = UserStateAccessor.ZOTERO_CONNECTION_TYPE_NONE;
441             }
442             zoteroConnection_.setType(connectionType);
443 
444             // manage ui. if the user ever interact with the control then 'auto' is gone
445             manageZoteroUI(true);
446             zoteroConnection_.addChangeHandler((event) -> {
447                zoteroIsAuto_ = false;
448                // connection type change invalidates libraries (as they were
449                // retrived from the previous connection). default back to
450                // 'My Library'
451                zoteroLibs_.setMyLibrary();
452                manageZoteroUI(false);
453             });
454          }
455 
456          @Override
457          public void onError(ServerError error)
458          {
459             Debug.logError(error);
460          }
461 
462       });
463 
464 
465       DialogTabLayoutPanel tabPanel = new DialogTabLayoutPanel("R Markdown");
466       tabPanel.setSize("435px", "533px");
467       tabPanel.add(basic, "Basic", basic.getBasePanelId());
468       tabPanel.add(advanced, "Advanced", advanced.getBasePanelId());
469       tabPanel.add(visualMode, "Visual", visualMode.getBasePanelId());
470       tabPanel.add(citations, "Citations", citations.getBasePanelId());
471       tabPanel.selectTab(0);
472       add(tabPanel);
473    }
474 
475    @Override
getIcon()476    public ImageResource getIcon()
477    {
478       return new ImageResource2x(res_.iconRMarkdown2x());
479    }
480 
481    @Override
validate()482    public boolean validate()
483    {
484       return visualModeWrapColumn_.validate() &&
485              visualModeContentWidth_.validate() &&
486              zoteroLibs_.validate();
487    }
488 
489    @Override
getName()490    public String getName()
491    {
492       return "R Markdown";
493    }
494 
495    @Override
initialize(UserPrefs prefs)496    protected void initialize(UserPrefs prefs)
497    {
498       docOutlineDisplay_.setValue(prefs_.docOutlineShow().getValue());
499       rmdViewerMode_.setValue(prefs_.rmdViewerType().getValue().toString());
500       latexPreviewWidget_.setValue(prefs_.latexPreviewOnCursorIdle().getValue());
501       if (knitWorkingDir_ != null)
502          knitWorkingDir_.setValue(prefs_.knitWorkingDir().getValue());
503 
504       visualModeWrap_.setValue(prefs.visualMarkdownEditingWrap().getGlobalValue());
505       visualModeReferences_.setValue(prefs.visualMarkdownEditingReferencesLocation().getGlobalValue());
506 
507       zoteroApiKey_.setProgressIndicator(getProgressIndicator());
508 
509       zoteroLibs_.setLibraries(prefs_.zoteroLibraries().getValue());
510       zoteroLibs_.addAvailableLibraries();
511 
512    }
513 
manageZoteroUI(boolean showLibs)514    private void manageZoteroUI(boolean showLibs)
515    {
516       zoteroApiKey_.setVisible(zoteroConnection_.getType().equals(UserStateAccessor.ZOTERO_CONNECTION_TYPE_WEB));
517       zoteroDataDir_.setVisible(zoteroConnection_.getType().equals(UserStateAccessor.ZOTERO_CONNECTION_TYPE_LOCAL));
518       zoteroLibs_.setVisible(showLibs && !zoteroConnection_.getType().equals(UserStateAccessor.ZOTERO_CONNECTION_TYPE_NONE));
519       zoteroUseBetterBibtex_.setVisible(zoteroDataDir_.isVisible() && zoteroLocalConfig_.betterBibtex);
520    }
521 
522    @Override
onApply(UserPrefs rPrefs)523    public RestartRequirement onApply(UserPrefs rPrefs)
524    {
525       RestartRequirement restartRequirement = super.onApply(rPrefs);
526 
527       prefs_.docOutlineShow().setGlobalValue(
528             docOutlineDisplay_.getValue());
529 
530       prefs_.rmdViewerType().setGlobalValue(
531             rmdViewerMode_.getValue());
532 
533       prefs_.latexPreviewOnCursorIdle().setGlobalValue(
534             latexPreviewWidget_.getValue());
535 
536       prefs_.visualMarkdownEditingFontSizePoints().setGlobalValue(
537             Integer.parseInt(visualModeFontSize_.getValue()));
538 
539       prefs_.visualMarkdownEditingListSpacing().setGlobalValue(
540             visualModeListSpacing_.getValue());
541 
542       prefs_.visualMarkdownEditingWrap().setGlobalValue(
543             visualModeWrap_.getValue());
544 
545       prefs_.visualMarkdownEditingReferencesLocation().setGlobalValue(
546             visualModeReferences_.getValue());
547 
548       if (knitWorkingDir_ != null)
549       {
550          prefs_.knitWorkingDir().setGlobalValue(
551                knitWorkingDir_.getValue());
552       }
553 
554       if (zoteroIsAuto_)
555       {
556          state_.zoteroConnectionType().setGlobalValue(UserStateAccessor.ZOTERO_CONNECTION_TYPE_AUTO);
557       }
558       else if (zoteroConnection_.getType().equals(UserStateAccessor.ZOTERO_CONNECTION_TYPE_LOCAL) &&
559                zoteroDataDir_.getText().isEmpty())
560       {
561          // not a valid set
562       }
563       else if (zoteroConnection_.getType().equals(UserStateAccessor.ZOTERO_CONNECTION_TYPE_WEB) &&
564                zoteroApiKey_.getKey().isEmpty())
565       {
566          // not a valid set
567       }
568       else
569       {
570          state_.zoteroConnectionType().setGlobalValue(zoteroConnection_.getType());
571       }
572 
573       prefs_.zoteroLibraries().setGlobalValue(zoteroLibs_.getLibraries());
574 
575       // if the zotero data dir is same as the detected data dir then
576       // set it to empty (allowing the server to always get the right default)
577       if (zoteroDataDir_.getText().equals(zoteroLocalConfig_.dataDirectory))
578          state_.zoteroDataDir().setGlobalValue("");
579       else
580          state_.zoteroDataDir().setGlobalValue(zoteroDataDir_.getText());
581 
582       state_.zoteroApiKey().setGlobalValue(zoteroApiKey_.getKey());
583 
584       return restartRequirement;
585    }
586 
587    private final UserPrefs prefs_;
588    private final UserState state_;
589 
590    private final PreferencesDialogResources res_;
591 
592    private final SelectWidget rmdViewerMode_;
593    private final SelectWidget docOutlineDisplay_;
594    private final SelectWidget latexPreviewWidget_;
595    private final SelectWidget knitWorkingDir_;
596 
597    private final SelectWidget visualModeFontSize_;
598    private final NumericValueWidget visualModeContentWidth_;
599    private final NumericValueWidget visualModeWrapColumn_;
600    private final SelectWidget visualModeListSpacing_;
601    private final SelectWidget visualModeWrap_;
602    private final SelectWidget visualModeReferences_;
603 
604    private final ZoteroConnectionWidget zoteroConnection_;
605    private final DirectoryChooserTextBox zoteroDataDir_;
606    private final ZoteroApiKeyWidget zoteroApiKey_;
607    private final ZoteroLibrariesWidget zoteroLibs_;
608    private final CheckBox zoteroUseBetterBibtex_;
609    private PanmirrorZoteroLocalConfig zoteroLocalConfig_ = new PanmirrorZoteroLocalConfig();
610    private boolean zoteroIsAuto_ = false;
611 }
612