[Doxygen] Add javascript to toggle call/caller graph visibility. (#6195)

Took 49 minutes

Took 26 seconds

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
BruebachL
2025-09-28 21:22:37 +02:00
committed by GitHub
parent ddbf5e1457
commit 0833f94502
6 changed files with 347 additions and 49 deletions

View File

@@ -1375,7 +1375,7 @@ HTML_FILE_EXTENSION = .html
# of the possible markers and block names see the documentation.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_HEADER =
HTML_HEADER = doc/doxygen/html/header.html
# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each
# generated HTML page. If the tag is left blank Doxygen will generate a standard
@@ -1385,7 +1385,7 @@ HTML_HEADER =
# that Doxygen normally uses.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_FOOTER =
HTML_FOOTER = doc/doxygen/html/footer.html
# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style
# sheet that is used by each HTML page. It can be used to fine-tune the look of
@@ -1415,7 +1415,7 @@ HTML_STYLESHEET =
# documentation.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_EXTRA_STYLESHEET = doxygen_style.css
HTML_EXTRA_STYLESHEET = doc/doxygen/css/doxygen_style.css
# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
# other source files which should be copied to the HTML output directory. Note
@@ -1425,7 +1425,7 @@ HTML_EXTRA_STYLESHEET = doxygen_style.css
# files will be copied as-is; there are no commands or markers available.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_EXTRA_FILES =
HTML_EXTRA_FILES = doc/doxygen/js/graph_toggle.js
# The HTML_COLORSTYLE tag can be used to specify if the generated HTML output
# should be rendered with a dark or light theme.

View File

@@ -0,0 +1,88 @@
/* === Accent Tuning for Doxygen Dark Mode === */
/* Main accent color */
:root {
--main-accent: #33a946;
}
/* Links */
a, a:visited {
color: var(--main-accent) !important;
}
a:hover {
color: #4fd467 !important; /* lighter green */
}
/* Project title */
#projectname {
color: var(--main-accent) !important;
}
/* Current sidebar item */
#side-nav .selected {
border-left: 3px solid var(--main-accent) !important;
}
/* Tabs */
.tablist li.current {
background: var(--main-accent) !important;
color: #fff !important;
}
/* Search box focus */
#MSearchField:focus {
outline: 1px solid var(--main-accent) !important;
}
/* Code block border highlight */
pre, code {
border: 1px solid #333;
}
pre.fragment {
border-color: var(--main-accent) !important;
}
/* Graph toggle (caller / callee two-button) */
.graph-toggle {
display: inline-flex;
margin: 0.5em 0;
border: 1px solid #444;
border-radius: 6px;
overflow: hidden;
background: #111;
}
.graph-toggle button {
flex: 1 1 50%;
padding: 0.35em 0.8em;
background: transparent;
color: #aaa;
border: 0;
cursor: pointer;
font-size: 0.95em;
}
.graph-toggle button:hover:not(:disabled) {
background: #222;
color: #fff;
}
.graph-toggle button.active {
background: var(--main-accent);
color: #fff;
}
.graph-toggle button:disabled,
.graph-toggle button.disabled {
opacity: 0.45;
cursor: default;
}
/* small accessibility focus ring */
.graph-toggle button:focus {
outline: 2px solid rgba(51, 169, 70, 0.3);
outline-offset: 1px;
}

View File

@@ -0,0 +1,27 @@
<!-- HTML footer for doxygen 1.14.0-->
<!-- start footer part -->
<script src="$relpath^graph_toggle.js"></script>
<!--BEGIN GENERATE_TREEVIEW-->
<div class="navpath" id="nav-path"><!-- id is needed for treeview function! -->
<ul>
$navpath
<li class="footer">$generatedby <a href="https://www.doxygen.org/index.html"><img alt="doxygen"
class="footer"
height="31" src="$relpath^doxygen.svg"
width="104"/></a>
$doxygenversion
</li>
</ul>
</div>
<!--END GENERATE_TREEVIEW-->
<!--BEGIN !GENERATE_TREEVIEW-->
<hr class="footer"/>
<address class="footer"><small>
$generatedby&#160;<a href="https://www.doxygen.org/index.html"><img alt="doxygen" class="footer"
height="31" src="$relpath^doxygen.svg" width="104"/></a>
$doxygenversion
</small></address>
</div><!-- doc-content -->
<!--END !GENERATE_TREEVIEW-->
</body>
</html>

