Skip to content

Commit

Permalink
macOS: Action to hide/show titlebar
Browse files Browse the repository at this point in the history
  • Loading branch information
jdpurcell committed Oct 21, 2024
1 parent 909e013 commit 8170c20
Show file tree
Hide file tree
Showing 10 changed files with 207 additions and 26 deletions.
9 changes: 9 additions & 0 deletions src/actionmanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,8 @@ QMenu *ActionManager::buildViewMenu(bool addIcon, QWidget *parent)
addCloneOfAction(viewMenu, "mirror");
addCloneOfAction(viewMenu, "flip");
viewMenu->addSeparator();
if (qvApp->supportsTitlebarHiding())
addCloneOfAction(viewMenu, "toggletitlebar");
addCloneOfAction(viewMenu, "fullscreen");

menuCloneLibrary.insert(viewMenu->menuAction()->data().toString(), viewMenu);
Expand Down Expand Up @@ -628,6 +630,8 @@ void ActionManager::actionTriggered(QAction *triggeredAction, MainWindow *releva
relevantWindow->mirror();
} else if (key == "flip") {
relevantWindow->flip();
} else if (key == "toggletitlebar") {
relevantWindow->toggleTitlebarHidden();
} else if (key == "fullscreen") {
relevantWindow->toggleFullScreen();
} else if (key == "firstfile") {
Expand Down Expand Up @@ -760,8 +764,13 @@ void ActionManager::initializeActionLibrary()
flipAction->setData({"disable"});
actionLibrary.insert("flip", flipAction);

auto *toggleTitlebarAction = new QAction(tr("Hide Title&bar"));
toggleTitlebarAction->setData({"windowdisable"});
actionLibrary.insert("toggletitlebar", toggleTitlebarAction);

auto *fullScreenAction = new QAction(QIcon::fromTheme("view-fullscreen"), tr("Enter F&ull Screen"));
fullScreenAction->setMenuRole(QAction::NoRole);
fullScreenAction->setData({"windowdisable"});
actionLibrary.insert("fullscreen", fullScreenAction);

auto *firstFileAction = new QAction(QIcon::fromTheme("go-first"), tr("&First File"));
Expand Down
137 changes: 111 additions & 26 deletions src/mainwindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ MainWindow::MainWindow(QWidget *parent) :
// Initialize variables
justLaunchedWithImage = false;
storedWindowState = Qt::WindowNoState;
storedTitlebarHidden = false;

// Initialize graphicsviewkDefaultBufferAlignment
graphicsView = new QVGraphicsView(this);
Expand All @@ -66,7 +67,7 @@ MainWindow::MainWindow(QWidget *parent) :
// Initialize escape shortcut
escShortcut = new QShortcut(Qt::Key_Escape, this);
connect(escShortcut, &QShortcut::activated, this, [this](){
if (windowState() == Qt::WindowFullScreen)
if (windowState().testFlag(Qt::WindowFullScreen))
toggleFullScreen();
});

Expand Down Expand Up @@ -131,6 +132,9 @@ MainWindow::MainWindow(QWidget *parent) :
ActionManager::actionTriggered(triggeredAction, this);
});

// Enable actions related to having a window
disableActions();

// Connect functions to application components
connect(&qvApp->getShortcutManager(), &ShortcutManager::shortcutsUpdated, this, &MainWindow::shortcutsUpdated);
connect(&qvApp->getSettingsManager(), &SettingsManager::settingsUpdated, this, &MainWindow::settingsUpdated);
Expand Down Expand Up @@ -234,24 +238,12 @@ void MainWindow::changeEvent(QEvent *event)
{
if (event->type() == QEvent::WindowStateChange)
{
const auto fullscreenActions = qvApp->getActionManager().getAllClonesOfAction("fullscreen", this);
for (const auto &fullscreenAction : fullscreenActions)
{
if (windowState() == Qt::WindowFullScreen)
{
fullscreenAction->setText(tr("Exit F&ull Screen"));
fullscreenAction->setIcon(QIcon::fromTheme("view-restore"));
}
else
{
fullscreenAction->setText(tr("Enter F&ull Screen"));
fullscreenAction->setIcon(QIcon::fromTheme("view-fullscreen"));
}
}

if (qvApp->getSettingsManager().getBoolean("fullscreendetails"))
ui->fullscreenLabel->setVisible(windowState() == Qt::WindowFullScreen);
const auto *changeEvent = static_cast<QWindowStateChangeEvent*>(event);
if (windowState().testFlag(Qt::WindowFullScreen) != changeEvent->oldState().testFlag(Qt::WindowFullScreen))
fullscreenChanged();
}

QMainWindow::changeEvent(event);
}

void MainWindow::mousePressEvent(QMouseEvent *event)
Expand Down Expand Up @@ -314,6 +306,23 @@ void MainWindow::paintEvent(QPaintEvent *event)
}
}

