Skip to content

Commit

Permalink
[GUI.Qt] Add clickable link to online documentation (#4650)
Browse files Browse the repository at this point in the history
* [GUI.Qt] Add clickable link to online documentation

* Revert adding hard-coded url

* Deduce URL automatically from namespace and class name

* Define URL at the toplevel
  • Loading branch information
alxbilger committed May 2, 2024
1 parent be90086 commit c8ea977
Show file tree
Hide file tree
Showing 9 changed files with 101 additions and 2 deletions.
6 changes: 5 additions & 1 deletion CMakeLists.txt
@@ -1,5 +1,7 @@
cmake_minimum_required(VERSION 3.22)
project(Sofa) # Cannot use VERSION with patch like "00"
project(Sofa
HOMEPAGE_URL https://www.sofa-framework.org/
) # Cannot use VERSION with patch like "00"

include(CMakeDependentOption)

Expand All @@ -9,6 +11,8 @@ set(Sofa_VERSION_MINOR 06)
set(Sofa_VERSION_PATCH 99)
set(Sofa_VERSION ${Sofa_VERSION_MAJOR}.${Sofa_VERSION_MINOR}.${Sofa_VERSION_PATCH})

set(SOFA_URL "${CMAKE_PROJECT_HOMEPAGE_URL}")

set(SOFA_VERSION_STR "\"${Sofa_VERSION}\"")
set(SOFA_VERSION "${Sofa_VERSION_MAJOR}${Sofa_VERSION_MINOR}${Sofa_VERSION_PATCH}")

Expand Down
25 changes: 25 additions & 0 deletions Sofa/GUI/Qt/src/sofa/gui/qt/QDataDescriptionWidget.cpp
Expand Up @@ -48,6 +48,25 @@ void QDataDescriptionWidget::addRow(QGridLayout* grid, const std::string& title,
grid->addWidget(tmplabel, row, 1, Qt::AlignTop);
}

void QDataDescriptionWidget::addRowHyperLink(QGridLayout* grid,
const std::string& title, const std::string& value, unsigned int row,
unsigned int minimumWidth)
{
QLabel* titlew = new QLabel(QString(title.c_str()));
grid->addWidget(titlew, row, 0, Qt::AlignTop);

QLabel* tmplabel = (new QLabel(QString(value.c_str())));
tmplabel->setMinimumWidth(20);
tmplabel->setWordWrap(true);
tmplabel->setAlignment(Qt::AlignTop);
tmplabel->setSizePolicy(QSizePolicy::MinimumExpanding,
QSizePolicy::MinimumExpanding);
tmplabel->setTextFormat(Qt::RichText);
tmplabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
tmplabel->setOpenExternalLinks(true);
grid->addWidget(tmplabel, row, 1, Qt::AlignTop);
}

QDataDescriptionWidget::QDataDescriptionWidget(QWidget* parent, core::objectmodel::Base* object)
:QWidget(parent)
{
Expand Down Expand Up @@ -110,6 +129,12 @@ QDataDescriptionWidget::QDataDescriptionWidget(QWidget* parent, core::objectmode
addRow(boxLayout, "Description", entry.description, nextRow, 20);
nextRow++;
}
if (!entry.documentationURL.empty() && entry.documentationURL != std::string("TODO"))
{
const std::string textURL = "<a href=\"" + entry.documentationURL + "\">" + entry.documentationURL + "</a>";
addRowHyperLink(boxLayout, "Documentation URL", textURL, nextRow, 20);
nextRow++;
}
const core::ObjectFactory::CreatorMap::iterator it = entry.creatorMap.find(object->getTemplateName());
if (it != entry.creatorMap.end() && *it->second->getTarget())
{
Expand Down
3 changes: 3 additions & 0 deletions Sofa/GUI/Qt/src/sofa/gui/qt/QDataDescriptionWidget.h
Expand Up @@ -42,6 +42,9 @@ class SOFA_GUI_QT_API QDataDescriptionWidget : public QWidget

void addRow(QGridLayout* grid, const std::string& title,
const std::string& value, unsigned int row, unsigned int minimumWidth =0);

void addRowHyperLink(QGridLayout* grid, const std::string& title,
const std::string& value, unsigned int row, unsigned int minimumWidth =0);
};


Expand Down
3 changes: 3 additions & 0 deletions Sofa/framework/Config/CMakeLists.txt
Expand Up @@ -84,6 +84,7 @@ set(SOFACONFIGSRC_ROOT "src/sofa")
set(HEADER_FILES
${SOFACONFIGSRC_ROOT}/config.h.in
${SOFACONFIGSRC_ROOT}/version.h.in
${SOFACONFIGSRC_ROOT}/url.h.in
)
set(SOURCE_FILES
${SOFACONFIGSRC_ROOT}/initSofaConfig.cpp # necessary to build a library
Expand Down Expand Up @@ -351,6 +352,8 @@ set_target_properties(${PROJECT_NAME} PROPERTIES Sofa_VERSION "${Sofa_VERSION}")
set_target_properties(${PROJECT_NAME} PROPERTIES SOFA_VERSION_STR "${SOFA_VERSION_STR}")
set_target_properties(${PROJECT_NAME} PROPERTIES SOFA_VERSION "${SOFA_VERSION}")

set_target_properties(${PROJECT_NAME} PROPERTIES SOFA_URL "${SOFA_URL}")

# CMakeParseLibraryList.cmake
configure_file(cmake/CMakeParseLibraryList.cmake ${CMAKE_BINARY_DIR}/lib/cmake/CMakeParseLibraryList.cmake COPYONLY)
install(FILES cmake/CMakeParseLibraryList.cmake DESTINATION lib/cmake/${PROJECT_NAME} COMPONENT headers)
Expand Down
28 changes: 28 additions & 0 deletions Sofa/framework/Config/src/sofa/url.h.in
@@ -0,0 +1,28 @@
/******************************************************************************
* SOFA, Simulation Open-Framework Architecture *
* (c) 2006 INRIA, USTL, UJF, CNRS, MGH *
* *
* This program is free software; you can redistribute it and/or modify it *
* under the terms of the GNU Lesser General Public License as published by *
* the Free Software Foundation; either version 2.1 of the License, or (at *
* your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, but WITHOUT *
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License *
* for more details. *
* *
* You should have received a copy of the GNU Lesser General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*******************************************************************************
* Authors: The SOFA Team and external contributors (see Authors.txt) *
* *
* Contact information: contact@sofa-framework.org *
******************************************************************************/
#pragma once

namespace sofa
{
constexpr const char* SOFA_URL = "@SOFA_URL@";
constexpr const char* SOFA_DOCUMENTATION_URL = "@SOFA_URL@community/doc/";
}
2 changes: 2 additions & 0 deletions Sofa/framework/Core/src/sofa/core/ComponentLibrary.cpp
Expand Up @@ -76,6 +76,8 @@ ComponentLibrary::ComponentLibrary( const std::string &componentN, const std::st
description += std::string("<li><b>Authors: </b>")+entry->authors +std::string("</li>");
if (!entry->license.empty())
description += std::string("<li><b>License: </b>") + entry->license + std::string("</li>");
if (!entry->documentationURL.empty())
description += std::string("<li><b>Documentation: </b>") + entry->documentationURL + std::string("</li>");

if (possiblePaths.size() != 0)
{
Expand Down
15 changes: 14 additions & 1 deletion Sofa/framework/Core/src/sofa/core/ObjectFactory.cpp
Expand Up @@ -467,6 +467,8 @@ void ObjectFactory::dump(std::ostream& out)
out << " authors : " << entry->authors << "\n";
if (!entry->license.empty())
out << " license : " << entry->license << "\n";
if (!entry->documentationURL.empty())
out << " documentation : " << entry->documentationURL << "\n";
for (CreatorMap::iterator itc = entry->creatorMap.begin(), itcend = entry->creatorMap.end(); itc != itcend; ++itc)
{
out << " template instance : " << itc->first << "\n";
Expand Down Expand Up @@ -507,6 +509,8 @@ void ObjectFactory::dumpXML(std::ostream& out)
out << "<authors>"<<entry->authors<<"</authors>\n";
if (!entry->license.empty())
out << "<license>"<<entry->license<<"</license>\n";
if (!entry->documentationURL.empty())
out << "<documentation>"<<entry->documentationURL<<"</documentation>\n";
for (CreatorMap::iterator itc = entry->creatorMap.begin(), itcend = entry->creatorMap.end(); itc != itcend; ++itc)
{
out << "<creator";
Expand Down Expand Up @@ -539,6 +543,8 @@ void ObjectFactory::dumpHTML(std::ostream& out)
out << "<li>Authors: <i>"<<entry->authors<<"</i></li>\n";
if (!entry->license.empty())
out << "<li>License: <i>"<<entry->license<<"</i></li>\n";
if (!entry->documentationURL.empty())
out << "<li>Documentation: <i>"<<entry->documentationURL<<"</i></li>\n";
if (entry->creatorMap.size()>2 || (entry->creatorMap.size()==1 && !entry->creatorMap.begin()->first.empty()))
{
out << "<li>Template instances:<i>";
Expand Down Expand Up @@ -591,14 +597,20 @@ RegisterObject& RegisterObject::addLicense(std::string val)
return *this;
}

RegisterObject& RegisterObject::addDocumentationURL(std::string url)
{
entry.documentationURL += url;
return *this;
}

RegisterObject& RegisterObject::addCreator(std::string classname,
std::string templatename,
ObjectFactory::Creator::SPtr creator)
{

if (!entry.className.empty() && entry.className != classname)
{
msg_error("ObjectFactory") << "Template already instanciated with a different classname: " << entry.className << " != " << classname;
msg_error("ObjectFactory") << "Template already instantiated with a different classname: " << entry.className << " != " << classname;
}
else if (entry.creatorMap.find(templatename) != entry.creatorMap.end())
{
Expand All @@ -624,6 +636,7 @@ RegisterObject::operator int()
reg.description += entry.description;
reg.authors += entry.authors;
reg.license += entry.license;
reg.documentationURL += entry.documentationURL;
if (!entry.defaultTemplate.empty())
{
if (!reg.defaultTemplate.empty())
Expand Down
20 changes: 20 additions & 0 deletions Sofa/framework/Core/src/sofa/core/ObjectFactory.h
Expand Up @@ -24,6 +24,9 @@
#include <sofa/core/objectmodel/BaseObject.h>
#include <sofa/core/objectmodel/BaseClassNameHelper.h>
#include <numeric>
#include <sofa/helper/Utils.h>
#include <sofa/url.h>


namespace sofa::core
{
Expand Down Expand Up @@ -86,6 +89,7 @@ class SOFA_CORE_API ObjectFactory
std::string description;
std::string authors;
std::string license;
std::string documentationURL;
std::string defaultTemplate;
CreatorMap creatorMap;
std::map<std::string, std::vector<std::string>> m_dataAlias ;
Expand Down Expand Up @@ -309,6 +313,9 @@ class SOFA_CORE_API RegisterObject
/// Specify a license (LGPL, GPL, ...)
RegisterObject& addLicense(std::string val);

/// Specify a documentation URL
RegisterObject& addDocumentationURL(std::string url);

/// Add a creator able to instance this class with the given templatename.
///
/// See the add<RealObject>() method for an easy way to add a Creator.
Expand All @@ -327,6 +334,19 @@ class SOFA_CORE_API RegisterObject
if (defaultTemplate)
entry.defaultTemplate = templatename;

if (entry.documentationURL.empty())
{
const std::string target = sofa_tostring(SOFA_TARGET);
const auto modulePaths = sofa::helper::split(target, '.');
if (modulePaths.size() > 2 && modulePaths[0] == "Sofa" && modulePaths[1] == "Component")
{
entry.documentationURL = std::string(sofa::SOFA_DOCUMENTATION_URL) + std::string("components/");
entry.documentationURL += sofa::helper::join(modulePaths.begin() + 2, modulePaths.end(),
[](const std::string& m){ return sofa::helper::Utils::downcaseString(m);}, "/");
entry.documentationURL += "/" + sofa::helper::Utils::downcaseString(classname);
}
}

return addCreator(classname, templatename, ObjectFactory::Creator::SPtr(new ObjectCreator<RealObject>));
}

Expand Down
Expand Up @@ -60,6 +60,7 @@ namespace sofa::simulation

int DefaultAnimationLoopClass = core::RegisterObject("Simulation loop to use in scene without constraints nor contact.")
.add<DefaultAnimationLoop>()
.addDocumentationURL(std::string(sofa::SOFA_DOCUMENTATION_URL) + std::string("components/animationloops/defaultanimationloop/"))
.addDescription(R"(
This loop triggers the following steps:
- build and solve all linear systems in the scene : collision and time integration to compute the new values of the dofs
Expand Down

0 comments on commit c8ea977

Please sign in to comment.