View File

@@ -0,0 +1,81 @@
<!-- HTML header for doxygen 1.14.0-->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html lang="$langISO" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta content="text/xhtml;charset=UTF-8" http-equiv="Content-Type"/>
<meta content="IE=11" http-equiv="X-UA-Compatible"/>
<meta content="Doxygen $doxygenversion" name="generator"/>
<meta content="width=device-width, initial-scale=1" name="viewport"/>
<!--BEGIN PROJECT_NAME--><title>$projectname: $title</title><!--END PROJECT_NAME-->
<!--BEGIN !PROJECT_NAME--><title>$title</title><!--END !PROJECT_NAME-->
<!--BEGIN PROJECT_ICON-->
<link href="$relpath^$projecticon" rel="icon" type="image/x-icon"/>
<!--END PROJECT_ICON-->
<link href="$relpath^tabs.css" rel="stylesheet" type="text/css"/>
<!--BEGIN FULL_SIDEBAR-->
<script type="text/javascript">var page_layout = 1;</script>
<!--END FULL_SIDEBAR-->
<script src="$relpath^jquery.js" type="text/javascript"></script>
<script src="$relpath^dynsections.js" type="text/javascript"></script>
<!--BEGIN COPY_CLIPBOARD-->
<script src="$relpath^clipboard.js" type="text/javascript"></script>
<!--END COPY_CLIPBOARD-->
$treeview
$search
$mathjax
$darkmode
<link href="$relpath^$stylesheet" rel="stylesheet" type="text/css"/>
$extrastylesheet
</head>
<body>
<!--BEGIN FULL_SIDEBAR-->
<div class="ui-resizable side-nav-resizable" id="side-nav"><!-- do not remove this div, it is closed by doxygen! -->
<!--END FULL_SIDEBAR-->
<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
<!--BEGIN TITLEAREA-->
<div id="titlearea">
<table cellpadding="0" cellspacing="0">
<tbody>
<tr id="projectrow">
<!--BEGIN PROJECT_LOGO-->
<td id="projectlogo"><img $logosize alt="Logo" src="$relpath^$projectlogo"/></td>
<!--END PROJECT_LOGO-->
<!--BEGIN PROJECT_NAME-->
<td id="projectalign">
<div id="projectname">$projectname<!--BEGIN PROJECT_NUMBER--><span id="projectnumber">&#160;$projectnumber</span>
<!--END PROJECT_NUMBER-->
</div>
<!--BEGIN PROJECT_BRIEF-->
<div id="projectbrief">$projectbrief</div><!--END PROJECT_BRIEF-->
</td>
<!--END PROJECT_NAME-->
<!--BEGIN !PROJECT_NAME-->
<!--BEGIN PROJECT_BRIEF-->
<td>
<div id="projectbrief">$projectbrief</div>
</td>
<!--END PROJECT_BRIEF-->
<!--END !PROJECT_NAME-->
<!--BEGIN DISABLE_INDEX-->
<!--BEGIN SEARCHENGINE-->
<!--BEGIN !FULL_SIDEBAR-->
<td>$searchbox</td>
<!--END !FULL_SIDEBAR-->
<!--END SEARCHENGINE-->
<!--END DISABLE_INDEX-->
</tr>
<!--BEGIN SEARCHENGINE-->
<!--BEGIN FULL_SIDEBAR-->
<tr>
<td colspan="2">$searchbox</td>
</tr>
<!--END FULL_SIDEBAR-->
<!--END SEARCHENGINE-->
</tbody>
</table>
</div>
<!--END TITLEAREA-->
<!-- end header part -->

View File

