<commit_before>/*
 * DesktopMenuCallback.cpp
 *
 * Copyright (C) 2009-17 by RStudio, Inc.
 *
 * Unless you have received this program directly from RStudio pursuant
 * to the terms of a commercial license agreement with RStudio, then
 * this program is licensed to you under the terms of version 3 of the
 * GNU Affero General Public License. This program is distributed WITHOUT
 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
 *
 */

#include "DesktopMenuCallback.hpp"
#include <QDebug>
#include <QApplication>

#ifdef Q_OS_MAC
#include <ApplicationServices/ApplicationServices.h>
#endif

namespace rstudio {
namespace desktop {

MenuCallback::MenuCallback(QObject *parent) :
    QObject(parent)
{
}

void MenuCallback::beginMainMenu()
{
   pMainMenu_ = new QMenuBar();
}

void MenuCallback::beginMenu(QString label)
{
#ifdef Q_OS_MAC
   if (label == QString::fromUtf8("&Help"))
   {
      pMainMenu_->addMenu(new WindowMenu(pMainMenu_));
   }
#endif

   auto* pMenu = new SubMenu(label, pMainMenu_);
   pMenu->setSeparatorsCollapsible(true);

   if (menuStack_.count() == 0)
      pMainMenu_->addMenu(pMenu);
   else
      menuStack_.top()->addMenu(pMenu);

   menuStack_.push(pMenu);
}

QAction* MenuCallback::addCustomAction(QString commandId,
                                       QString label,
                                       QString tooltip,
                                       QKeySequence keySequence,
                                       bool checkable)
{

   QAction* pAction = nullptr;

#ifdef Q_OS_MAC
   // On Mac, certain commands will be automatically moved to Application Menu by Qt. If we want them to also
   // appear in RStudio menus, check for them here and return nullptr.
   if (duplicateAppMenuAction(QString::fromUtf8("showAboutDialog"),
                              commandId, label, tooltip, keySequence, checkable))
   {
      return nullptr;
   }
   else if (duplicateAppMenuAction(QString::fromUtf8("quitSession"),
                              commandId, label, tooltip, keySequence, checkable))
   {
      return nullptr;
   }

   // If we want a command to not be automatically moved to Application Menu, include it here and return the
   // created action.
   pAction = duplicateAppMenuAction(QString::fromUtf8("buildToolsProjectSetup"),
                                    commandId, label, tooltip, keySequence, checkable);
   if (pAction)
      return pAction;

#endif // Q_OS_MAC

   if (commandId == QString::fromUtf8("zoomIn"))
   {
      pAction = menuStack_.top()->addAction(QIcon(),
                                            label,
                                            this,
                                            SIGNAL(zoomIn()),
                                            QKeySequence::ZoomIn);
   }
   else if (commandId == QString::fromUtf8("zoomOut"))
   {
      pAction = menuStack_.top()->addAction(QIcon(),
                                            label,
                                            this,
                                            SIGNAL(zoomOut()),
                                            QKeySequence::ZoomOut);
   }
#ifdef Q_OS_LINUX
   else if (commandId == QString::fromUtf8("nextTab"))
   {
      pAction = menuStack_.top()->addAction(QIcon(),
                                            label,
                                            this,
                                            SLOT(actionInvoked()),
                                            QKeySequence(Qt::CTRL +
                                                         Qt::Key_PageDown));
   }
   else if (commandId == QString::fromUtf8("previousTab"))
   {
      pAction = menuStack_.top()->addAction(QIcon(),
                                            label,
                                            this,
                                            SLOT(actionInvoked()),
                                            QKeySequence(Qt::CTRL +
                                                         Qt::Key_PageUp));
   }
#endif

   if (pAction != nullptr)
   {
      pAction->setData(commandId);
      pAction->setToolTip(tooltip);
      return pAction;
   }
   else
   {
      return nullptr;
   }
}

QAction* MenuCallback::duplicateAppMenuAction(QString commandToDuplicate,
                                              QString commandId,
                                              QString label,
                                              QString tooltip,
                                              QKeySequence keySequence,
                                              bool checkable)
{
   QAction* pAction = nullptr;
   if (commandId == commandToDuplicate)
   {
      pAction = new QAction(QIcon(), label);
      pAction->setMenuRole(QAction::NoRole);
      pAction->setData(commandId);
      pAction->setToolTip(tooltip);
      pAction->setShortcut(keySequence);
      if (checkable)
         pAction->setCheckable(true);

      menuStack_.top()->addAction(pAction);

      auto* pBinder = new MenuActionBinder(menuStack_.top(), pAction);
      connect(pBinder, SIGNAL(manageCommand(QString, QAction * )), this, SIGNAL(manageCommand(QString, QAction * )));
      connect(pAction, SIGNAL(triggered()), this, SLOT(actionInvoked()));
   }
   return pAction;
}

void MenuCallback::addCommand(QString commandId,
                              QString label,
                              QString tooltip,
                              QString shortcut,
                              bool checkable)
{
   // replace instances of 'Cmd' with 'Ctrl' -- note that on macOS
   // Qt automatically maps that to the Command key
   shortcut.replace(QStringLiteral("Cmd"), QStringLiteral("Ctrl"));
   
   QKeySequence keySequence(shortcut);<commit_msg>render ctrl+based shortcuts in menus correctly on macos; fixes #2010<commit_after>/*
 * DesktopMenuCallback.cpp
 *
 * Copyright (C) 2009-17 by RStudio, Inc.
 *
 * Unless you have received this program directly from RStudio pursuant
 * to the terms of a commercial license agreement with RStudio, then
 * this program is licensed to you under the terms of version 3 of the
 * GNU Affero General Public License. This program is distributed WITHOUT
 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
 *
 */

#include "DesktopMenuCallback.hpp"
#include <QDebug>
#include <QApplication>

#ifdef Q_OS_MAC
#include <ApplicationServices/ApplicationServices.h>
#endif

namespace rstudio {
namespace desktop {

MenuCallback::MenuCallback(QObject *parent) :
    QObject(parent)
{
}

void MenuCallback::beginMainMenu()
{
   pMainMenu_ = new QMenuBar();
}

void MenuCallback::beginMenu(QString label)
{
#ifdef Q_OS_MAC
   if (label == QString::fromUtf8("&Help"))
   {
      pMainMenu_->addMenu(new WindowMenu(pMainMenu_));
   }
#endif

   auto* pMenu = new SubMenu(label, pMainMenu_);
   pMenu->setSeparatorsCollapsible(true);

   if (menuStack_.count() == 0)
      pMainMenu_->addMenu(pMenu);
   else
      menuStack_.top()->addMenu(pMenu);

   menuStack_.push(pMenu);
}

QAction* MenuCallback::addCustomAction(QString commandId,
                                       QString label,
                                       QString tooltip,
                                       QKeySequence keySequence,
                                       bool checkable)
{

   QAction* pAction = nullptr;

#ifdef Q_OS_MAC
   // On Mac, certain commands will be automatically moved to Application Menu by Qt. If we want them to also
   // appear in RStudio menus, check for them here and return nullptr.
   if (duplicateAppMenuAction(QString::fromUtf8("showAboutDialog"),
                              commandId, label, tooltip, keySequence, checkable))
   {
      return nullptr;
   }
   else if (duplicateAppMenuAction(QString::fromUtf8("quitSession"),
                              commandId, label, tooltip, keySequence, checkable))
   {
      return nullptr;
   }

   // If we want a command to not be automatically moved to Application Menu, include it here and return the
   // created action.
   pAction = duplicateAppMenuAction(QString::fromUtf8("buildToolsProjectSetup"),
                                    commandId, label, tooltip, keySequence, checkable);
   if (pAction)
      return pAction;

#endif // Q_OS_MAC

   if (commandId == QString::fromUtf8("zoomIn"))
   {
      pAction = menuStack_.top()->addAction(QIcon(),
                                            label,
                                            this,
                                            SIGNAL(zoomIn()),
                                            QKeySequence::ZoomIn);
   }
   else if (commandId == QString::fromUtf8("zoomOut"))
   {
      pAction = menuStack_.top()->addAction(QIcon(),
                                            label,
                                            this,
                                            SIGNAL(zoomOut()),
                                            QKeySequence::ZoomOut);
   }
#ifdef Q_OS_LINUX
   else if (commandId == QString::fromUtf8("nextTab"))
   {
      pAction = menuStack_.top()->addAction(QIcon(),
                                            label,
                                            this,
                                            SLOT(actionInvoked()),
                                            QKeySequence(Qt::CTRL +
                                                         Qt::Key_PageDown));
   }
   else if (commandId == QString::fromUtf8("previousTab"))
   {
      pAction = menuStack_.top()->addAction(QIcon(),
                                            label,
                                            this,
                                            SLOT(actionInvoked()),
                                            QKeySequence(Qt::CTRL +
                                                         Qt::Key_PageUp));
   }
#endif

   if (pAction != nullptr)
   {
      pAction->setData(commandId);
      pAction->setToolTip(tooltip);
      return pAction;
   }
   else
   {
      return nullptr;
   }
}

QAction* MenuCallback::duplicateAppMenuAction(QString commandToDuplicate,
                                              QString commandId,
                                              QString label,
                                              QString tooltip,
                                              QKeySequence keySequence,
                                              bool checkable)
{
   QAction* pAction = nullptr;
   if (commandId == commandToDuplicate)
   {
      pAction = new QAction(QIcon(), label);
      pAction->setMenuRole(QAction::NoRole);
      pAction->setData(commandId);
      pAction->setToolTip(tooltip);
      pAction->setShortcut(keySequence);
      if (checkable)
         pAction->setCheckable(true);

      menuStack_.top()->addAction(pAction);

      auto* pBinder = new MenuActionBinder(menuStack_.top(), pAction);
      connect(pBinder, SIGNAL(manageCommand(QString, QAction * )), this, SIGNAL(manageCommand(QString, QAction * )));
      connect(pAction, SIGNAL(triggered()), this, SLOT(actionInvoked()));
   }
   return pAction;
}

void MenuCallback::addCommand(QString commandId,
                              QString label,
                              QString tooltip,
                              QString shortcut,
                              bool checkable)
{

#ifdef Q_OS_MAC
   // on macOS, replace instances of 'Ctrl' with 'Meta'; QKeySequence renders "Ctrl" using the
   // macOS command symbol, but we want the menu to show the literal Ctrl symbol (^)
   shortcut.replace(QStringLiteral("Ctrl"), QStringLiteral("Meta"));
#endif

   // replace instances of 'Cmd' with 'Ctrl' -- note that on macOS
   // Qt automatically maps that to the Command key
   shortcut.replace(QStringLiteral("Cmd"), QStringLiteral("Ctrl"));
   
   QKeySequence keySequence(shortcut);
{'fraction_non_alphanumeric': 0.09136554448925838, 'fraction_numerical': 0.0032924520536669684, 'mean_word_length': 1.6464822478762797, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 41, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 104, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1200126', 'n_tokens_mistral': 3021, 'n_tokens_neox': 2934, 'n_words': 758}