Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,26 @@ All notable changes to Q1View are documented here. Releases follow [semantic ver

---

## [2.8.2] — 2026-10-19

### Changed

- Windows Viewer: retain direct resolution, color-space and FPS menus; show sampling at bottom left and zoom at bottom right. Simplify Options and Browse without duplicate Open/Browser, View/More or Fit/100% controls. (#120)

- Windows Viewer and Comparator: add Selection Mode to Options, retaining S/context shortcuts and matching checks. Add Viewer window synchronization to Options; keep Comparator Clear Selection in its context menu/Esc.

### Fixed

- Viewer: autoplay videos and multi-frame RAW on open and Page Up/Page Down, honoring the saved setting without resizing the window. Keep single frames and internal reloads paused.

- Viewer: allow supported Y4M/.yuv resolution and color-space reinterpretation with correct frame boundaries, FPS, seeking and autoplay; refresh RAW menu labels after changes.

- Viewer and Comparator: prevent hover/arrow navigation from executing commands; unify Viewer context-menu fonts/colors and remove dashed disabled outlines while retaining keyboard focus.

- Viewer: keep Compare enabled without a file, opening an empty Comparator; avoid full menu scans when idle command availability is unchanged.

---

## [2.8.1] — 2026-10-18

### Changed
Expand Down Expand Up @@ -515,7 +535,8 @@ Native display checks passed at 100–175%; 200% layout/font calculations passed

---

[Unreleased]: https://github.com/chammoru/Q1View/compare/v2.8.1...HEAD
[Unreleased]: https://github.com/chammoru/Q1View/compare/v2.8.2...HEAD
[2.8.2]: https://github.com/chammoru/Q1View/compare/v2.8.1...v2.8.2
[2.8.1]: https://github.com/chammoru/Q1View/compare/v2.8.0...v2.8.1
[2.8.0]: https://github.com/chammoru/Q1View/compare/v2.7.28...v2.8.0
[2.7.28]: https://github.com/chammoru/Q1View/compare/v2.7.27...v2.7.28
Expand Down
14 changes: 14 additions & 0 deletions Comparator/ComparatorDoc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,22 @@
IMPLEMENT_DYNCREATE(CComparatorDoc, CDocument)

BEGIN_MESSAGE_MAP(CComparatorDoc, CDocument)
ON_COMMAND(ID_SELECTION_MODE, &CComparatorDoc::ToggleSelectionMode)
ON_UPDATE_COMMAND_UI(ID_SELECTION_MODE, &CComparatorDoc::OnUpdateSelectionMode)
END_MESSAGE_MAP()

void CComparatorDoc::ToggleSelectionMode()
{
mSelMode = !mSelMode;
UpdateAllViews(nullptr);
}

void CComparatorDoc::OnUpdateSelectionMode(CCmdUI *command)
{
command->Enable(TRUE);
command->SetCheck(mSelMode);
}


CComparatorDoc::CComparatorDoc()
: mPane(new ComparatorPane[IMG_VIEW_MAX])
Expand Down
2 changes: 2 additions & 0 deletions Comparator/ComparatorDoc.h
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,8 @@ class CComparatorDoc : public CDocument
DECLARE_MESSAGE_MAP()
public:
void ViewOnMouseWheel(short zDelta, int wCanvas, int hCanvas);
void ToggleSelectionMode();
afx_msg void OnUpdateSelectionMode(CCmdUI *command);
virtual BOOL OnOpenDocument(LPCTSTR lpszPathName);
};

17 changes: 10 additions & 7 deletions Comparator/ComparatorView.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -886,9 +886,9 @@ int CComparatorView::OnCreate(LPCREATESTRUCT lpCreateStruct)
mNameQMenu.MoveWindow(&mRcNameQMenu);

// Right-click popup, mirroring the MFC Viewer's mouse menu: a checkable
// "Sel Mode" toggle plus "Clear Selection" for the synchronized rectangle.
// Selection Mode is also available in Options; clearing stays contextual.
mMouseMenu.CreatePopupMenu();
mMouseMenu.AppendMenu(MF_STRING, ID_MOUSEMENU_START + QMouseMenuSel, _T("Sel Mode"));
mMouseMenu.AppendMenu(MF_STRING, ID_SELECTION_MODE, L"&Selection Mode\tS");
mMouseMenu.AppendMenu(MF_STRING, ID_MOUSEMENU_START + QMouseMenuClear, _T("Clear Selection"));

return 0;
Expand Down Expand Up @@ -1376,7 +1376,11 @@ void CComparatorView::OnCsChange(UINT nID)

void CComparatorView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
if (nChar == VK_TAB && mCsQMenu.IsWindowEnabled()) { mCsQMenu.SetFocus(); return; }
if (nChar == VK_TAB && mCsQMenu.IsWindowEnabled()) {
mCsQMenu.SetFocus();
mCsQMenu.SendMessage(WM_CHANGEUISTATE, MAKEWPARAM(UIS_CLEAR, UISF_HIDEFOCUS));
return;
}
CComparatorDoc* pDoc = GetDocument();

bool isProcessing = pDoc->CheckImgViewProcessing();
Expand Down Expand Up @@ -1414,8 +1418,8 @@ void CComparatorView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
Invalidate(FALSE);
break;
case 'S': // Toggle selection mode: left-drag selects a region (issue #74).
pDoc->mSelMode = !pDoc->mSelMode;
break;
pDoc->ToggleSelectionMode();
return;
case VK_ESCAPE: // Clear the synchronized selection rectangle.
ClearSelection(pDoc);
break;
Expand Down Expand Up @@ -1489,8 +1493,7 @@ void CComparatorView::OnMouseMenu(UINT nID)
switch (nID - ID_MOUSEMENU_START) {
case QMouseMenuSel:
// Same effect as the 'S' shortcut: left-drag selects instead of panning.
pDoc->mSelMode = !pDoc->mSelMode;
pDoc->UpdateAllViews(NULL);
pDoc->ToggleSelectionMode();
break;
case QMouseMenuClear:
ClearSelection(pDoc);
Expand Down
2 changes: 2 additions & 0 deletions Comparator/MainFrm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1048,6 +1048,8 @@ int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
// Options menu.
str.Format(_T("Allow Different Resolutions"));
mOptionsMenu.AppendMenu(MF_STRING, (UINT_PTR)ID_OPTIONS_DIFF_RESOLUTION, str);
mOptionsMenu.AppendMenu(MF_SEPARATOR);
mOptionsMenu.AppendMenu(MF_STRING, ID_SELECTION_MODE, L"&Selection Mode\tS");
mAppliedCanvasColor = Q1UI_COLOR_CANVAS_BG;

str.Format(_T("&Options"));
Expand Down
8 changes: 5 additions & 3 deletions Comparator/QMenuItem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ void CQMenuItem::Paint(HDC dc)
if (mMenu) {
const int arrow = q1view::WindowsUiPixels(10, q1view::WindowsUiDpi(m_hWnd));
const int x = rect.right - arrow / 2, y = rect.CenterPoint().y;
CPen pen(enabled ? PS_SOLID : PS_DASH, 1, q1view::WindowsUiColorValue(q1view::WindowsUiColor::Boundary));
CPen pen(PS_SOLID, 1, q1view::WindowsUiColorValue(q1view::WindowsUiColor::Boundary));
CPen* oldPen = target->SelectObject(&pen);
auto* oldBrush = target->SelectStockObject(NULL_BRUSH);
target->Rectangle(mRcClient);
Expand All @@ -81,7 +81,7 @@ void CQMenuItem::Paint(HDC dc)
}
if (pressed) rect.OffsetRect(1, 1);
target->DrawText(text, &rect, DT_SINGLELINE | DT_VCENTER | mTextHorizAlign | DT_END_ELLIPSIS | DT_NOPREFIX);
if (mMenu && enabled && GetFocus() == this) {
if (mMenu && enabled && GetFocus() == this && !(SendMessage(WM_QUERYUISTATE) & UISF_HIDEFOCUS)) {
CRect focus = mRcClient; focus.DeflateRect(3, 3); target->DrawFocusRect(focus);
}
}
Expand Down Expand Up @@ -121,13 +121,14 @@ BOOL CQMenuItem::OnEraseBkgnd(CDC*) { return TRUE; }
LRESULT CQMenuItem::WindowProc(UINT message, WPARAM wp, LPARAM lp)
{
if (message == OpenPopupMessage) { if (mPopupQueued) OpenPopup(); return 0; }
if (mMenu && message == WM_LBUTTONDOWN) SendMessage(WM_CHANGEUISTATE, MAKEWPARAM(UIS_SET, UISF_HIDEFOCUS));
const HWND self = m_hWnd;
const LRESULT result = CWnd::WindowProc(message, wp, lp);
// Owner-drawn native buttons can request a parent draw synchronously on a
// state change. Our local buffered WM_PAINT path must be invalidated too.
if (::IsWindow(self) && CWnd::FromHandlePermanent(self) == this &&
(message == BM_SETSTATE || message == WM_CAPTURECHANGED || message == WM_LBUTTONDOWN ||
message == WM_LBUTTONUP || message == WM_KEYDOWN || message == WM_KEYUP)) Invalidate(FALSE);
message == WM_LBUTTONUP || message == WM_KEYDOWN || message == WM_KEYUP || message == WM_UPDATEUISTATE)) Invalidate(FALSE);
return result;
}
void CQMenuItem::OnSize(UINT type, int cx, int cy) { CWnd::OnSize(type, cx, cy); mRcClient.SetRect(0, 0, cx, cy); }
Expand All @@ -145,6 +146,7 @@ void CQMenuItem::OnKillFocus(CWnd* next) { CWnd::OnKillFocus(next); Invalidate(F
void CQMenuItem::OnEnable(BOOL enable) { CWnd::OnEnable(enable); mMouseIn = false; Invalidate(FALSE); }
void CQMenuItem::OnKeyDown(UINT key, UINT repeat, UINT flags)
{
if (mMenu && IsWindowEnabled()) SendMessage(WM_CHANGEUISTATE, MAKEWPARAM(UIS_CLEAR, UISF_HIDEFOCUS));
if (mMenu && key == VK_TAB) { GetParent()->SetFocus(); return; }
if (mMenu && IsWindowEnabled() && (key == VK_RETURN || key == VK_DOWN || key == VK_F4)) {
if (!(flags & KF_REPEAT)) OnClicked();
Expand Down
1 change: 1 addition & 0 deletions Comparator/Resource.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#define ID_OPTIONS_END 0x75ff
#define ID_MOUSEMENU_START 0x7600
#define ID_MOUSEMENU_END 0x76ff
#define ID_SELECTION_MODE ID_MOUSEMENU_START
#define ID_MAGNIFY 32771
#define ID_COMPARATOR_HELP 32772

Expand Down
33 changes: 33 additions & 0 deletions QVisionCore/Q1UiCommandLayout.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#pragma once
#include <algorithm>
#include <vector>

namespace q1view {
struct WindowsUiCommandLayoutItem { int width, order, priority; };
struct WindowsUiCommandLayoutPlan { std::vector<int> visible, overflow; };

// Geometry-only policy; widths have already been measured using the actual
// font, DPI and accessibility scale. priority >= 100 is always in More.
inline WindowsUiCommandLayoutPlan PlanWindowsUiCommandRow(
const std::vector<WindowsUiCommandLayoutItem>& items, int more, int available)
{
WindowsUiCommandLayoutPlan plan;
int total = more >= 0 ? items[more].width : 0;
for (int i = 0; i < int(items.size()); ++i) {
if (i == more) continue;
if (items[i].priority >= 100) plan.overflow.push_back(i);
else { plan.visible.push_back(i); total += items[i].width; }
}
while (total > available && !plan.visible.empty()) {
auto least = std::max_element(plan.visible.begin(), plan.visible.end(), [&](int a, int b) {
return items[a].priority != items[b].priority ? items[a].priority < items[b].priority : items[a].order < items[b].order;
});
total -= items[*least].width; plan.overflow.push_back(*least); plan.visible.erase(least);
}
auto order = [&](int a, int b) { return items[a].order < items[b].order; };
std::stable_sort(plan.visible.begin(), plan.visible.end(), order);
std::stable_sort(plan.overflow.begin(), plan.overflow.end(), order);
if (more >= 0 && !plan.overflow.empty()) plan.visible.push_back(more);
return plan;
}
} // namespace q1view
4 changes: 2 additions & 2 deletions QVisionCore/Q1UiCommandWin.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
namespace q1view {

// Incremental policy for existing commands, not a replacement command router.
enum class WindowsUiCommand { RawInput, CopyPixels, PasteImage, CompareFile, ClearSelection };
enum class WindowsUiCommand { RawInput, CopyPixels, PasteImage, OpenComparator, ClearSelection };
struct WindowsUiCommandContext {
bool rawInput = true; // An empty session may preconfigure RAW interpretation.
bool pixels = false, clipboardImage = false, file = false, selection = false;
Expand All @@ -17,7 +17,7 @@ inline WindowsUiCommandState WindowsUiResolveCommand(WindowsUiCommand command, c
case WindowsUiCommand::RawInput: return {context.rawInput, L"Raw input only"};
case WindowsUiCommand::CopyPixels: return {context.pixels, L"No image pixels available"};
case WindowsUiCommand::PasteImage: return {context.clipboardImage, L"No image on the clipboard"};
case WindowsUiCommand::CompareFile: return {context.file, L"Open a file first"};
case WindowsUiCommand::OpenComparator: return {true, L""}; // Launch an empty comparison workspace too.
case WindowsUiCommand::ClearSelection: return {context.selection, L"No selection to clear"};
}
return {false, L"Unavailable"};
Expand Down
Loading
Loading