qskinny/src/dialogs/QskDialog.cpp

626 lines
18 KiB
C++
Raw Normal View History

2017-07-21 16:21:34 +00:00
/******************************************************************************
2024-01-17 13:31:45 +00:00
* QSkinny - Copyright (C) The authors
2023-04-06 07:23:37 +00:00
* SPDX-License-Identifier: BSD-3-Clause
2017-07-21 16:21:34 +00:00
*****************************************************************************/
#include "QskDialog.h"
#include "QskDialogButtonBox.h"
2025-01-14 15:40:20 +00:00
#include "QskLinearBox.h"
2017-07-21 16:21:34 +00:00
#include "QskMessageSubWindow.h"
2018-08-03 06:15:28 +00:00
#include "QskMessageWindow.h"
2017-07-21 16:21:34 +00:00
#include "QskSelectionSubWindow.h"
2018-08-03 06:15:28 +00:00
#include "QskSelectionWindow.h"
2017-07-21 16:21:34 +00:00
2025-01-22 14:20:48 +00:00
#include "QskPushButton.h"
2025-01-23 08:56:36 +00:00
#include "QskScrollArea.h"
2025-01-14 15:40:20 +00:00
#include "QskSimpleListBox.h"
2017-07-21 16:21:34 +00:00
#include "QskFocusIndicator.h"
2025-01-23 08:56:36 +00:00
#include <qfilesystemmodel.h>
2018-08-03 06:15:28 +00:00
#include <qguiapplication.h>
2018-07-19 12:10:48 +00:00
#include <qpointer.h>
#include <qquickwindow.h>
2017-07-21 16:21:34 +00:00
#include <qpa/qplatformdialoghelper.h>
2018-11-06 17:54:21 +00:00
static QskDialog::Action qskActionCandidate( const QskDialogButtonBox* buttonBox )
{
// not the fastest code ever, but usually we always
// have a AcceptRole or YesRole button
const QskDialog::ActionRole candidates[] =
{
QskDialog::AcceptRole, QskDialog::YesRole,
QskDialog::RejectRole, QskDialog::NoRole, QskDialog::DestructiveRole,
QskDialog::UserRole, QskDialog::ResetRole,
QskDialog::ApplyRole, QskDialog::HelpRole
};
for ( auto role : candidates )
{
const auto& buttons = buttonBox->buttons( role );
if ( !buttons.isEmpty() )
return buttonBox->action( buttons.first() );
}
return QskDialog::NoAction;
}
static QskDialog::DialogCode qskExec( QskDialogWindow* dialogWindow )
{
#if 1
auto focusIndicator = new QskFocusIndicator();
focusIndicator->setObjectName( QStringLiteral( "DialogFocusIndicator" ) );
dialogWindow->addItem( focusIndicator );
#endif
return dialogWindow->exec();
}
2025-01-14 15:40:20 +00:00
namespace
{
template< typename W >
class WindowOrSubWindow : public W
2025-01-14 15:40:20 +00:00
{
public:
WindowOrSubWindow( QObject* parent, const QString& title,
QskDialog::Actions actions, QskDialog::Action defaultAction )
2025-01-14 15:40:20 +00:00
: W( parent, title, actions, defaultAction )
{
}
};
template<>
class WindowOrSubWindow< QskDialogWindow > : public QskDialogWindow
2025-01-14 15:40:20 +00:00
{
public:
WindowOrSubWindow( QObject* parent, const QString& title,
QskDialog::Actions actions, QskDialog::Action defaultAction )
2025-01-14 15:40:20 +00:00
: QskDialogWindow( static_cast< QWindow* >( parent ) )
{
auto* transientParent = static_cast< QWindow* >( parent );
setTransientParent( transientParent );
setTitle( title );
setDialogActions( actions );
if ( actions != QskDialog::NoAction && defaultAction == QskDialog::NoAction )
defaultAction = qskActionCandidate( buttonBox() );
setDefaultDialogAction( defaultAction );
setModality( transientParent ? Qt::WindowModal : Qt::ApplicationModal );
const QSize size = sizeConstraint();
if ( this->parent() )
{
QRect r( QPoint(), size );
r.moveCenter( QRect( QPoint(), this->parent()->size() ).center() );
setGeometry( r );
}
if ( size.isValid() )
{
setFlags( flags() | Qt::MSWindowsFixedSizeDialogHint );
setFixedSize( size );
}
setModality( Qt::ApplicationModal );
}
QskDialog::DialogCode exec()
{
return qskExec( this );
}
void setContentItem( QQuickItem* item )
{
QskDialogWindow::setDialogContentItem( item );
}
QQuickItem* rootItem()
{
return contentItem();
}
};
template<>
class WindowOrSubWindow< QskDialogSubWindow > : public QskDialogSubWindow
2025-01-14 15:40:20 +00:00
{
public:
WindowOrSubWindow( QObject* parent, const QString& title,
QskDialog::Actions actions, QskDialog::Action defaultAction )
2025-01-14 15:40:20 +00:00
: QskDialogSubWindow( static_cast< QQuickWindow* >( parent )->contentItem() )
{
setPopupFlag( QskPopup::DeleteOnClose );
setModal( true );
setTitle( title );
setDialogActions( actions );
if ( actions != QskDialog::NoAction && defaultAction == QskDialog::NoAction )
defaultAction = qskActionCandidate( buttonBox() );
setDefaultDialogAction( defaultAction );
}
QskDialog::DialogCode exec()
{
return QskDialogSubWindow::exec();
}
void setContentItem( QQuickItem* item )
{
QskDialogSubWindow::setContentItem( item );
}
QQuickItem* rootItem()
{
return this;
}
};
template< typename W >
class FileSelectionWindow : public WindowOrSubWindow< W >
2025-01-14 15:40:20 +00:00
{
using Inherited = WindowOrSubWindow< W >;
2025-01-14 15:40:20 +00:00
public:
FileSelectionWindow( QObject* parent, const QString& title,
QskDialog::Actions actions, QskDialog::Action defaultAction,const QString& directory )
: WindowOrSubWindow< W >( parent, title, actions, defaultAction )
, m_model( new QFileSystemModel( parent ) )
2025-01-14 15:40:20 +00:00
{
m_model->setRootPath( directory );
2025-01-14 15:40:20 +00:00
auto* outerBox = new QskLinearBox( Qt::Vertical );
2025-01-22 14:20:48 +00:00
outerBox->setMargins( 20 );
outerBox->setSpacing( 20 );
2025-01-14 15:40:20 +00:00
#if 1
2025-01-22 14:20:48 +00:00
outerBox->setFixedSize( 500, 500 );
2025-01-14 15:40:20 +00:00
#endif
setupHeader( outerBox );
setupListBox( outerBox );
Inherited::setContentItem( outerBox );
}
QString selectedFile() const
{
const auto index = m_model->index( m_model->rootPath() );
return m_model->filePath( index );
}
2025-01-14 15:40:20 +00:00
private:
void setupHeader( QQuickItem* parentItem )
{
2025-01-23 08:56:36 +00:00
m_scrollArea = new QskScrollArea( parentItem );
m_scrollArea->setSizePolicy( Qt::Vertical, QskSizePolicy::Fixed );
m_scrollArea->setFlickableOrientations( Qt::Horizontal );
m_headerBox = new QskLinearBox( Qt::Horizontal, m_scrollArea );
m_scrollArea->setScrolledItem( m_headerBox );
2025-01-22 14:20:48 +00:00
}
static QStringList splitPath( const QString& path )
{
const auto cleanPath = QDir::cleanPath( path );
QDir dir( cleanPath );
QStringList result;
do
{
if( dir != QDir::root() )
{
result.prepend( dir.absolutePath() );
}
}
while( dir.cdUp() );
return result;
2025-01-14 15:40:20 +00:00
}
void updateHeader( const QString& path )
{
2025-01-22 14:20:48 +00:00
const auto dirPaths = splitPath( path );
for( int i = 0; i < dirPaths.count(); ++i )
{
QskPushButton* b;
if( m_breadcrumbsButtons.count() <= i )
{
b = new QskPushButton( m_headerBox );
b->setStrutSizeHint( QskPushButton::Panel, { -1, -1 } );
m_breadcrumbsButtons.append( b );
}
else
{
b = m_breadcrumbsButtons.at( i );
b->disconnect();
}
QFileInfo fi( dirPaths.at( i ) );
b->setText( fi.baseName() );
QObject::connect( b, &QskPushButton::clicked, this, [this, fi]()
{
m_model->setRootPath( fi.filePath() );
2025-01-22 14:20:48 +00:00
});
}
for( int i = dirPaths.count(); i < m_breadcrumbsButtons.count(); i++ )
{
m_breadcrumbsButtons.at( i )->deleteLater();
m_breadcrumbsButtons.removeAt( i );
}
2025-01-23 08:56:36 +00:00
m_scrollArea->ensureItemVisible( m_breadcrumbsButtons.last() );
2025-01-14 15:40:20 +00:00
}
void setupListBox( QQuickItem* parentItem )
{
m_listBox = new QskSimpleListBox( parentItem );
2025-01-22 14:20:48 +00:00
QObject::connect( m_model, &QFileSystemModel::directoryLoaded,
2025-01-23 08:56:36 +00:00
this, &FileSelectionWindow< W >::loadContents );
2025-01-22 14:20:48 +00:00
QObject::connect( m_listBox, &QskSimpleListBox::selectedEntryChanged,
this, [this]( const QString& path )
2025-01-23 08:56:36 +00:00
{
QFileInfo fi( m_model->rootPath(), path );
2025-01-23 10:19:54 +00:00
if( fi.isDir() )
{
m_model->setRootPath( fi.absoluteFilePath() );
}
2025-01-23 08:56:36 +00:00
});
2025-01-14 15:40:20 +00:00
}
2025-01-23 08:56:36 +00:00
void loadContents()
2025-01-14 15:40:20 +00:00
{
m_listBox->removeBulk( 0 );
2025-01-22 14:20:48 +00:00
const auto index = m_model->index( m_model->rootPath() );
2025-01-23 08:56:36 +00:00
for ( int row = 0; row < m_model->rowCount( index ); row++ )
2025-01-23 08:56:36 +00:00
{
auto idx = m_model->index( row, 0, index );
m_listBox->append( m_model->fileName( idx ) );
2025-01-23 08:56:36 +00:00
}
updateHeader( QDir( selectedFile() ).path() );
2025-01-14 15:40:20 +00:00
}
QFileSystemModel* const m_model;
2025-01-14 15:40:20 +00:00
QskSimpleListBox* m_listBox;
2025-01-22 14:20:48 +00:00
QskLinearBox* m_headerBox;
2025-01-23 08:56:36 +00:00
QskScrollArea* m_scrollArea;
2025-01-22 14:20:48 +00:00
QVector< QskPushButton* > m_breadcrumbsButtons;
2025-01-14 15:40:20 +00:00
};
}
2017-07-21 16:21:34 +00:00
static QQuickWindow* qskSomeQuickWindow()
{
// not the best code ever, but as it is a fallback only
// maybe we should also add the stacking order
2022-03-24 07:11:29 +00:00
const auto windows = QGuiApplication::topLevelWindows();
2017-07-21 16:21:34 +00:00
for ( auto window : windows )
{
if ( window->isVisible() )
{
if ( auto quickWindow = qobject_cast< QQuickWindow* >( window ) )
return quickWindow;
}
}
return nullptr;
}
2018-08-03 06:15:28 +00:00
static void qskSetupSubWindow(
const QString& title, QskDialog::Actions actions,
2018-11-06 17:54:21 +00:00
QskDialog::Action defaultAction, QskDialogSubWindow* subWindow )
2017-07-21 16:21:34 +00:00
{
subWindow->setPopupFlag( QskPopup::DeleteOnClose );
2017-07-21 16:21:34 +00:00
subWindow->setModal( true );
#if 0
subWindow->setWindowTitle( ... );
#endif
subWindow->setTitle( title );
2018-11-05 12:50:41 +00:00
subWindow->setDialogActions( actions );
2017-07-21 16:21:34 +00:00
2018-11-06 17:54:21 +00:00
if ( actions != QskDialog::NoAction && defaultAction == QskDialog::NoAction )
defaultAction = qskActionCandidate( subWindow->buttonBox() );
2017-07-21 16:21:34 +00:00
2018-11-05 12:50:41 +00:00
subWindow->setDefaultDialogAction( defaultAction );
2017-07-21 16:21:34 +00:00
}
static void qskSetupWindow(
QWindow* transientParent, const QString& title,
QskDialog::Actions actions, QskDialog::Action defaultAction,
2018-11-06 17:54:21 +00:00
QskDialogWindow* window )
2017-07-21 16:21:34 +00:00
{
window->setTransientParent( transientParent );
window->setTitle( title );
2018-11-05 12:50:41 +00:00
window->setDialogActions( actions );
2017-07-21 16:21:34 +00:00
2018-11-06 17:54:21 +00:00
if ( actions != QskDialog::NoAction && defaultAction == QskDialog::NoAction )
defaultAction = qskActionCandidate( window->buttonBox() );
2017-07-21 16:21:34 +00:00
2018-11-05 12:50:41 +00:00
window->setDefaultDialogAction( defaultAction );
2017-07-21 16:21:34 +00:00
window->setModality( transientParent ? Qt::WindowModal : Qt::ApplicationModal );
const QSize size = window->sizeConstraint();
2025-01-14 15:40:20 +00:00
qDebug() << "sc:" << size;
2017-07-21 16:21:34 +00:00
if ( window->parent() )
{
QRect r( QPoint(), size );
r.moveCenter( QRect( QPoint(), window->parent()->size() ).center() );
window->setGeometry( r );
}
if ( size.isValid() )
{
window->setFlags( window->flags() | Qt::MSWindowsFixedSizeDialogHint );
window->setFixedSize( size );
}
window->setModality( Qt::ApplicationModal );
}
static QskDialog::Action qskMessageSubWindow(
2018-08-03 06:15:28 +00:00
QQuickWindow* window, const QString& title,
const QString& text, uint priority, QskDialog::Actions actions,
QskDialog::Action defaultAction )
2017-07-21 16:21:34 +00:00
{
auto subWindow = new QskMessageSubWindow( window->contentItem() );
subWindow->setPriority( priority );
subWindow->setText( text );
2017-07-21 16:21:34 +00:00
qskSetupSubWindow( title, actions, defaultAction, subWindow );
( void ) subWindow->exec();
2017-07-21 16:21:34 +00:00
auto clickedAction = subWindow->clickedAction();
if ( clickedAction == QskDialog::NoAction )
2017-07-21 16:21:34 +00:00
{
// dialog might have been closed by the window menu
clickedAction = QskDialog::Cancel;
2017-07-21 16:21:34 +00:00
}
return clickedAction;
2017-07-21 16:21:34 +00:00
}
static QskDialog::Action qskMessageWindow(
2018-08-03 06:15:28 +00:00
QWindow* transientParent, const QString& title,
const QString& text, uint priority, QskDialog::Actions actions,
QskDialog::Action defaultAction )
2017-07-21 16:21:34 +00:00
{
Q_UNUSED( priority ); // can we do something with it ?
2017-07-21 16:21:34 +00:00
QskMessageWindow messageWindow;
2018-11-06 18:00:42 +00:00
messageWindow.setText( text );
2017-07-21 16:21:34 +00:00
qskSetupWindow( transientParent, title, actions, defaultAction, &messageWindow );
2017-07-21 16:21:34 +00:00
( void ) qskExec( &messageWindow );
auto clickedAction = messageWindow.clickedAction();
if ( clickedAction == QskDialog::NoAction )
2017-07-21 16:21:34 +00:00
{
// dialog might have been closed by the window menu
clickedAction = QskDialog::Cancel;
2017-07-21 16:21:34 +00:00
}
return clickedAction;
2017-07-21 16:21:34 +00:00
}
2018-08-03 06:15:28 +00:00
static QString qskSelectSubWindow(
QQuickWindow* window, const QString& title,
QskDialog::Actions actions, QskDialog::Action defaultAction,
2017-07-21 16:21:34 +00:00
const QStringList& entries, int selectedRow )
{
auto subWindow = new QskSelectionSubWindow( window->contentItem() );
subWindow->setEntries( entries );
subWindow->setSelectedRow( selectedRow );
2017-07-21 16:21:34 +00:00
QString selectedEntry;
2017-07-21 16:21:34 +00:00
qskSetupSubWindow( title, actions, defaultAction, subWindow );
if ( subWindow->exec() == QskDialog::Accepted )
selectedEntry = subWindow->selectedEntry();
2017-07-21 16:21:34 +00:00
return selectedEntry;
}
2018-08-03 06:15:28 +00:00
static QString qskSelectWindow(
QWindow* transientParent, const QString& title,
QskDialog::Actions actions, QskDialog::Action defaultAction,
2017-07-21 16:21:34 +00:00
const QStringList& entries, int selectedRow )
{
QskSelectionWindow window;
window.setEntries( entries );
window.setSelectedRow( selectedRow );
QString selectedEntry = window.selectedEntry();
qskSetupWindow( transientParent, title, actions, defaultAction, &window );
2017-07-21 16:21:34 +00:00
if ( qskExec( &window ) == QskDialog::Accepted )
selectedEntry = window.selectedEntry();
return selectedEntry;
}
2025-01-14 15:40:20 +00:00
template< typename W >
static QString qskSelectFile( FileSelectionWindow< W >& window )
{
QString selectedFile = window.selectedFile();
if( window.exec() == QskDialog::Accepted )
selectedFile = window.selectedFile();
return selectedFile;
}
2017-07-21 16:21:34 +00:00
class QskDialog::PrivateData
{
2018-08-03 06:15:28 +00:00
public:
2017-07-21 16:21:34 +00:00
QPointer< QWindow > transientParent;
QskDialog::Policy policy = QskDialog::TopLevelWindow;
2017-07-21 16:21:34 +00:00
};
2018-08-03 06:15:28 +00:00
QskDialog::QskDialog()
: m_data( new PrivateData )
2017-07-21 16:21:34 +00:00
{
}
QskDialog::~QskDialog()
{
}
QskDialog* QskDialog::instance()
{
static QskDialog instance;
return &instance;
2017-07-21 16:21:34 +00:00
}
void QskDialog::setPolicy( Policy policy )
{
if ( policy != m_data->policy )
{
m_data->policy = policy;
Q_EMIT policyChanged();
}
}
QskDialog::Policy QskDialog::policy() const
{
return m_data->policy;
}
void QskDialog::setTransientParent( QWindow* window )
{
if ( m_data->transientParent != window )
{
m_data->transientParent = window;
Q_EMIT transientParentChanged();
}
}
QWindow* QskDialog::transientParent() const
{
return m_data->transientParent;
}
QskDialog::Action QskDialog::message(
const QString& title, const QString& text, uint priority,
Actions actions, Action defaultAction ) const
2017-07-21 16:21:34 +00:00
{
if ( m_data->policy == EmbeddedBox )
{
2018-08-03 06:15:28 +00:00
auto quickWindow = qobject_cast< QQuickWindow* >( m_data->transientParent );
2017-07-21 16:21:34 +00:00
if ( quickWindow == nullptr )
quickWindow = qskSomeQuickWindow();
if ( quickWindow )
{
return qskMessageSubWindow( quickWindow,
title, text, priority, actions, defaultAction );
2017-07-21 16:21:34 +00:00
}
}
return qskMessageWindow( m_data->transientParent,
title, text, priority, actions, defaultAction );
2017-07-21 16:21:34 +00:00
}
QskDialog::Action QskDialog::information(
2017-07-21 16:21:34 +00:00
const QString& title, const QString& text,
Actions actions, Action defaultAction ) const
2017-07-21 16:21:34 +00:00
{
return QskDialog::message( title, text, 0, actions, defaultAction );
2017-07-21 16:21:34 +00:00
}
QskDialog::Action QskDialog::question(
2017-07-21 16:21:34 +00:00
const QString& title, const QString& text,
Actions actions, Action defaultAction ) const
2017-07-21 16:21:34 +00:00
{
return QskDialog::message( title, text, 0, actions, defaultAction );
2017-07-21 16:21:34 +00:00
}
QString QskDialog::select( const QString& title,
2017-07-21 16:21:34 +00:00
const QStringList& entries, int selectedRow ) const
{
#if 1
// should be parameters
const auto actions = QskDialog::Ok | QskDialog::Cancel;
const auto defaultAction = QskDialog::Ok;
2017-07-21 16:21:34 +00:00
#endif
if ( m_data->policy == EmbeddedBox )
{
2018-08-03 06:15:28 +00:00
auto quickWindow = qobject_cast< QQuickWindow* >( m_data->transientParent );
2017-07-21 16:21:34 +00:00
if ( quickWindow == nullptr )
quickWindow = qskSomeQuickWindow();
if ( quickWindow )
{
return qskSelectSubWindow( quickWindow,
title, actions, defaultAction, entries, selectedRow );
2017-07-21 16:21:34 +00:00
}
}
return qskSelectWindow( m_data->transientParent, title,
actions, defaultAction, entries, selectedRow );
2017-07-21 16:21:34 +00:00
}
2025-01-14 15:40:20 +00:00
QString QskDialog::selectFile(
const QString& title, const QString& directory ) const
{
#if 1
// should be parameters
const auto actions = QskDialog::Ok | QskDialog::Cancel;
const auto defaultAction = QskDialog::Ok;
#endif
if ( m_data->policy == EmbeddedBox )
{
auto quickWindow = qobject_cast< QQuickWindow* >( m_data->transientParent );
if ( quickWindow == nullptr )
quickWindow = qskSomeQuickWindow();
if ( quickWindow )
{
FileSelectionWindow< QskDialogSubWindow > window( quickWindow, title, actions, defaultAction, directory );
return qskSelectFile< QskDialogSubWindow >( window );
}
}
FileSelectionWindow< QskDialogWindow > window( m_data->transientParent, title, actions, defaultAction, directory );
return qskSelectFile< QskDialogWindow >( window );
}
QskDialog::ActionRole QskDialog::actionRole( Action action )
{
using Q = QPlatformDialogHelper;
const auto role = Q::buttonRole( static_cast< Q::StandardButton >( action ) );
return static_cast< ActionRole >( role );
}
2017-07-21 16:21:34 +00:00
#include "moc_QskDialog.cpp"