void MainWindow::fullscreenChanged()
{
const bool isFullscreen = windowState().testFlag(Qt::WindowFullScreen);
const auto fullscreenActions = qvApp->getActionManager().getAllClonesOfAction("fullscreen", this);
for (const auto &fullscreenAction : fullscreenActions)
{
fullscreenAction->setText(isFullscreen ? tr("Exit F&ull Screen") : tr("Enter F&ull Screen"));
fullscreenAction->setIcon(isFullscreen ? QIcon::fromTheme("view-restore") : QIcon::fromTheme("view-fullscreen"));
}
ui->fullscreenLabel->setVisible(isFullscreen && qvApp->getSettingsManager().getBoolean("fullscreendetails"));
if (!isFullscreen && storedTitlebarHidden)
{
setTitlebarHidden(true);
storedTitlebarHidden = false;
}
}

void MainWindow::openFile(const QString &fileName)
{
graphicsView->loadFile(fileName);
Expand Down Expand Up @@ -348,7 +357,7 @@ void MainWindow::settingsUpdated()
slideshowTimer->setInterval(static_cast<int>(settingsManager.getDouble("slideshowtimer")*1000));


ui->fullscreenLabel->setVisible(qvApp->getSettingsManager().getBoolean("fullscreendetails") && (windowState() == Qt::WindowFullScreen));
ui->fullscreenLabel->setVisible(qvApp->getSettingsManager().getBoolean("fullscreendetails") && windowState().testFlag(Qt::WindowFullScreen));

setWindowSize();

Expand Down Expand Up @@ -387,6 +396,7 @@ void MainWindow::fileChanged()
if (info->isVisible())
refreshProperties();
buildWindowTitle();
updateWindowFilePath();

// repaint to handle error message
update();
Expand Down Expand Up @@ -422,6 +432,10 @@ void MainWindow::disableActions()
{
clone->setEnabled(!getCurrentFileDetails().folderFileInfoList.isEmpty());
}
else if (cloneData.last() == "windowdisable")
{
clone->setEnabled(true);
}
}
}
}
Expand Down Expand Up @@ -522,14 +536,62 @@ void MainWindow::buildWindowTitle()

// Update fullscreen label to titlebar text as well
ui->fullscreenLabel->setText(newString);
}