@@ -0,0 +1,147 @@
document.addEventListener("DOMContentLoaded", () => {
document.querySelectorAll(".memdoc").forEach(memdoc => {
let callContent = null, callHeader = null;
let callerContent = null, callerHeader = null;
memdoc.querySelectorAll("div.dynheader").forEach(header => {
const text = (header.textContent || "").trim().toLowerCase();
let content = header.nextElementSibling;
let tries = 0;
while (content && !content.classList.contains("dyncontent") && tries < 8) {
content = content.nextElementSibling;
tries++;
}
if (!content) return;
if (text.includes("caller")) {
callerContent = content;
callerHeader = header;
} else if (text.includes("call graph") || text.includes("callgraph") || text.includes("call graph for")) {
callContent = content;
callHeader = header;
} else if (text.includes("call")) {
if (!callContent) {
callContent = content;
callHeader = header;
}
}
});
if (!callContent && !callerContent) return;
if (memdoc.querySelector(".graph-toggle")) return;
const toggle = document.createElement("div");
toggle.className = "graph-toggle";
const callerBtn = document.createElement("button");
callerBtn.type = "button";
callerBtn.textContent = "Caller Graph";
const callBtn = document.createElement("button");
callBtn.type = "button";
callBtn.textContent = "Call Graph";
toggle.appendChild(callerBtn);
toggle.appendChild(callBtn);
const firstHeader = memdoc.querySelector("div.dynheader");
memdoc.insertBefore(toggle, firstHeader || memdoc.firstChild);
// hide everything initially
if (callerContent) {
callerContent.style.display = "none";
callerHeader.style.display = "none";
}
if (callContent) {
callContent.style.display = "none";
callHeader.style.display = "none";
}
// disable missing buttons
if (!callerContent) {
callerBtn.disabled = true;
callerBtn.classList.add("disabled");
}
if (!callContent) {
callBtn.disabled = true;
callBtn.classList.add("disabled");
}
// track current state
let current = null; // "caller", "call", "both", null=hidden
function setActive(type) {
if (type === "caller") {
if (current === "caller") { // hide it
if (callerContent) {
callerContent.style.display = "none";
callerHeader.style.display = "none";
}
current = null;
} else if (current === "call") { // show both
if (callerContent) {
callerContent.style.display = "block";
callerHeader.style.display = "block";
}
current = "both";
} else if (current === "both") { // hide caller only → call only
if (callerContent) {
callerContent.style.display = "none";
callerHeader.style.display = "none";
}
current = "call";
} else { // nothing visible → show caller
if (callerContent) {
callerContent.style.display = "block";
callerHeader.style.display = "block";
}
if (callContent) {
callContent.style.display = "none";
callHeader.style.display = "none";
}
current = "caller";
}
} else if (type === "call") {
if (current === "call") { // hide it
if (callContent) {
callContent.style.display = "none";
callHeader.style.display = "none";
}
current = null;
} else if (current === "caller") { // show both
if (callContent) {
callContent.style.display = "block";
callHeader.style.display = "block";
}
current = "both";
} else if (current === "both") { // hide call only → caller only
if (callContent) {
callContent.style.display = "none";
callHeader.style.display = "none";
}
current = "caller";
} else { // nothing visible → show call only
if (callContent) {
callContent.style.display = "block";
callHeader.style.display = "block";
}
if (callerContent) {
callerContent.style.display = "none";
callerHeader.style.display = "none";
}
current = "call";
}
}
// update button styles
callerBtn.classList.toggle("active", current === "caller" || current === "both");
callBtn.classList.toggle("active", current === "call" || current === "both");
}
callerBtn.addEventListener("click", () => {
if (!callerBtn.disabled) setActive("caller");
});
callBtn.addEventListener("click", () => {
if (!callBtn.disabled) setActive("call");
});
});
});

View File

@@ -1,45 +0,0 @@
/* === Accent Tuning for Doxygen Dark Mode === */
/* Main accent color */
:root {
--main-accent: #33a946;
}
/* Links */
a, a:visited {
color: var(--main-accent) !important;
}
a:hover {
color: #4fd467 !important; /* lighter green */
}
/* Project title */
#projectname {
color: var(--main-accent) !important;
}
/* Current sidebar item */
#side-nav .selected {
border-left: 3px solid var(--main-accent) !important;
}
/* Tabs */
.tablist li.current {
background: var(--main-accent) !important;
color: #fff !important;
}
/* Search box focus */
#MSearchField:focus {
outline: 1px solid var(--main-accent) !important;
}
/* Code block border highlight */
pre, code {
border: 1px solid #333;
}
pre.fragment {
border-color: var(--main-accent) !important;
}