Skip to content

FEAT(client): Enhance image viewer with zoom and pan capabilities#7210

Open
nixietab wants to merge 1 commit into
mumble-voip:masterfrom
nixietab:robust-imageviewer
Open

FEAT(client): Enhance image viewer with zoom and pan capabilities#7210
nixietab wants to merge 1 commit into
mumble-voip:masterfrom
nixietab:robust-imageviewer

Conversation

@nixietab
Copy link
Copy Markdown
Contributor

This improves the current image viewer originally implemented 9 months ago.

It now preserves the image aspect ratio correctly by introducing zoom support, click-and-drag navigation, and panning functionality for a more usable viewing experience.

It was tested and works correctly on my openSUSE Tumbleweed installation

Checks

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 22, 2026

Review Change Stack

Walkthrough

This PR refactors ResponsiveImageDialog to replace its QScrollArea/QLabel-based image preview with a QGraphicsScene/QGraphicsView implementation. The class header updates includes, replaces label and scroll area member pointers with graphics view/scene pointers, and replaces the resizeEvent override with showEvent and eventFilter overrides. The implementation adds graphics view configuration, defers initial scene fitting through a timer callback in showEvent, and implements wheel-zoom scaling and view refitting via an installed event filter.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'FEAT(client): Enhance image viewer with zoom and pan capabilities' accurately describes the main changes: adding zoom and pan functionality to the image viewer component.
Description check ✅ Passed The description covers the improvement to the image viewer, mentions key features (aspect ratio preservation, zoom, click-and-drag navigation, panning), includes testing information, and confirms commit guidelines compliance.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/mumble/widgets/ResponsiveImageDialog.cpp`:
- Around line 68-70: ResponsiveImageDialog currently calls
m_view->fitInView(...) on every QEvent::Resize in
ResponsiveImageDialog::eventFilter, which resets user zoom/pan; add a boolean
member (e.g., m_autoFitEnabled) initialized true (set true after show) and
change the resize branch to call fitInView only when m_autoFitEnabled is true;
additionally, in eventFilter detect user-initiated zoom/pan inputs (wheel events
that call m_view->scale(...), mouse press/move for panning, or gesture events)
and set m_autoFitEnabled = false when those occur so further resizes won’t
override the user’s view (optionally expose a method to re-enable auto-fit).
- Around line 59-67: The wheel handler in ResponsiveImageDialog::eventFilter
currently applies m_view->scale(…) unbounded on each QWheelEvent
(angleDelta().y()), so repeatedly zooming can make the view unusable; change it
to read the current scale from m_view->transform() (e.g. transform().m11()),
define sensible minScale and maxScale constants, compute the proposed new scale
factor when zooming in/out, clamp that factor to [minScale, maxScale], and only
call m_view->scale(...) (or adjust to an exact factor to reach the clamp
boundary) when the resulting scale stays within limits; ensure the logic lives
inside eventFilter and still returns true after handling the event.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: aa0de64f-782f-467f-b3aa-2e7650cbdf85

📥 Commits

Reviewing files that changed from the base of the PR and between c173ec5 and 1136a4a.

📒 Files selected for processing (2)
  • src/mumble/widgets/ResponsiveImageDialog.cpp
  • src/mumble/widgets/ResponsiveImageDialog.h

Comment on lines +59 to +67
if (obj == m_view->viewport() && event->type() == QEvent::Wheel) {
QWheelEvent *wheelEvent = static_cast< QWheelEvent * >(event);
// Zoom in or out.
if (wheelEvent->angleDelta().y() > 0) {
m_view->scale(1.1, 1.1);
} else if (wheelEvent->angleDelta().y() < 0) {
m_view->scale(1.0 / 1.1, 1.0 / 1.1);
}
return true; // Event handled.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="src/mumble/widgets/ResponsiveImageDialog.cpp"

# Ensure file exists
test -f "$file"

echo "== File: $file =="
nl -ba "$file" | sed -n '1,220p'

echo "== Zoom/scale related matches in file =="
rg -n "wheelEvent|QWheelEvent|angleDelta|scale\(|transform\(\)\.m11|m11\(|setTransformationAnchor|setTransform|clamp|min|max|zoom" "$file" || true

echo "== m_view->scale usage in widgets folder (context) =="
rg -n "m_view-&gt;scale\(" src/mumble/widgets | head -n 200 || true

echo "== Any existing min/max transform or zoom handling in repo (context) =="
rg -n "min.*scale|max.*scale|clamp.*scale|m11\(\).*scale|setTransform|fitInView|wheelEvent|QEvent::Wheel" src/mumble/widgets | head -n 200 || true

Repository: mumble-voip/mumble

Length of output: 181


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="src/mumble/widgets/ResponsiveImageDialog.cpp"
test -f "$file"

echo "== File: $file (lines 1-220) =="
cat -n "$file" | sed -n '1,220p'

echo "== Zoom/scale related matches in file =="
rg -n "wheelEvent|QWheelEvent|angleDelta|scale\(|transform\(\)\.m11|m11\(|setTransformationAnchor|setTransform|clamp|min|max|zoom" "$file" || true

echo "== m_view->scale usage in widgets folder =="
rg -n "m_view->scale\(" src/mumble/widgets | head -n 200 || true

echo "== Repo context for existing zoom/transform clamps (widgets folder) =="
rg -n "min.*scale|max.*scale|clamp.*scale|m11\(\).*scale|setTransform|fitInView|wheelEvent|QEvent::Wheel" src/mumble/widgets | head -n 200 || true

Repository: mumble-voip/mumble

Length of output: 5470


Clamp wheel zoom range to keep the view recoverable.

ResponsiveImageDialog::eventFilter() applies unbounded m_view->scale(…) on every wheel event (angleDelta().y() > 0 / < 0) with no min/max transform limits; repeated zooming can make the image effectively unusable until a resize/show resets via fitInView.

Proposed fix
 if (obj == m_view->viewport() && event->type() == QEvent::Wheel) {
 	QWheelEvent *wheelEvent = static_cast< QWheelEvent * >(event);
-	// Zoom in or out.
-	if (wheelEvent->angleDelta().y() > 0) {
-		m_view->scale(1.1, 1.1);
-	} else if (wheelEvent->angleDelta().y() < 0) {
-		m_view->scale(1.0 / 1.1, 1.0 / 1.1);
-	}
+	const qreal currentScale = m_view->transform().m11();
+	const qreal factor       = (wheelEvent->angleDelta().y() > 0) ? 1.1 : (1.0 / 1.1);
+	const qreal nextScale    = currentScale * factor;
+	if (wheelEvent->angleDelta().y() != 0 && nextScale >= 0.1 && nextScale <= 10.0) {
+		m_view->scale(factor, factor);
+	}
 	return true; // Event handled.
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/mumble/widgets/ResponsiveImageDialog.cpp` around lines 59 - 67, The wheel
handler in ResponsiveImageDialog::eventFilter currently applies m_view->scale(…)
unbounded on each QWheelEvent (angleDelta().y()), so repeatedly zooming can make
the view unusable; change it to read the current scale from m_view->transform()
(e.g. transform().m11()), define sensible minScale and maxScale constants,
compute the proposed new scale factor when zooming in/out, clamp that factor to
[minScale, maxScale], and only call m_view->scale(...) (or adjust to an exact
factor to reach the clamp boundary) when the resulting scale stays within
limits; ensure the logic lives inside eventFilter and still returns true after
handling the event.