if (windowHandle() != nullptr)
{
if (getCurrentFileDetails().isPixmapLoaded)
windowHandle()->setFilePath(getCurrentFileDetails().fileInfo.absoluteFilePath());
void MainWindow::updateWindowFilePath()
{
if (!windowHandle())
return;

const bool shouldPopulate = getCurrentFileDetails().isPixmapLoaded && !getTitlebarHidden();
windowHandle()->setFilePath(shouldPopulate ? getCurrentFileDetails().fileInfo.absoluteFilePath() : "");
}

bool MainWindow::getTitlebarHidden() const
{
if (!windowHandle())
return false;

#if defined COCOA_LOADED && QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
return QVCocoaFunctions::getTitlebarHidden(windowHandle());
#else
return !windowFlags().testFlag(Qt::WindowTitleHint);
#endif
}

void MainWindow::setTitlebarHidden(const bool shouldHide)
{
if (!windowHandle())
return;

auto customizeWindowFlags = [this](const Qt::WindowFlags flagsToChange, const bool on) {
Qt::WindowFlags newFlags = windowFlags() | Qt::CustomizeWindowHint;
if (on)
newFlags |= flagsToChange;
else
windowHandle()->setFilePath("");
newFlags &= ~flagsToChange;
overrideWindowFlags(newFlags);
windowHandle()->setFlags(newFlags);
};

#if defined COCOA_LOADED && QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
QVCocoaFunctions::setTitlebarHidden(windowHandle(), shouldHide);
customizeWindowFlags(Qt::WindowCloseButtonHint | Qt::WindowMinMaxButtonsHint | Qt::WindowFullscreenButtonHint, !shouldHide);
#elif defined WIN32_LOADED
customizeWindowFlags(Qt::WindowTitleHint | Qt::WindowMinMaxButtonsHint, !shouldHide);
#else
customizeWindowFlags(Qt::WindowTitleHint, !shouldHide);
#endif

const auto toggleTitlebarActions = qvApp->getActionManager().getAllClonesOfAction("toggletitlebar", this);
for (const auto &toggleTitlebarAction : toggleTitlebarActions)
{
toggleTitlebarAction->setText(shouldHide ? tr("Show Title&bar") : tr("Hide Title&bar"));
}

updateWindowFilePath();
update();
resetZoom();
}

void MainWindow::setWindowSize()
Expand All @@ -545,7 +607,7 @@ void MainWindow::setWindowSize()
justLaunchedWithImage = false;

//check if window is maximized or fullscreened
if (windowState() == Qt::WindowMaximized || windowState() == Qt::WindowFullScreen)
if (windowState().testFlag(Qt::WindowMaximized) || windowState().testFlag(Qt::WindowFullScreen))
return;


Expand Down Expand Up @@ -1181,15 +1243,38 @@ void MainWindow::increaseSpeed()

void MainWindow::toggleFullScreen()
{
if (windowState() == Qt::WindowFullScreen)
// Note: This is only triggered by the menu action, so the logic here should be kept to a minimum. Anything that
// needs to run even if the window manager initiated the change should be triggered by QEvent::WindowStateChange.

// Disable updates during window state change to resolve visual glitches on macOS if the titlebar is hidden
setUpdatesEnabled(false);

if (windowState().testFlag(Qt::WindowFullScreen))
{
setWindowState(storedWindowState);
}
else
{
storedWindowState = windowState();

// Restore the titlebar if it was hidden because the window manager might do something special with the
// titlebar (e.g. macOS) in fullscreen mode or get confused by the titlebar being hidden (e.g. Windows).
storedTitlebarHidden = getTitlebarHidden();
if (storedTitlebarHidden)
setTitlebarHidden(false);

showFullScreen();
}

setUpdatesEnabled(true);
}

void MainWindow::toggleTitlebarHidden()
{
if (windowState().testFlag(Qt::WindowFullScreen))
return;

setTitlebarHidden(!getTitlebarHidden());
}

int MainWindow::getTitlebarOverlap() const
Expand Down
11 changes: 11 additions & 0 deletions src/mainwindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ class MainWindow : public QMainWindow

void buildWindowTitle();

void updateWindowFilePath();

bool getTitlebarHidden() const;

void setTitlebarHidden(const bool shouldHide);

void setWindowSize();

bool getIsPixmapLoaded() const;
Expand Down Expand Up @@ -111,6 +117,8 @@ class MainWindow : public QMainWindow

void toggleFullScreen();

void toggleTitlebarHidden();

int getTitlebarOverlap() const;

const QVImageCore::FileDetails& getCurrentFileDetails() const { return graphicsView->getCurrentFileDetails(); }
Expand Down Expand Up @@ -145,6 +153,8 @@ public slots:

void paintEvent(QPaintEvent *event) override;

void fullscreenChanged();

protected slots:
void settingsUpdated();
void shortcutsUpdated();
Expand All @@ -167,6 +177,7 @@ protected slots:
bool justLaunchedWithImage;

Qt::WindowStates storedWindowState;
bool storedTitlebarHidden;

QNetworkAccessManager networkAccessManager;

Expand Down
9 changes: 9 additions & 0 deletions src/qvapplication.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,15 @@ void QVApplication::defineFilterLists()
nameFilterList << tr("All Files") + " (*)";
}

bool QVApplication::supportsTitlebarHiding()
{
#if defined COCOA_LOADED && QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
return true;
#else
return false;
#endif
}

qreal QVApplication::getPerceivedBrightness(const QColor &color)
{
return (color.red() * 0.299 + color.green() * 0.587 + color.blue() * 0.114) / 255.0;
Expand Down
2 changes: 2 additions & 0 deletions src/qvapplication.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ class QVApplication : public QApplication

ActionManager &getActionManager() { return actionManager; }

static bool supportsTitlebarHiding();

static qreal getPerceivedBrightness(const QColor &color);

private:
Expand Down
4 changes: 4 additions & 0 deletions src/qvcocoafunctions.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ class QVCocoaFunctions

static void setFullSizeContentView(QWindow *window, const bool enable);

static bool getTitlebarHidden(QWindow *window);

static void setTitlebarHidden(QWindow *window, const bool shouldHide);

static void setVibrancy(bool alwaysDark, QWindow *window);

static int getObscuredHeight(QWindow *window);
Expand Down
13 changes: 13 additions & 0 deletions src/qvcocoafunctions.mm
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,19 @@ static void fixNativeMenuEccentricities(QMenu *menu, NSMenu *nativeMenu)
#endif
}

bool QVCocoaFunctions::getTitlebarHidden(QWindow *window)
{
auto *view = reinterpret_cast<NSView*>(window->winId());
return view.window.titleVisibility == NSWindowTitleHidden;
}

void QVCocoaFunctions::setTitlebarHidden(QWindow *window, const bool shouldHide)
{
auto *view = reinterpret_cast<NSView*>(window->winId());
view.window.titleVisibility = shouldHide ? NSWindowTitleHidden : NSWindowTitleVisible;
view.window.titlebarAppearsTransparent = shouldHide;
}

void QVCocoaFunctions::setVibrancy(bool alwaysDark, QWindow *window)
{
auto *view = reinterpret_cast<NSView*>(window->winId());
Expand Down
Loading

0 comments on commit 8170c20

Please sign in to comment.