1 /*
2  * AppCommand.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.core.client.command;
16 
17 import com.google.gwt.aria.client.MenuitemRole;
18 import com.google.gwt.aria.client.Roles;
19 import com.google.gwt.event.dom.client.ClickEvent;
20 import com.google.gwt.event.dom.client.ClickHandler;
21 import com.google.gwt.event.shared.HandlerManager;
22 import com.google.gwt.event.shared.HandlerRegistration;
23 import com.google.gwt.resources.client.ImageResource;
24 import com.google.gwt.safehtml.shared.SafeHtml;
25 import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
26 import com.google.gwt.user.client.Command;
27 import com.google.gwt.user.client.ui.Image;
28 import com.google.gwt.user.client.ui.MenuItem;
29 
30 import org.rstudio.core.client.ElementIds;
31 import org.rstudio.core.client.SafeHtmlUtil;
32 import org.rstudio.core.client.StringUtil;
33 import org.rstudio.core.client.command.impl.DesktopMenuCallback;
34 import org.rstudio.core.client.dom.DomUtils;
35 import org.rstudio.core.client.resources.ImageResource2x;
36 import org.rstudio.core.client.theme.res.ThemeResources;
37 import org.rstudio.core.client.theme.res.ThemeStyles;
38 import org.rstudio.core.client.widget.Toolbar;
39 import org.rstudio.core.client.widget.ToolbarButton;
40 import org.rstudio.studio.client.RStudio;
41 import org.rstudio.studio.client.RStudioGinjector;
42 import org.rstudio.studio.client.application.Desktop;
43 import org.rstudio.studio.client.application.ui.RStudioThemes;
44 import org.rstudio.studio.client.common.satellite.Satellite;
45 import org.rstudio.studio.client.common.satellite.SatelliteManager;
46 
47 public class AppCommand implements Command, ClickHandler, ImageResourceProvider
48 {
49    private class CommandToolbarButton extends ToolbarButton implements
50          EnabledChangedHandler, VisibleChangedHandler
51    {
CommandToolbarButton(String buttonLabel, String buttonTitle, ImageResourceProvider imageResourceProvider, AppCommand command, boolean synced)52       public CommandToolbarButton(String buttonLabel, String buttonTitle,
53             ImageResourceProvider imageResourceProvider, AppCommand command,
54             boolean synced)
55       {
56          super(buttonLabel, buttonTitle, imageResourceProvider, command);
57          command_ = command;
58          synced_ = synced;
59       }
60 
61       @Override
onAttach()62       protected void onAttach()
63       {
64          if (synced_)
65          {
66             setEnabled(command_.isEnabled());
67             setVisible(command_.isVisible());
68             handlerReg_ = command_.addEnabledChangedHandler(this);
69             handlerReg2_ = command_.addVisibleChangedHandler(this);
70          }
71 
72          parentToolbar_ = getParentToolbar();
73 
74          super.onAttach();
75          ElementIds.assignElementId(getElement(), "tb_" + ElementIds.idSafeString(getId()));
76       }
77 
78       @Override
onDetach()79       protected void onDetach()
80       {
81          super.onDetach();
82 
83          if (synced_)
84          {
85             handlerReg_.removeHandler();
86             handlerReg2_.removeHandler();
87          }
88       }
89 
onEnabledChanged(EnabledChangedEvent event)90       public void onEnabledChanged(EnabledChangedEvent event)
91       {
92          setEnabled(command_.isEnabled());
93       }
94 
onVisibleChanged(VisibleChangedEvent event)95       public void onVisibleChanged(VisibleChangedEvent event)
96       {
97          setVisible(command_.isVisible());
98          if (command_.isVisible())
99             setEnabled(command_.isEnabled());
100 
101          parentToolbar_.invalidateSeparators();
102       }
103 
104       private final AppCommand command_;
105       private boolean synced_ = true;
106       private HandlerRegistration handlerReg_;
107       private HandlerRegistration handlerReg2_;
108       private Toolbar parentToolbar_;
109    }
110 
AppCommand()111    public AppCommand()
112    {
113       if (Desktop.hasDesktopFrame())
114       {
115          // If this command is destined for the main window, do not allow
116          // satellite windows to alter the global state of the command.
117          //
118          // Note that we can't use the more robust isCurrentWindowSatellite()
119          // here since AppCommand callbacks run on app init (before the
120          // satellite callbacks are wired up).
121          //
122          // We also need to execute this check at runtime (not in the constructor)
123          // since windowMode is not set when the AppCommand is constructed.
124          addEnabledChangedHandler((event) ->
125          {
126             if (!StringUtil.equals(getWindowMode(), WINDOW_MODE_MAIN) ||
127                 StringUtil.isNullOrEmpty(RStudio.getSatelliteView()))
128             {
129                DesktopMenuCallback.setCommandEnabled(id_, enabled_);
130             }
131          });
132          addVisibleChangedHandler((event) ->
133          {
134             if (!StringUtil.equals(getWindowMode(), WINDOW_MODE_MAIN) ||
135                 StringUtil.isNullOrEmpty(RStudio.getSatelliteView()))
136             {
137                DesktopMenuCallback.setCommandVisible(id_, visible_);
138             }
139          });
140       }
141    }
142 
executeFromShortcut()143    void executeFromShortcut()
144    {
145       executedFromShortcut_ = true;
146       doExecute();
147    }
148 
execute()149    public void execute()
150    {
151       executedFromShortcut_ = false;
152       doExecute();
153    }
154 
doExecute()155    private void doExecute()
156    {
157       assert enabled_ : "AppCommand executed when it was not enabled";
158       if (!enabled_)
159          return;
160       assert visible_ : "AppCommand executed when it was not visible";
161       if (!visible_)
162          return;
163 
164       // if this window is a satellite but the command only wants to be handled
165       // in the a different window, execute the command there instead
166       Satellite satellite = RStudioGinjector.INSTANCE.getSatellite();
167       if (getWindowMode() != WINDOW_MODE_ANY &&
168           Satellite.isCurrentWindowSatellite() &&
169           satellite.getSatelliteName() != getWindowMode())
170       {
171          if (getWindowMode() == WINDOW_MODE_MAIN)
172          {
173             // raise the main window if it's not a background command
174             satellite.focusMainWindow();
175          }
176          else if (getWindowMode() == WINDOW_MODE_BACKGROUND &&
177                   Desktop.hasDesktopFrame())
178          {
179             // for background commands, we still want the main window to be
180             // as visible as possible, so bring it up behind the current window
181             // (this is of course only possible in desktop mode)
182             Desktop.getFrame().bringMainFrameBehindActive();
183          }
184 
185          // satellites don't fire commands peer-to-peer--route it to the main
186          // window for processing
187          SatelliteManager mgr = RStudioGinjector.INSTANCE.getSatelliteManager();
188          mgr.dispatchCommand(this, null);
189          return;
190       }
191 
192       if (enableNoHandlerAssertions_)
193       {
194          assert handlers_.getHandlerCount(CommandEvent.TYPE) > 0
195                   : "AppCommand executed but nobody was listening: " + getId();
196       }
197 
198       CommandEvent event = new CommandEvent(this);
199       RStudioGinjector.INSTANCE.getEventBus().fireEvent(event);
200       handlers_.fireEvent(event);
201    }
202 
isEnabled()203    public boolean isEnabled()
204    {
205       return enabled_ && isVisible(); // jcheng 06/30/2010: Hmmmm, smells weird.
206    }
207 
208    /**
209     * Determines whether there are any handlers established that will execute
210     * when this command runs. This is useful for determining if the command
211     * will do anything when executed.
212     *
213     * @return Whether this command has handlers.
214     */
hasCommandHandlers()215    public boolean hasCommandHandlers()
216    {
217       return handlers_.getHandlerCount(CommandEvent.TYPE) > 0;
218    }
219 
setEnabled(boolean enabled)220    public void setEnabled(boolean enabled)
221    {
222       setEnabled(enabled, false);
223    }
224 
setEnabled(boolean enabled, boolean force)225    public void setEnabled(boolean enabled, boolean force)
226    {
227       if (force || enabled != enabled_)
228       {
229          enabled_ = enabled;
230          handlers_.fireEvent(new EnabledChangedEvent(this));
231       }
232    }
233 
isVisible()234    public boolean isVisible()
235    {
236       return visible_;
237    }
238 
setVisible(boolean visible)239    public void setVisible(boolean visible)
240    {
241       if (!removed_ && visible != visible_)
242       {
243          visible_ = visible;
244          handlers_.fireEvent(new VisibleChangedEvent(this));
245       }
246    }
247 
248    /**
249     * Restores a command which was formerly removed. The command must still be made
250     * visible and enabled in order to work.
251     */
restore()252    public void restore()
253    {
254       removed_ = false;
255    }
256 
isCheckable()257    public boolean isCheckable()
258    {
259       return checkable_;
260    }
261 
setCheckable(boolean isCheckable)262    public void setCheckable(boolean isCheckable)
263    {
264       if (isRadio())
265          checkable_ = true;
266       else
267          checkable_ = isCheckable;
268    }
269 
isChecked()270    public boolean isChecked()
271    {
272       return checkable_ && checked_;
273    }
274 
setChecked(boolean checked)275    public void setChecked(boolean checked)
276    {
277       if (!isCheckable())
278          return;
279 
280       checked_ = checked;
281       if (Desktop.hasDesktopFrame())
282          DesktopMenuCallback.setCommandChecked(id_, checked_);
283    }
284 
setRadio(boolean isRadio)285    public void setRadio(boolean isRadio)
286    {
287       radio_ = isRadio;
288       if (radio_)
289          setCheckable(true);
290    }
291 
isRadio()292    public boolean isRadio()
293    {
294       return radio_;
295    }
296 
getMenuRole()297    public MenuitemRole getMenuRole()
298    {
299       if (isRadio())
300          return Roles.getMenuitemradioRole();
301       else if (isCheckable())
302          return Roles.getMenuitemcheckboxRole();
303       else
304          return Roles.getMenuitemRole();
305    }
306 
getWindowMode()307    public String getWindowMode()
308    {
309       return windowMode_;
310    }
311 
setWindowMode(String mode)312    public void setWindowMode(String mode)
313    {
314       windowMode_ = mode;
315    }
316 
isRebindable()317    public boolean isRebindable()
318    {
319       return rebindable_;
320    }
321 
setRebindable(boolean rebindable)322    public void setRebindable(boolean rebindable)
323    {
324       rebindable_ = rebindable;
325    }
326 
327    public enum Context
328    {
329       Workbench, Editor, R, Cpp, PackageDevelopment, RMarkdown,
330       Markdown, Sweave, Help, VCS, Packrat, Renv, RPresentation,
331       Addin, Viewer, History, Tutorial, Diagnostics, Import, Files;
332 
333       @Override
toString()334       public String toString()
335       {
336          if (this == Cpp)
337             return "C / C++";
338 
339          return StringUtil.prettyCamel(super.toString());
340       }
341    }
342 
getContext()343    public Context getContext()
344    {
345       return context_;
346    }
347 
setContext(String context)348    public void setContext(String context)
349    {
350       String lower = context.toLowerCase();
351       if (lower.equals("workbench"))
352          context_ = Context.Workbench;
353       else if (lower.equals("editor"))
354          context_ = Context.Editor;
355       else if (lower.equals("vcs"))
356          context_ = Context.VCS;
357       else if (lower.equals("r"))
358          context_ = Context.R;
359       else if (lower.equals("cpp"))
360          context_ = Context.Cpp;
361       else if (lower.equals("packagedevelopment"))
362          context_ = Context.PackageDevelopment;
363       else if (lower.equals("rmarkdown"))
364          context_ = Context.RMarkdown;
365       else if (lower.equals("sweave"))
366          context_ = Context.Markdown;
367       else if (lower.equals("markdown"))
368          context_ = Context.Sweave;
369       else if (lower.equals("help"))
370          context_ = Context.Help;
371       else if (lower.equals("packrat"))
372          context_ = Context.Packrat;
373       else if (lower.equals("renv"))
374          context_ = Context.Renv;
375       else if (lower.equals("presentation"))
376          context_ = Context.RPresentation;
377       else if (lower.equals("viewer"))
378          context_ = Context.Viewer;
379       else if (lower.equals("tutorial"))
380          context_ = Context.Tutorial;
381       else if (lower.equals("diagnostics"))
382          context_ = Context.Diagnostics;
383       else if (lower.equals("history"))
384          context_ = Context.History;
385       else if (lower.equals("import"))
386          context_ = Context.Import;
387       else if (lower.equals("files"))
388          context_ = Context.Files;
389       else
390          throw new Error("Invalid AppCommand context '" + context + "'");
391    }
392 
393    /**
394     * Hides the command and makes sure it never comes back.
395     */
remove()396    public void remove()
397    {
398       setVisible(false);
399       removed_ = true;
400    }
401 
getDesc()402    public String getDesc()
403    {
404       return desc_;
405    }
406 
getTooltip()407    public String getTooltip()
408    {
409       String desc = StringUtil.notNull(getDesc());
410       String shortcut = getShortcutPrettyHtml();
411       shortcut = StringUtil.isNullOrEmpty(shortcut)
412                  ? ""
413                  : "(" + DomUtils.htmlToText(shortcut) + ")";
414 
415       String result = (desc + " " + shortcut).trim();
416       return result.length() == 0 ? null : result;
417    }
418 
getId()419    public String getId()
420    {
421       return id_;
422    }
423 
424    // Called by CommandBundleGenerator
setId(String id)425    public void setId(String id)
426    {
427       id_ = id;
428    }
429 
setDesc(String desc)430    public void setDesc(String desc)
431    {
432       desc_ = desc;
433    }
434 
getLabel()435    public String getLabel()
436    {
437       return label_;
438    }
439 
setLabel(String label)440    public void setLabel(String label)
441    {
442       label_ = label;
443    }
444 
getButtonLabel()445    public String getButtonLabel()
446    {
447       if (buttonLabel_ != null)
448          return buttonLabel_;
449       return getLabel();
450    }
451 
setButtonLabel(String buttonLabel)452    public void setButtonLabel(String buttonLabel)
453    {
454       buttonLabel_ = buttonLabel;
455    }
456 
getMenuLabel(boolean useMnemonics)457    public String getMenuLabel(boolean useMnemonics)
458    {
459       if (menuLabel_ != null)
460       {
461          return AppMenuItem.replaceMnemonics(menuLabel_, useMnemonics ? "&" : "");
462       }
463       return getLabel();
464    }
465 
setMenuLabel(String menuLabel)466    public void setMenuLabel(String menuLabel)
467    {
468       menuLabel_ = menuLabel;
469       if (Desktop.hasDesktopFrame())
470          DesktopMenuCallback.setCommandLabel(id_, menuLabel_);
471    }
472 
473    @Override
getImageResource()474    public ImageResource getImageResource()
475    {
476       return menuImageResource(isCheckable(), isChecked(), imageResource_);
477    }
478 
menuImageResource(boolean isCheckable, boolean isChecked, ImageResource defaultImage)479    public static ImageResource menuImageResource(boolean isCheckable, boolean isChecked, ImageResource defaultImage)
480    {
481       if (isCheckable)
482       {
483          if (RStudioThemes.isFlat() && RStudioThemes.isEditorDark()) {
484             return isChecked ?
485                new ImageResource2x(ThemeResources.INSTANCE.menuCheckInverted2x()) :
486                null;
487          }
488          else {
489             return isChecked ?
490                new ImageResource2x(ThemeResources.INSTANCE.menuCheck2x()) :
491                null;
492          }
493       }
494       else
495       {
496          return defaultImage;
497       }
498    }
499 
500    @Override
addRenderedImage(Image image)501    public void addRenderedImage(Image image)
502    {
503    }
504 
setImageResource(ImageResource imageResource)505    public void setImageResource(ImageResource imageResource)
506    {
507       imageResource_ = imageResource;
508    }
509 
setRightImage(ImageResource image)510    public void setRightImage(ImageResource image)
511    {
512       setRightImage(image, null);
513    }
514 
setRightImage(ImageResource image, String desc)515    public void setRightImage(ImageResource image, String desc)
516    {
517       rightImage_ = image;
518       rightImageDesc_ = desc;
519    }
520 
addHandler(CommandHandler handler)521    public HandlerRegistration addHandler(CommandHandler handler)
522    {
523       return handlers_.addHandler(CommandEvent.TYPE, handler);
524    }
525 
addEnabledChangedHandler( EnabledChangedHandler handler)526    public HandlerRegistration addEnabledChangedHandler(
527          EnabledChangedHandler handler)
528    {
529       return handlers_.addHandler(EnabledChangedEvent.TYPE, handler);
530    }
531 
addVisibleChangedHandler( VisibleChangedHandler handler)532    public HandlerRegistration addVisibleChangedHandler(
533          VisibleChangedHandler handler)
534    {
535       return handlers_.addHandler(VisibleChangedEvent.TYPE, handler);
536    }
537 
onClick(ClickEvent event)538    public void onClick(ClickEvent event)
539    {
540       execute();
541    }
542 
createToolbarButton()543    public ToolbarButton createToolbarButton()
544    {
545       return createToolbarButton(true);
546    }
547 
createToolbarButton(boolean synced)548    public ToolbarButton createToolbarButton(boolean synced)
549    {
550       CommandToolbarButton button = new CommandToolbarButton(getButtonLabel(),
551                                                              getDesc(),
552                                                              this,
553                                                              this,
554                                                              synced);
555       if (getTooltip() != null)
556          button.setTitle(getTooltip());
557       return button;
558    }
559 
createMenuItem(boolean mainMenu)560    public MenuItem createMenuItem(boolean mainMenu)
561    {
562       return new AppMenuItem(this, mainMenu);
563    }
564 
getMenuHTML(boolean mainMenu)565    public String getMenuHTML(boolean mainMenu)
566    {
567       String label = getMenuLabel(false);
568       String shortcut = shortcut_ != null ? shortcut_.toString(true) : "";
569 
570       return formatMenuLabel(
571             getImageResource(), label, shortcut, rightImage_, rightImageDesc_);
572    }
573 
formatMenuLabel(ImageResource icon, String label, String shortcut)574    public static String formatMenuLabel(ImageResource icon,
575          String label,
576          String shortcut)
577    {
578       return formatMenuLabel(icon, label, false, shortcut);
579    }
580 
formatMenuLabelWithStyle(ImageResource icon, String label, String shortcut, String styleName)581    public static String formatMenuLabelWithStyle(ImageResource icon,
582                                                  String label,
583                                                  String shortcut,
584                                                  String styleName)
585    {
586       return formatMenuLabel(icon,
587             label,
588             false,
589             shortcut,
590             null,
591             null,
592             null,
593             styleName);
594    }
595 
formatMenuLabel(ImageResource icon, String label, boolean html, String shortcut)596    public static String formatMenuLabel(ImageResource icon,
597          String label,
598          boolean html,
599          String shortcut)
600    {
601       return formatMenuLabel(icon, label, html, shortcut, null, null);
602    }
603 
formatMenuLabel(ImageResource icon, String label, String shortcut, ImageResource rightImage, String rightImageDesc)604    public static String formatMenuLabel(ImageResource icon,
605          String label,
606          String shortcut,
607          ImageResource rightImage,
608          String rightImageDesc)
609    {
610       return formatMenuLabel(icon, label, false, shortcut, rightImage, rightImageDesc);
611    }
612 
formatMenuLabel(ImageResource icon, String label, boolean html, String shortcut, ImageResource rightImage, String rightImageDesc)613    public static String formatMenuLabel(ImageResource icon,
614                                          String label,
615                                          boolean html,
616                                          String shortcut,
617                                          ImageResource rightImage,
618                                          String rightImageDesc)
619    {
620       return formatMenuLabel(icon,
621                              label,
622                              html,
623                              shortcut,
624                              null,
625                              rightImage,
626                              rightImageDesc,
627                              null);
628    }
629 
formatMenuLabel(ImageResource icon, String label, String shortcut, Integer iconOffsetY)630    public static String formatMenuLabel(ImageResource icon,
631          String label,
632          String shortcut,
633          Integer iconOffsetY)
634    {
635       return formatMenuLabel(icon, label, false, shortcut, iconOffsetY);
636    }
637 
formatMenuLabel(ImageResource icon, String label, boolean html, String shortcut, Integer iconOffsetY)638    public static String formatMenuLabel(ImageResource icon,
639                                         String label,
640                                         boolean html,
641                                         String shortcut,
642                                         Integer iconOffsetY)
643    {
644       return formatMenuLabel(icon, label, html, shortcut, iconOffsetY, null, null, null);
645    }
646 
formatMenuLabel(ImageResource icon, String label, boolean html, String shortcut, Integer iconOffsetY, ImageResource rightImage, String rightImageDesc, String styleName)647    public static String formatMenuLabel(ImageResource icon,
648                                          String label,
649                                          boolean html,
650                                          String shortcut,
651                                          Integer iconOffsetY,
652                                          ImageResource rightImage,
653                                          String rightImageDesc,
654                                          String styleName)
655    {
656       StringBuilder text = new StringBuilder();
657       int topOffset = -2;
658       if (iconOffsetY != null)
659          topOffset += iconOffsetY;
660       text.append("<table role=\"presentation\"");
661       if (label != null)
662       {
663          text.append("id=\"" + ElementIds.idFromLabel(label) + "_command\" ");
664       }
665 
666       if (styleName != null)
667       {
668          text.append("class='" + styleName + "' ");
669       }
670 
671       text.append("border=0 cellpadding=0 cellspacing=0 width='100%'><tr>");
672 
673       text.append("<td width=\"25\" style=\"vertical-align: top\">" +
674                   "<div style=\"width: 25px; margin-top: " +
675                   topOffset + "px; margin-bottom: -10px\">");
676       if (icon != null)
677       {
678          SafeHtml imageHtml = createMenuImageHtml(icon);
679          text.append(imageHtml.asString());
680       }
681       else
682       {
683          text.append("<br/>");
684       }
685       text.append("</div></td>");
686 
687       label = StringUtil.notNull(label);
688       if (!html)
689          label = DomUtils.textToHtml(label);
690       text.append("<td>" + label + "</td>");
691       if (rightImage != null)
692       {
693          SafeHtml imageHtml = createRightImageHtml(rightImage, rightImageDesc);
694          text.append("<td align=right width=\"25\"><div style=\"width: 25px; float: right; margin-top: -7px; margin-bottom: -10px;\">" + imageHtml.asString() + "</div></td>");
695       }
696       else if (shortcut != null)
697       {
698          text.append("<td align=right nowrap>&nbsp;&nbsp;&nbsp;&nbsp;" + shortcut + "</td>");
699       }
700       text.append("</tr></table>");
701 
702       return text.toString();
703    }
704 
getShortcut()705    public KeyboardShortcut getShortcut()
706    {
707       return shortcut_;
708    }
709 
getKeySequence()710    public KeySequence getKeySequence()
711    {
712       if (shortcut_ == null)
713          return new KeySequence();
714 
715       return shortcut_.getKeySequence();
716    }
717 
setShortcut(KeyboardShortcut shortcut)718    public void setShortcut(KeyboardShortcut shortcut)
719    {
720       shortcut_ = shortcut;
721    }
722 
getShortcutRaw()723    public String getShortcutRaw()
724    {
725       return shortcut_ != null ? shortcut_.toString(false) : null;
726    }
727 
getShortcutPrettyHtml()728    public String getShortcutPrettyHtml()
729    {
730       return shortcut_ != null ? shortcut_.toString(true) : null;
731    }
732 
getExecutedFromShortcut()733    public boolean getExecutedFromShortcut()
734    {
735       return executedFromShortcut_;
736    }
737 
disableNoHandlerAssertions()738    public static void disableNoHandlerAssertions()
739    {
740       enableNoHandlerAssertions_ = false;
741    }
742 
summarize()743    public String summarize()
744    {
745       if (!StringUtil.isNullOrEmpty(getMenuLabel(false)))
746          return getMenuLabel(false);
747       else if (!StringUtil.isNullOrEmpty(getButtonLabel()))
748          return getButtonLabel();
749       else if (!StringUtil.isNullOrEmpty(getDesc()))
750          return getDesc();
751       else
752          return "(no description)";
753    }
754 
createRightImageHtml(ImageResource image, String desc)755    private static SafeHtml createRightImageHtml(ImageResource image,
756                                                 String desc)
757    {
758       SafeHtmlBuilder sb = new SafeHtmlBuilder();
759       sb.append(SafeHtmlUtil.createOpenTag("img",
760         "class", ThemeStyles.INSTANCE.menuRightImage(),
761         "title", StringUtil.notNull(desc),
762         "aria-label", StringUtil.notNull(desc),
763         "width", Integer.toString(image.getWidth()),
764         "height", Integer.toString(image.getHeight()),
765         "src", image.getSafeUri().asString(),
766         "alt", ""));
767       sb.appendHtmlConstant("</img>");
768       return sb.toSafeHtml();
769    }
770 
createMenuImageHtml(ImageResource image)771    private static SafeHtml createMenuImageHtml(ImageResource image)
772    {
773       SafeHtmlBuilder sb = new SafeHtmlBuilder();
774       sb.append(SafeHtmlUtil.createOpenTag("img",
775         "width", Integer.toString(image.getWidth()),
776         "height", Integer.toString(image.getHeight()),
777         "src", image.getSafeUri().asString(),
778         "alt", ""));
779       sb.appendHtmlConstant("</img>");
780       return sb.toSafeHtml();
781    }
782 
783    private boolean enabled_ = true;
784    private boolean visible_ = true;
785    private boolean removed_ = false;
786    private boolean checkable_ = false;
787    private boolean radio_ = false;
788    private boolean checked_ = false;
789    private String windowMode_ = "any";
790    private final HandlerManager handlers_ = new HandlerManager(this);
791    private boolean rebindable_ = true;
792    private Context context_ = Context.Workbench;
793 
794    private String label_ = null;
795    private String buttonLabel_ = null;
796    private String menuLabel_ = null;
797    private String desc_;
798    private ImageResource imageResource_;
799    private KeyboardShortcut shortcut_;
800    private String id_;
801    private ImageResource rightImage_ = null;
802    private String rightImageDesc_ = null;
803 
804    private boolean executedFromShortcut_ = false;
805 
806    private static boolean enableNoHandlerAssertions_ = true;
807 
808    public static final String WINDOW_MODE_BACKGROUND = "background";
809    public static final String WINDOW_MODE_MAIN = "main";
810    public static final String WINDOW_MODE_ANY = "any";
811 }
812