Comment on lines +68 to +70
} else if (obj == m_view && event->type() == QEvent::Resize) {
m_view->fitInView(m_scene->itemsBoundingRect(), Qt::KeepAspectRatio);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="src/mumble/widgets/ResponsiveImageDialog.cpp"
if [ ! -f "$FILE" ]; then
  echo "Missing file: $FILE" >&2
  exit 1
fi

# Show the relevant region around the reported lines
nl -ba "$FILE" | sed -n '1,140p'

# Also search for fitInView usage and any zoom/pan/auto-fit related flags
rg -n "fitInView|KeepAspectRatio|itemsBoundingRect|auto.?fit|zoom|pan" "$FILE" || true

# Search for any event handlers or state tracking in the same file
rg -n "QEvent::Resize|event->type\\(\\)|mouse|wheel|scroll|drag" "$FILE" || true

Repository: mumble-voip/mumble

Length of output: 106


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="src/mumble/widgets/ResponsiveImageDialog.cpp"
if [ ! -f "$FILE" ]; then
  echo "Missing file: $FILE" >&2
  exit 1
fi

# Print with line numbers using awk (nl isn't available)
awk '{printf "%5d\t%s\n", NR, $0}' "$FILE" | sed -n '50,110p'

echo "---- searches ----"
rg -n "fitInView|KeepAspectRatio|itemsBoundingRect|auto.?fit|zoom|pan" "$FILE" || true
rg -n "QEvent::Resize|event->type\\(|mouse|wheel|scroll|drag" "$FILE" || true

Repository: mumble-voip/mumble

Length of output: 1904


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="src/mumble/widgets/ResponsiveImageDialog.cpp"
awk '{printf "%5d\t%s\n", NR, $0}' "$FILE" | sed -n '1,220p'

echo "---- additional searches ----"
rg -n "fitInView\\(|scale\\(|translate\\(|scroll|drag|mouse|pan|zoom|auto.?fit|manual|user" "$FILE" || true

Repository: mumble-voip/mumble

Length of output: 3640


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="src/mumble/widgets/ResponsiveImageDialog.h"
if [ ! -f "$FILE" ]; then
  echo "Missing file: $FILE" >&2
  exit 1
fi

awk '{printf "%5d\t%s\n", NR, $0}' "$FILE" | sed -n '1,200p'

echo "---- searches ----"
rg -n "fitInView|auto.?fit|zoom|pan|scale|transform|eventFilter|resize" "$FILE" || true

Repository: mumble-voip/mumble

Length of output: 1243


Don’t reset user zoom/pan on every resize.

ResponsiveImageDialog::eventFilter unconditionally calls m_view->fitInView(...) on every QEvent::Resize, overriding the wheel zoom (m_view->scale(...)) and resetting the user’s current view/pan state:

} else if (obj == m_view && event->type() == QEvent::Resize) {
	m_view->fitInView(m_scene->itemsBoundingRect(), Qt::KeepAspectRatio);
}

Gate this resize “auto-fit” behind an “auto-fit enabled” flag (enabled initially / after show), and disable it after the first manual zoom/pan input.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/mumble/widgets/ResponsiveImageDialog.cpp` around lines 68 - 70,
ResponsiveImageDialog currently calls m_view->fitInView(...) on every
QEvent::Resize in ResponsiveImageDialog::eventFilter, which resets user
zoom/pan; add a boolean member (e.g., m_autoFitEnabled) initialized true (set
true after show) and change the resize branch to call fitInView only when
m_autoFitEnabled is true; additionally, in eventFilter detect user-initiated
zoom/pan inputs (wheel events that call m_view->scale(...), mouse press/move for
panning, or gesture events) and set m_autoFitEnabled = false when those occur so
further resizes won’t override the user’s view (optionally expose a method to
re-enable auto-fit).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant