diff --git a/examples/gallery/dialog/DialogPage.cpp b/examples/gallery/dialog/DialogPage.cpp index 62e8f8a1..08cda235 100644 --- a/examples/gallery/dialog/DialogPage.cpp +++ b/examples/gallery/dialog/DialogPage.cpp @@ -9,6 +9,8 @@ #include #include +#include + #if QT_CONFIG(thread) /* WebAssembly without asyncify support does not allow recursive @@ -96,6 +98,18 @@ namespace auto selectButton = new Button( "Selection", this ); connect( selectButton, &Button::clicked, this, &ButtonBox::execSelection ); + auto fileSelectionButton = new Button( "File selection", this ); + connect( fileSelectionButton, &Button::clicked, this, &ButtonBox::execFileSelection ); + + auto directorySelectionButton = new Button( "Directory selection", this ); + connect( directorySelectionButton, &Button::clicked, this, &ButtonBox::execDirectorySelection ); + + auto colorSelectionButton = new Button( "Color selection", this ); + connect( colorSelectionButton, &Button::clicked, this, &ButtonBox::execColorSelection ); + + auto fontSelectionButton = new Button( "Font selection", this ); + connect( fontSelectionButton, &Button::clicked, this, &ButtonBox::execFontSelection ); + setExtraSpacingAt( Qt::BottomEdge ); } @@ -155,6 +169,42 @@ namespace QskDialog::Ok | QskDialog::Cancel, QskDialog::Ok, entries, 7 ); #else (void )qskDialog->select( title, entries, 7 ); +#endif + } + + void execFileSelection() + { +#ifndef QSK_USE_EXEC + // not implemented for now (class is not public) +#else + ( void ) qskDialog->selectFile( "select file", QDir::currentPath() ); +#endif + } + + void execDirectorySelection() + { +#ifndef QSK_USE_EXEC + // not implemented for now (class is not public) +#else + ( void ) qskDialog->selectDirectory( "select directory", QDir::currentPath() ); +#endif + } + + void execColorSelection() + { +#ifndef QSK_USE_EXEC + // not implemented for now (class is not public) +#else + ( void ) qskDialog->selectColor( "select color" ); +#endif + } + + void execFontSelection() + { +#ifndef QSK_USE_EXEC + // not implemented for now (class is not public) +#else + ( void ) qskDialog->selectFont( "select font" ); #endif } }; diff --git a/playground/systemdialogs/main.cpp b/playground/systemdialogs/main.cpp index 0a144e92..91533586 100644 --- a/playground/systemdialogs/main.cpp +++ b/playground/systemdialogs/main.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include @@ -20,34 +21,6 @@ #include #include -#include - -static QQuickAbstractDialog* createQml( const char* className ) -{ - static QQmlEngine engine( nullptr ); - - QByteArray qmlCode = "import QtQuick.Dialogs\n"; - qmlCode += className; - qmlCode += " {}"; - - auto component = new QQmlComponent( &engine ); - component->setData( qmlCode.constData(), QUrl() ); - - if ( component->status() != QQmlComponent::Ready ) - { - qWarning() << component->errorString(); - delete component; - - return nullptr; - } - - auto dialog = qobject_cast< QQuickAbstractDialog* >( component->create() ); - QObject::connect( dialog, &QObject::destroyed, - component, &QObject::deleteLater ); - - return dialog; -} - namespace { class ButtonBox : public QskLinearBox @@ -108,10 +81,38 @@ namespace if ( qGuiApp->testAttribute( Qt::AA_DontUseNativeDialogs ) ) { - const auto metaEnum = QMetaEnum::fromType(); - m_dialog = createQml( metaEnum.key( dialogType ) ); - if ( m_dialog ) - m_dialog->setParentWindow( window() ); + switch( dialogType ) + { + case ColorDialog: + { + qskDialog->selectColor( "select color" ); + break; + } + case FileDialog: + { + qskDialog->selectFile( "select file", QDir::currentPath() ); + break; + } + case FolderDialog: + { + qskDialog->selectDirectory( "select directory", QDir::currentPath() ); + break; + } + case FontDialog: + { + qskDialog->selectFont( "select font" ); + break; + } + case MessageDialog: + { + qskDialog->message( "message", "The quick brown fox jumps over the lazy dog" ); + break; + } + default: + { + qWarning() << "unknown dialog type detected"; + } + } } else { diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3b587adc..e2c45b5e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -458,16 +458,31 @@ list(APPEND HEADERS dialogs/QskSelectionWindow.h ) +list(APPEND PRIVATE_HEADERS + dialogs/QskColorPicker.h + dialogs/QskColorPickerSkinlet.h + dialogs/QskColorSelectionWindow.h + dialogs/QskFileSelectionWindow.h + dialogs/QskFontSelectionWindow.h + dialogs/QskWindowOrSubWindow.h +) + list(APPEND SOURCES + dialogs/QskColorPicker.cpp + dialogs/QskColorPickerSkinlet.cpp + dialogs/QskColorSelectionWindow.cpp dialogs/QskDialogButton.cpp dialogs/QskDialogButtonBox.cpp dialogs/QskDialog.cpp dialogs/QskDialogSubWindow.cpp dialogs/QskDialogWindow.cpp + dialogs/QskFileSelectionWindow.cpp + dialogs/QskFontSelectionWindow.cpp dialogs/QskMessageSubWindow.cpp dialogs/QskMessageWindow.cpp dialogs/QskSelectionSubWindow.cpp dialogs/QskSelectionWindow.cpp + dialogs/QskWindowOrSubWindow.cpp ) list(APPEND HEADERS diff --git a/src/controls/QskSkin.cpp b/src/controls/QskSkin.cpp index 1b4a73e7..0ada463b 100644 --- a/src/controls/QskSkin.cpp +++ b/src/controls/QskSkin.cpp @@ -109,6 +109,9 @@ #include "QskStatusIndicator.h" #include "QskStatusIndicatorSkinlet.h" +#include "QskColorPicker.h" +#include "QskColorPickerSkinlet.h" + #include "QskInternalMacros.h" #include @@ -219,6 +222,7 @@ QskSkin::QskSkin( QObject* parent ) declareSkinlet< QskProgressBar, QskProgressBarSkinlet >(); declareSkinlet< QskProgressRing, QskProgressRingSkinlet >(); declareSkinlet< QskRadioBox, QskRadioBoxSkinlet >(); + declareSkinlet< QskColorPicker, QskColorPickerSkinlet >(); const QFont font = QGuiApplication::font(); setupFontTable( font.family(), font.italic() ); diff --git a/src/dialogs/QskColorPicker.cpp b/src/dialogs/QskColorPicker.cpp new file mode 100644 index 00000000..e505bf30 --- /dev/null +++ b/src/dialogs/QskColorPicker.cpp @@ -0,0 +1,191 @@ +/****************************************************************************** + * QSkinny - Copyright (C) The authors + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#include "QskColorPicker.h" +#include "QskColorPickerSkinlet.h" +#include "QskEvent.h" +#include "QskFunctions.h" + +#include + +QSK_SUBCONTROL( QskColorPicker, Panel ) +QSK_SUBCONTROL( QskColorPicker, ColorPane ) +QSK_SUBCONTROL( QskColorPicker, Selector ) + +using A = QskAspect; + +namespace +{ + const auto HPos = QskColorPicker::Selector | A::Horizontal; + const auto VPos = QskColorPicker::Selector | A::Vertical; + + void createImage( QImage* image, const QSizeF& size, qreal v ) + { + if( image->size() != size ) + { + *image = QImage( size.width(), size.height(), QImage::Format_RGB32 ); + } + + QColor color; + float h, s; + + for ( int y = 0; y < image->height(); y++ ) + { + s = 1.0 - static_cast< float >( y ) / image->height(); + auto line = reinterpret_cast< QRgb* >( image->scanLine( y ) ); + + for ( int x = 0; x < image->width(); x++ ) + { + h = static_cast< float >( x ) / image->width(); + color.setHsvF( h, s, v ); + *line++ = color.rgb(); + } + } + } +} + +class QskColorPicker::PrivateData +{ + public: + qreal value = 255; + bool isPressed = false; + QImage image; +}; + +QskColorPicker::QskColorPicker( QQuickItem* parent ) + : Inherited( parent ) + , m_data( new PrivateData ) +{ + setAcceptedMouseButtons( Qt::LeftButton ); + setBoundaries( 0, 255 ); + setPolishOnResize( true ); + createImage(); + setPositionHint( HPos, -1 ); + setPositionHint( VPos, -1 ); +} + +QskColorPicker::~QskColorPicker() = default; + +QColor QskColorPicker::selectedColor() const +{ + if( image().isNull() ) + { + return {}; + } + + return m_data->image.pixelColor( position().toPoint() ); +} + +qreal QskColorPicker::value() const +{ + return m_data->value; +} + +qreal QskColorPicker::valueAsRatio() const +{ + return valueAsRatio( m_data->value ); +} + +QImage QskColorPicker::image() const +{ + return m_data->image; +} + +void QskColorPicker::setValue( qreal v ) +{ + if( !qskFuzzyCompare( m_data->value, v ) ) + { + m_data->value = v; + createImage(); + update(); + Q_EMIT valueChanged( m_data->value ); + Q_EMIT selectedColorChanged(); + } +} + +void QskColorPicker::setValueAsRatio( qreal ratio ) +{ + ratio = qBound( 0.0, ratio, 1.0 ); + setValue( minimum() + ratio * boundaryLength() ); +} + +void QskColorPicker::updateLayout() +{ + createImage(); + updatePosition( position() ); +} + +void QskColorPicker::mousePressEvent( QMouseEvent* event ) +{ + m_data->isPressed = true; + updatePosition( qskMousePosition( event ) ); +} + +void QskColorPicker::mouseMoveEvent( QMouseEvent* event ) +{ + if( !m_data->isPressed ) + { + return; + } + + updatePosition( qskMousePosition( event ) ); +} + +void QskColorPicker::mouseReleaseEvent( QMouseEvent* ) +{ + m_data->isPressed = false; +} + +void QskColorPicker::updatePosition( const QPointF& point ) +{ + const auto rect = subControlRect( ColorPane ); + + if( rect.isEmpty() ) + { + return; + } + + auto p = point; + p.rx() = qBound( rect.x(), p.x(), rect.right() - 1.0 ); + p.ry() = qBound( rect.y(), p.y(), rect.bottom() - 1.0 ); + + const auto oldX = positionHint( HPos ); + const auto oldY = positionHint( VPos ); + + if( !qskFuzzyCompare( p.x(), oldX ) || !qskFuzzyCompare( p.y(), oldY ) ) + { + setPositionHint( HPos, p.x() ); + setPositionHint( VPos, p.y() ); + + update(); + Q_EMIT positionChanged(); + Q_EMIT selectedColorChanged(); + } +} + +QPointF QskColorPicker::position() const +{ + const auto r = subControlRect( ColorPane ); + + if( !r.size().isValid() ) + { + return {}; + } + + const auto x = positionHint( HPos ); + const auto y = positionHint( VPos ); + return { x, y }; +} + +void QskColorPicker::createImage() +{ + const auto r = subControlRect( ColorPane ); + const auto ratio = window() ? window()->effectiveDevicePixelRatio() : 1.0; + const auto size = r.size() * ratio; + + ::createImage( &m_data->image, size, valueAsRatio() ); +} + +#include "moc_QskColorPicker.cpp" diff --git a/src/dialogs/QskColorPicker.h b/src/dialogs/QskColorPicker.h new file mode 100644 index 00000000..f5077ab4 --- /dev/null +++ b/src/dialogs/QskColorPicker.h @@ -0,0 +1,57 @@ +/****************************************************************************** + * QSkinny - Copyright (C) The authors + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#ifndef QSK_COLOR_PICKER_H +#define QSK_COLOR_PICKER_H + +#include "QskBoundedControl.h" + +#include + +class QskColorPicker : public QskBoundedControl +{ + Q_OBJECT + + using Inherited = QskBoundedControl; + + public: + QSK_SUBCONTROLS( Panel, ColorPane, Selector ) + + QskColorPicker( QQuickItem* parentItem = nullptr ); + ~QskColorPicker() override; + + QColor selectedColor() const; + + qreal value() const; // value as in hue / saturation / value + qreal valueAsRatio() const; // [0.0, 1.0] + using QskBoundedControl::valueAsRatio; + + QImage image() const; + QPointF position() const; + + public Q_SLOTS: + void setValue( qreal ); + void setValueAsRatio( qreal ); + + Q_SIGNALS: + void valueChanged( qreal ); + void selectedColorChanged(); + void positionChanged(); + + protected: + void updateLayout() override; + void mousePressEvent( QMouseEvent* ) override; + void mouseMoveEvent( QMouseEvent* ) override; + void mouseReleaseEvent( QMouseEvent* ) override; + + private: + void updatePosition( const QPointF& ); + void createImage(); + + class PrivateData; + std::unique_ptr< PrivateData > m_data; +}; + +#endif diff --git a/src/dialogs/QskColorPickerSkinlet.cpp b/src/dialogs/QskColorPickerSkinlet.cpp new file mode 100644 index 00000000..fe10e556 --- /dev/null +++ b/src/dialogs/QskColorPickerSkinlet.cpp @@ -0,0 +1,115 @@ +/****************************************************************************** + * QSkinny - Copyright (C) The authors + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#include "QskColorPickerSkinlet.h" +#include "QskColorPicker.h" + +#include "QskSGNode.h" +#include "QskPaintedNode.h" + +#include + +using Q = QskColorPicker; + +namespace +{ + class ColorPaneNode : public QskPaintedNode + { + public: + void paint( QPainter* p, const QSize& size, const void* nodeData ) override + { + const Q* q = static_cast< const Q* >( nodeData ); + const auto image = q->image().scaled( size ); + p->drawImage( QPointF( 0, 0 ), image ); + } + + void updateNode( QQuickWindow* window, const QRectF& rect, const Q* q ) + { + update( window, rect, QSizeF(), q ); + } + + protected: + QskHashValue hash( const void* nodeData ) const override + { + const Q* q = static_cast< const Q* >( nodeData ); + const auto r = q->subControlRect( Q::ColorPane ); + + QskHashValue h = qHash( r.width() ); + h = qHash( r.height() ); + h = qHash( q->value() ); + + return h; + } + }; +} + +QskColorPickerSkinlet::QskColorPickerSkinlet( QskSkin* skin ) + : Inherited( skin ) +{ + setNodeRoles( { PanelRole, ColorPaneRole, SelectorRole } ); +} + +QskColorPickerSkinlet::~QskColorPickerSkinlet() = default; + +QSGNode* QskColorPickerSkinlet::updateSubNode( + const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* node ) const +{ + const auto q = static_cast< const Q* >( skinnable ); + + switch ( nodeRole ) + { + case PanelRole: + { + return updateBoxNode( skinnable, node, Q::Panel ); + } + case ColorPaneRole: + { + return updateColorPaneNode( q, node ); + } + case SelectorRole: + { + return updateBoxNode( skinnable, node, Q::Selector ); + } + } + + return Inherited::updateSubNode( skinnable, nodeRole, node ); +} + +QRectF QskColorPickerSkinlet::subControlRect( + const QskSkinnable* skinnable, const QRectF& contentsRect, + QskAspect::Subcontrol subControl ) const +{ + const auto q = static_cast< const Q* >( skinnable ); + + if( subControl == Q::Panel || subControl == Q::ColorPane ) + { + return contentsRect; + } + + if( subControl == Q::Selector ) + { + const auto s = q->strutSizeHint( Q::Selector ); + const auto p = q->position(); + + const QRectF r( { p.x() - s.width() / 2.0, p.y() - s.height() / 2.0 }, s ); + return r; + } + + return Inherited::subControlRect( skinnable, contentsRect, subControl ); +} + +QSGNode* QskColorPickerSkinlet::updateColorPaneNode( + const QskColorPicker* q, QSGNode* node ) const +{ + auto* colorPaneNode = QskSGNode::ensureNode< ColorPaneNode >( node ); + + const auto rect = q->subControlRect( Q::ColorPane ); + + colorPaneNode->updateNode( q->window(), rect, q ); + + return colorPaneNode; +} + +#include "moc_QskColorPickerSkinlet.cpp" diff --git a/src/dialogs/QskColorPickerSkinlet.h b/src/dialogs/QskColorPickerSkinlet.h new file mode 100644 index 00000000..8986d3f3 --- /dev/null +++ b/src/dialogs/QskColorPickerSkinlet.h @@ -0,0 +1,45 @@ +/****************************************************************************** + * QSkinny - Copyright (C) The authors + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#ifndef QSK_COLOR_PICKER_SKINLET_H +#define QSK_COLOR_PICKER_SKINLET_H + +#include "QskSkinlet.h" + +class QskColorPicker; + +class QskColorPickerSkinlet : public QskSkinlet +{ + Q_GADGET + + using Inherited = QskSkinlet; + + public: + enum NodeRole : quint8 + { + PanelRole, + ColorPaneRole, + SelectorRole, + + RoleCount + }; + + Q_INVOKABLE QskColorPickerSkinlet( QskSkin* = nullptr ); + ~QskColorPickerSkinlet() override; + + QRectF subControlRect( const QskSkinnable*, + const QRectF&, QskAspect::Subcontrol ) const override; + + protected: + QSGNode* updateSubNode( const QskSkinnable*, + quint8 nodeRole, QSGNode* ) const override; + + private: + QSGNode* updateColorPaneNode( const QskColorPicker*, QSGNode* ) const; + + QRectF cursorRect( const QskSkinnable*, const QRectF&, int index ) const; +}; + +#endif diff --git a/src/dialogs/QskColorSelectionWindow.cpp b/src/dialogs/QskColorSelectionWindow.cpp new file mode 100644 index 00000000..dd9fa5ea --- /dev/null +++ b/src/dialogs/QskColorSelectionWindow.cpp @@ -0,0 +1,128 @@ +/****************************************************************************** + * QSkinny - Copyright (C) The authors + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#include "QskColorSelectionWindow.h" + +#include "QskBoxBorderColors.h" +#include "QskBoxBorderMetrics.h" +#include "QskBoxShapeMetrics.h" +#include "QskColorPicker.h" +#include "QskComboBox.h" +#include "QskGridBox.h" +#include "QskLinearBox.h" +#include "QskSlider.h" +#include "QskTextField.h" +#include "QskTextLabel.h" + +template< typename W > +class QskColorSelectionWindow< W >::PrivateData +{ + public: + QskColorPicker* colorPicker; +}; + +template< typename W > +QskColorSelectionWindow< W >::QskColorSelectionWindow( QObject* parent, const QString& title, + QskDialog::Actions actions, QskDialog::Action defaultAction ) + : Inherited( parent, title, actions, defaultAction ) + , m_data( new PrivateData ) +{ + auto* outerBox = new QskLinearBox( Qt::Vertical ); + outerBox->setMargins( 20 ); + outerBox->setSpacing( 20 ); +#if 1 + outerBox->setFixedSize( 350, 500 ); +#endif + auto* upperBox = new QskLinearBox( Qt::Horizontal, outerBox ); + upperBox->setSizePolicy( Qt::Vertical, QskSizePolicy::Expanding ); + upperBox->setSpacing( 12 ); + + m_data->colorPicker = new QskColorPicker( upperBox ); + m_data->colorPicker->setStrutSizeHint( QskColorPicker::Selector, { 18, 18 } ); + m_data->colorPicker->setBoxShapeHint( QskColorPicker::Selector, { 100, Qt::RelativeSize } ); + m_data->colorPicker->setBoxBorderMetricsHint( QskColorPicker::Selector, 2 ); + m_data->colorPicker->setBoxBorderColorsHint( QskColorPicker::Selector, Qt::black ); + m_data->colorPicker->setGradientHint( QskColorPicker::Selector, Qt::transparent ); + + auto* outputBox = new QskBox( upperBox ); + outputBox->setPanel( true ); + + QObject::connect( m_data->colorPicker, &QskColorPicker::selectedColorChanged, + this, [this, outputBox]() + { + const auto c = m_data->colorPicker->selectedColor(); + outputBox->setGradientHint( QskBox::Panel, c ); + } ); + + upperBox->setStretchFactor( m_data->colorPicker, 9 ); + upperBox->setStretchFactor( outputBox, 1 ); + + + auto* valueSlider = new QskSlider( outerBox ); + valueSlider->setBoundaries( 0, 1 ); + valueSlider->setValue( m_data->colorPicker->value() ); + + QskGradient g( Qt::black, Qt::white ); + g.setLinearDirection( Qt::Horizontal ); + valueSlider->setGradientHint( QskSlider::Groove, g ); + valueSlider->setGradientHint( QskSlider::Fill, Qt::transparent ); + valueSlider->setGradientHint( QskSlider::Handle, Qt::black ); + + QObject::connect( valueSlider, &QskSlider::valueChanged, + m_data->colorPicker, &QskColorPicker::setValueAsRatio ); + + + auto* gridBox = new QskGridBox( outerBox ); + gridBox->setSizePolicy( Qt::Vertical, QskSizePolicy::Preferred ); + + auto* menu = new QskComboBox( gridBox ); + menu->addOption( QUrl(), "RGB" ); + menu->setCurrentIndex( 0 ); + gridBox->addItem( menu, 0, 0 ); + + auto* rgbValue = new QskTextField( gridBox ); + rgbValue->setReadOnly( true ); + gridBox->addItem( rgbValue, 0, 2 ); + + auto* redValue = new QskTextField( gridBox ); + redValue->setReadOnly( true ); + gridBox->addItem( redValue, 1, 0 ); + gridBox->addItem( new QskTextLabel( "Red", gridBox ), 1, 1 ); + + auto* greenValue = new QskTextField( gridBox ); + greenValue->setReadOnly( true ); + gridBox->addItem( greenValue, 2, 0 ); + gridBox->addItem( new QskTextLabel( "Green", gridBox ), 2, 1 ); + + auto* blueValue = new QskTextField( gridBox ); + blueValue->setReadOnly( true ); + gridBox->addItem( blueValue, 3, 0 ); + gridBox->addItem( new QskTextLabel( "Blue", gridBox ), 3, 1 ); + + QObject::connect( m_data->colorPicker, &QskColorPicker::selectedColorChanged, + this, [this, rgbValue, redValue, greenValue, blueValue]() + { + const auto c = m_data->colorPicker->selectedColor(); + rgbValue->setText( c.name() ); + + redValue->setText( QString::number( c.red() ) ); + greenValue->setText( QString::number( c.green() ) ); + blueValue->setText( QString::number( c.blue() ) ); + } ); + + Inherited::setContentItem( outerBox ); +} + +template< typename W > +QskColorSelectionWindow< W >::~QskColorSelectionWindow() = default; + +template< typename W > +QColor QskColorSelectionWindow< W >::selectedColor() const +{ + return m_data->colorPicker->selectedColor(); +} + +template class QskColorSelectionWindow< QskDialogWindow >; +template class QskColorSelectionWindow< QskDialogSubWindow >; diff --git a/src/dialogs/QskColorSelectionWindow.h b/src/dialogs/QskColorSelectionWindow.h new file mode 100644 index 00000000..e603aa4c --- /dev/null +++ b/src/dialogs/QskColorSelectionWindow.h @@ -0,0 +1,29 @@ +/****************************************************************************** + * QSkinny - Copyright (C) The authors + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#ifndef QSK_COLOR_SELECTION_WINDODW_H +#define QSK_COLOR_SELECTION_WINDODW_H + +#include "QskWindowOrSubWindow.h" + +template< typename W > +class QskColorSelectionWindow : public QskWindowOrSubWindow< W > +{ + using Inherited = QskWindowOrSubWindow< W >; + + public: + + QskColorSelectionWindow( QObject* parent, const QString& title, + QskDialog::Actions actions, QskDialog::Action defaultAction ); + ~QskColorSelectionWindow(); + + QColor selectedColor() const; + + private: + class PrivateData; + std::unique_ptr< PrivateData > m_data; +}; + +#endif diff --git a/src/dialogs/QskDialog.cpp b/src/dialogs/QskDialog.cpp index fc2493c8..3c6b2d3a 100644 --- a/src/dialogs/QskDialog.cpp +++ b/src/dialogs/QskDialog.cpp @@ -12,6 +12,10 @@ #include "QskSelectionSubWindow.h" #include "QskSelectionWindow.h" +#include "QskColorSelectionWindow.h" +#include "QskFileSelectionWindow.h" +#include "QskFontSelectionWindow.h" + #include "QskFocusIndicator.h" #include @@ -207,6 +211,39 @@ static QString qskSelectWindow( return selectedEntry; } +template< typename W > +static QString qskSelectPath( QskFileSelectionWindow< W >& window ) +{ + QString selectedFile = window.selectedPath(); + + if( window.exec() == QskDialog::Accepted ) + selectedFile = window.selectedPath(); + + return selectedFile; +} + +template< typename W > +static QColor qskSelectColor( QskColorSelectionWindow< W >& window ) +{ + QColor selectedColor = window.selectedColor(); + + if( window.exec() == QskDialog::Accepted ) + selectedColor = window.selectedColor(); + + return selectedColor; +} + +template< typename W > +static QFont qskSelectFont( QskFontSelectionWindow< W >& window ) +{ + QFont selectedFont = window.selectedFont(); + + if( window.exec() == QskDialog::Accepted ) + selectedFont = window.selectedFont(); + + return selectedFont; +} + class QskDialog::PrivateData { public: @@ -321,6 +358,124 @@ QString QskDialog::select( const QString& title, } +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 + + const auto filters = QDir::AllEntries | QDir::NoDotAndDotDot | QDir::AllDirs; + + if ( m_data->policy == EmbeddedBox ) + { + auto quickWindow = qobject_cast< QQuickWindow* >( m_data->transientParent ); + + if ( quickWindow == nullptr ) + quickWindow = qskSomeQuickWindow(); + + if ( quickWindow ) + { + QskFileSelectionWindow< QskDialogSubWindow > window( quickWindow, title, + actions, defaultAction, directory, filters ); + return qskSelectPath< QskDialogSubWindow >( window ); + } + } + + QskFileSelectionWindow< QskDialogWindow > window( m_data->transientParent, title, + actions, defaultAction, directory, filters ); + return qskSelectPath< QskDialogWindow >( window ); +} + +QString QskDialog::selectDirectory( + 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 + + const auto filters = QDir::NoDotAndDotDot | QDir::AllDirs; + + if ( m_data->policy == EmbeddedBox ) + { + auto quickWindow = qobject_cast< QQuickWindow* >( m_data->transientParent ); + + if ( quickWindow == nullptr ) + quickWindow = qskSomeQuickWindow(); + + if ( quickWindow ) + { + QskFileSelectionWindow< QskDialogSubWindow > window( quickWindow, title, + actions, defaultAction, directory, filters ); + return qskSelectPath< QskDialogSubWindow >( window ); + } + } + + QskFileSelectionWindow< QskDialogWindow > window( m_data->transientParent, title, + actions, defaultAction, directory, filters ); + return qskSelectPath< QskDialogWindow >( window ); +} + +QColor QskDialog::selectColor( const QString& title ) 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 ) + { + QskColorSelectionWindow< QskDialogSubWindow > window( quickWindow, title, + actions, defaultAction ); + return qskSelectColor< QskDialogSubWindow >( window ); + } + } + + QskColorSelectionWindow< QskDialogWindow > window( m_data->transientParent, title, + actions, defaultAction ); + return qskSelectColor< QskDialogWindow >( window ); +} + +QFont QskDialog::selectFont( const QString& title ) 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 ) + { + QskFontSelectionWindow< QskDialogSubWindow > window( quickWindow, title, + actions, defaultAction ); + return qskSelectFont< QskDialogSubWindow >( window ); + } + } + + QskFontSelectionWindow< QskDialogWindow > window( m_data->transientParent, title, + actions, defaultAction ); + return qskSelectFont< QskDialogWindow >( window ); +} + QskDialog::ActionRole QskDialog::actionRole( Action action ) { using Q = QPlatformDialogHelper; diff --git a/src/dialogs/QskDialog.h b/src/dialogs/QskDialog.h index 863a456f..803fa20d 100644 --- a/src/dialogs/QskDialog.h +++ b/src/dialogs/QskDialog.h @@ -8,6 +8,7 @@ #include "QskGlobal.h" +#include #include #include @@ -130,6 +131,16 @@ class QSK_EXPORT QskDialog : public QObject Q_INVOKABLE QString select( const QString& title, const QStringList& entries, int selectedRow = 0 ) const; + Q_INVOKABLE QString selectFile( const QString& title, + const QString& directory ) const; + + Q_INVOKABLE QString selectDirectory( const QString& title, + const QString& directory ) const; + + Q_INVOKABLE QColor selectColor( const QString& title ) const; + + Q_INVOKABLE QFont selectFont( const QString& title ) const; + static ActionRole actionRole( Action action ); Q_SIGNALS: diff --git a/src/dialogs/QskFileSelectionWindow.cpp b/src/dialogs/QskFileSelectionWindow.cpp new file mode 100644 index 00000000..38dd4eaf --- /dev/null +++ b/src/dialogs/QskFileSelectionWindow.cpp @@ -0,0 +1,455 @@ +/****************************************************************************** + * QSkinny - Copyright (C) The authors + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#include "QskFileSelectionWindow.h" + +#include "QskEvent.h" +#include "QskFunctions.h" +#include "QskInternalMacros.h" +#include "QskLinearBox.h" +#include "QskListView.h" +#include "QskScrollArea.h" +#include "QskPushButton.h" + +#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 ) +#include +#else +#include +#endif + +QSK_QT_PRIVATE_BEGIN +#include +QSK_QT_PRIVATE_END + +// copied from QskListView.cpp: +static inline int qskRowAt( const QskListView* listView, const QPointF& pos ) +{ + const auto rect = listView->viewContentsRect(); + if ( rect.contains( pos ) ) + { + const auto y = pos.y() - rect.top() + listView->scrollPos().y(); + + const int row = y / listView->rowHeight(); + if ( row >= 0 && row < listView->rowCount() ) + return row; + } + + return -1; +} + +// copied from QskGestureRecognizer.cpp: +static QMouseEvent* qskClonedMouseEvent( const QMouseEvent* event ) +{ + QMouseEvent* clonedEvent; + +#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 ) + clonedEvent = QQuickWindowPrivate::cloneMouseEvent( + const_cast< QMouseEvent* >( event ), nullptr ); +#else + clonedEvent = event->clone(); +#endif + clonedEvent->setAccepted( false ); + + return clonedEvent; +} + +namespace +{ +#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 ) + class Model : public QObject + { + Q_OBJECT + + public: + + Model( QObject* parent ) + : QObject( parent ) + { + } + + QString rootPath() const + { + return m_dir.absolutePath(); + } + + void setRootPath( const QString& path ) + { + m_dir.setPath( path ); + m_list = m_dir.entryInfoList(); + Q_EMIT directoryLoaded( m_dir.path() ); + Q_EMIT rootPathChanged( m_dir.path() ); + } + + QDir::Filters filter() const + { + return m_dir.filter(); + } + + void setFilter( QDir::Filters filter ) + { + m_dir.setFilter( filter ); + } + + int rows() const + { + return m_dir.count(); + } + + int columns() const + { + return 3; + } + + QVariant entry( int row, int col ) + { + Q_ASSERT( row < m_list.count() ); + + const auto fi = m_list.at( row ); + + switch( col ) + { + case 0: + return fi.fileName(); + case 1: + { + if( fi.isDir() ) + return "Directory"; + else if( fi.isExecutable() ) + return "Executable"; + else + return "File"; + } + default: + return fi.fileTime( QFileDevice::FileAccessTime ).toString(); + } + } + + Q_SIGNALS: + void directoryLoaded( const QString& path ); + void rootPathChanged( const QString& path ); + + private: + QDir m_dir; + QFileInfoList m_list; + }; +#else + class Model : public QFileSystemModel + { + using Inherited = QFileSystemModel; + + public: + Model( QObject* parent ) + : QFileSystemModel( parent ) + { + } + + int rows() const + { + const auto i = index( rootPath() ); + return Inherited::rowCount( i ); + } + + int columns() const + { + const auto i = index( rootPath() ); + return Inherited::columnCount( i ); + } + + QVariant entry( int row, int col ) const + { + const auto rootIndex = index( rootPath() ); + + const auto i = index( row, col, rootIndex ); + const auto v = data( i ); + + return v; + } + }; +#endif + + class FileSystemView : public QskListView + { + using Inherited = QskListView; + + Q_OBJECT + + public: + FileSystemView( const QString& directory, QDir::Filters filters, QQuickItem* parent = nullptr ) + : QskListView( parent ) + , m_model( new Model( this ) ) + { + const auto defaultWidth = 50; + + connect( m_model, &Model::directoryLoaded, this, [this, defaultWidth]() + { + m_columnWidths.fill( defaultWidth ); + updateScrollableSize(); + setScrollPos( { 0, 0 } ); + setSelectedRow( -1 ); + }); + + connect( m_model, &Model::rootPathChanged, + this, &FileSystemView::rootPathChanged ); + + m_columnWidths.fill( defaultWidth, m_model->columns() ); + + m_model->setFilter( filters ); + m_model->setRootPath( {} ); // invalidate to make sure to get an update + m_model->setRootPath( directory ); + } + + virtual int rowCount() const override + { + return m_model->rows(); + } + + virtual int columnCount() const override + { + return m_model->columns(); + } + + virtual qreal columnWidth( int col ) const override + { + auto w = m_columnWidths.at( col ); + + if( col == 0 ) + { + w = qMax( 250, w ); // min width for the name + } + + return w + 15; // spacing + } + + virtual qreal rowHeight() const override + { + const auto hint = strutSizeHint( Cell ); + const auto padding = paddingHint( Cell ); + + qreal h = effectiveFontHeight( Text ); + h += padding.top() + padding.bottom(); + + return qMax( h, hint.height() ); + } + + virtual QVariant valueAt( int row, int col ) const override + { + const auto v = m_model->entry( row, col ); + + const auto w = qskHorizontalAdvance( effectiveFont( Text ), v.toString() ); + + if( w > m_columnWidths.at( col ) ) + { + m_columnWidths[ col ] = w; + } + + return v; + } + + QString rootPath() const + { + return m_model->rootPath(); + } + + void setRootPath( const QString& rootPath ) + { + m_model->setRootPath( rootPath ); + } + + QDir::Filters filter() const + { + return m_model->filter(); + } + + Q_SIGNALS: + void rootPathChanged( const QString& rootPath ); + + protected: + void mousePressEvent( QMouseEvent* event ) override + { + if( m_doubleClickEvent && m_doubleClickEvent->timestamp() == event->timestamp() ) + { + // do not select rows from double click mouse events + m_doubleClickEvent = nullptr; + } + else + { + Inherited::mousePressEvent( event ); + } + } + + void mouseDoubleClickEvent( QMouseEvent* event ) override + { + m_doubleClickEvent = qskClonedMouseEvent( event ); + + const int row = qskRowAt( this, qskMousePosition( event ) ); + const auto path = valueAt( row, 0 ).toString(); + + QFileInfo fi( m_model->rootPath(), path ); + + if( fi.isDir() ) + { + m_model->setRootPath( fi.absoluteFilePath() ); + } + + Inherited::mouseDoubleClickEvent( event ); + } + + private: + Model* const m_model; + mutable QVector< int > m_columnWidths; + QMouseEvent* m_doubleClickEvent = nullptr; + }; +} + +template< typename W > +class QskFileSelectionWindow< W >::PrivateData +{ + public: + QskScrollArea* headerScrollArea; + QskLinearBox* headerBox; + QVector< QskPushButton* > breadcrumbsButtons; + + FileSystemView* fileView; +}; + +template< typename W > +QskFileSelectionWindow< W >::QskFileSelectionWindow( QObject* parent, const QString& title, + QskDialog::Actions actions, QskDialog::Action defaultAction, + const QString& directory, QDir::Filters filters ) + : QskWindowOrSubWindow< W >( parent, title, actions, defaultAction ) + , m_data( new PrivateData ) +{ + auto* outerBox = new QskLinearBox( Qt::Vertical ); + outerBox->setMargins( 20 ); + outerBox->setSpacing( 20 ); +#if 1 + outerBox->setFixedSize( 700, 500 ); +#endif + setupHeader( outerBox ); + setupFileSystemView( directory, filters, outerBox ); + + updateHeader( directory ); + + Inherited::setContentItem( outerBox ); +} + +template< typename W > +QskFileSelectionWindow< W >::~QskFileSelectionWindow() = default; + +template< typename W > +QString QskFileSelectionWindow< W >::selectedPath() const +{ + if( m_data->fileView->selectedRow() != -1 ) + { + const auto path = m_data->fileView->valueAt( m_data->fileView->selectedRow(), 0 ).toString(); + QFileInfo fi( m_data->fileView->rootPath(), path ); + return fi.absoluteFilePath(); + } + + return {}; +} + +template< typename W > +void QskFileSelectionWindow< W >::setupHeader( QQuickItem* parentItem ) +{ + m_data->headerScrollArea = new QskScrollArea( parentItem ); + m_data->headerScrollArea->setSizePolicy( Qt::Vertical, QskSizePolicy::Fixed ); + m_data->headerScrollArea->setFlickableOrientations( Qt::Horizontal ); + + m_data->headerBox = new QskLinearBox( Qt::Horizontal, m_data->headerScrollArea ); + m_data->headerScrollArea->setScrolledItem( m_data->headerBox ); +} + +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; +} + +template< typename W > +void QskFileSelectionWindow< W >::updateHeader( const QString& path ) +{ + const auto dirPaths = ::splitPath( path ); + + for( int i = 0; i < dirPaths.count(); ++i ) + { + QskPushButton* b; + + if( m_data->breadcrumbsButtons.count() <= i ) + { + b = new QskPushButton( m_data->headerBox ); + b->setStrutSizeHint( QskPushButton::Panel, { -1, -1 } ); + m_data->breadcrumbsButtons.append( b ); + } + else + { + b = m_data->breadcrumbsButtons.at( i ); + b->disconnect(); + } + + QFileInfo fi( dirPaths.at( i ) ); + b->setText( fi.baseName() ); + + QObject::connect( b, &QskPushButton::clicked, this, [this, fi]() + { + m_data->fileView->setRootPath( fi.filePath() ); + }); + } + + for( int i = dirPaths.count(); i < m_data->breadcrumbsButtons.count(); i++ ) + { + m_data->breadcrumbsButtons.at( i )->deleteLater(); + } + + m_data->breadcrumbsButtons.remove( dirPaths.count(), m_data->breadcrumbsButtons.count() - dirPaths.count() ); + + if( !m_data->breadcrumbsButtons.isEmpty() ) + { + auto* b = m_data->breadcrumbsButtons.last(); + + // button might just have been created and not be layed out yet: + QObject::connect( b, &QskPushButton::widthChanged, this, [this, b]() + { + m_data->headerScrollArea->ensureItemVisible( b ); + } ); + } +} + +template< typename W > +void QskFileSelectionWindow< W >::setupFileSystemView( const QString& directory, QDir::Filters filters, QQuickItem* parentItem ) +{ + m_data->fileView = new FileSystemView( directory, filters, parentItem ); + + QObject::connect( m_data->fileView, &FileSystemView::rootPathChanged, + this, &QskFileSelectionWindow< W >::updateHeader ); + + QObject::connect( m_data->fileView, &QskListView::selectedRowChanged, this, [this]() + { + if( m_data->fileView->filter() & QDir::Files ) + { + QFileInfo fi( selectedPath() ); + W::defaultButton()->setEnabled( !fi.isDir() ); + } + } ); +} + +template class QskFileSelectionWindow< QskDialogWindow >; +template class QskFileSelectionWindow< QskDialogSubWindow >; + +#include "QskFileSelectionWindow.moc" diff --git a/src/dialogs/QskFileSelectionWindow.h b/src/dialogs/QskFileSelectionWindow.h new file mode 100644 index 00000000..b8b44e74 --- /dev/null +++ b/src/dialogs/QskFileSelectionWindow.h @@ -0,0 +1,41 @@ +/****************************************************************************** + * QSkinny - Copyright (C) The authors + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#ifndef QSK_FILE_SELECTION_WINDODW_H +#define QSK_FILE_SELECTION_WINDODW_H + +#include "QskWindowOrSubWindow.h" + +#include + +template< typename W > +class QskFileSelectionWindow : public QskWindowOrSubWindow< W > +{ + using Inherited = QskWindowOrSubWindow< W >; + + public: + + QskFileSelectionWindow( QObject* parent, const QString& title, + QskDialog::Actions actions, QskDialog::Action defaultAction, + const QString& directory, QDir::Filters filters ); + ~QskFileSelectionWindow(); + + QString selectedPath() const; + + private: + void setupHeader( QQuickItem* parentItem ); + + static QStringList splitPath( const QString& path ); + + void updateHeader( const QString& path ); + + void setupFileSystemView( const QString& directory, QDir::Filters filters, QQuickItem* parentItem ); + + private: + class PrivateData; + std::unique_ptr< PrivateData >m_data; +}; + +#endif diff --git a/src/dialogs/QskFontSelectionWindow.cpp b/src/dialogs/QskFontSelectionWindow.cpp new file mode 100644 index 00000000..6611a194 --- /dev/null +++ b/src/dialogs/QskFontSelectionWindow.cpp @@ -0,0 +1,196 @@ +/****************************************************************************** + * QSkinny - Copyright (C) The authors + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#include "QskFontSelectionWindow.h" + +#include "QskFontRole.h" +#include "QskGridBox.h" +#include "QskLinearBox.h" +#include "QskSimpleListBox.h" +#include "QskTextLabel.h" + +#include + +namespace +{ +#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 ) + QStringList families() + { + QFontDatabase db; + return db.families(); + } + + QStringList styles( const QString& family ) + { + QFontDatabase db; + return db.styles( family ); + } + + QList< int > pointSizes( const QString& family ) + { + QFontDatabase db; + return db.pointSizes( family ); + } +#else + QStringList families() + { + return QFontDatabase::families(); + } + + QStringList styles( const QString& family ) + { + return QFontDatabase::styles( family ); + } + + QList< int > pointSizes( const QString& family ) + { + return QFontDatabase::pointSizes( family ); + } +#endif +} + +template< typename W > +class QskFontSelectionWindow< W >::PrivateData +{ + public: + QFont selectedFont; + + QskSimpleListBox* familyView; + QskSimpleListBox* styleView; + QskSimpleListBox* sizeView; + + QskTextLabel* outputLabel; +}; + +template< typename W > +QskFontSelectionWindow< W >::QskFontSelectionWindow( QObject* parent, const QString& title, + QskDialog::Actions actions, QskDialog::Action defaultAction ) + : Inherited( parent, title, actions, defaultAction ) + , m_data( new PrivateData ) +{ + auto* outerBox = new QskLinearBox( Qt::Vertical ); + outerBox->setMargins( 20 ); + outerBox->setSpacing( 20 ); +#if 1 + outerBox->setFixedSize( 700, 500 ); +#endif + + setupControls( outerBox ); + connectSignals(); + + Inherited::setContentItem( outerBox ); +} + +template< typename W > +QskFontSelectionWindow< W >::~QskFontSelectionWindow() = default; + +template< typename W > +QFont QskFontSelectionWindow< W >::selectedFont() const +{ + return m_data->selectedFont; +} + +template< typename W > +void QskFontSelectionWindow< W >::setupControls( QQuickItem* parentItem ) +{ + auto* gridBox = new QskGridBox( parentItem ); + gridBox->setSpacing( 10 ); + + const QskFontRole role( QskFontRole::Subtitle, QskFontRole::Normal ); + + auto* familyLabel = new QskTextLabel( "Family", gridBox ); + familyLabel->setFontRole( role ); + gridBox->addItem( familyLabel, 0, 0 ); + + auto* styleLabel = new QskTextLabel( "Style", gridBox ); + styleLabel->setFontRole( role ); + gridBox->addItem( styleLabel, 0, 1 ); + + auto* sizeLabel = new QskTextLabel( "Size", gridBox ); + sizeLabel->setFontRole( role ); + gridBox->addItem( sizeLabel, 0, 2 ); + + m_data->familyView = new QskSimpleListBox( gridBox ); + m_data->familyView->setSizePolicy( Qt::Vertical, QskSizePolicy::Expanding ); + gridBox->addItem( m_data->familyView, 1, 0 ); + + m_data->styleView = new QskSimpleListBox( gridBox ); + m_data->styleView->setSizePolicy( Qt::Vertical, QskSizePolicy::Expanding ); + gridBox->addItem( m_data->styleView, 1, 1 ); + + m_data->sizeView = new QskSimpleListBox( gridBox ); + m_data->sizeView->setSizePolicy( Qt::Vertical, QskSizePolicy::Expanding ); + gridBox->addItem( m_data->sizeView, 1, 2 ); + + auto* sampleLabel = new QskTextLabel( "Sample", gridBox ); + sampleLabel->setFontRole( role ); + gridBox->addItem( sampleLabel, 2, 0 ); + + m_data->outputLabel = new QskTextLabel( gridBox ); + m_data->outputLabel->setSizePolicy( QskSizePolicy::Preferred, QskSizePolicy::Minimum ); + m_data->outputLabel->setElideMode( Qt::ElideRight ); + gridBox->addItem( m_data->outputLabel, 3, 0, 1, 3 ); + + gridBox->setColumnStretchFactor( 0, 5 ); + gridBox->setColumnStretchFactor( 1, 3 ); + gridBox->setColumnStretchFactor( 2, 2 ); +} + +template< typename W > +void QskFontSelectionWindow< W >::connectSignals() +{ + m_data->familyView->setEntries( families() ); + + QObject::connect( m_data->familyView, &QskSimpleListBox::selectedEntryChanged, + this, [this]( const QString& family ) + { + m_data->styleView->setEntries( styles( family ) ); + } ); + + QObject::connect( m_data->familyView, &QskSimpleListBox::selectedEntryChanged, + this, [this]( const QString& family ) + { + const auto sizes = pointSizes( family ); + QStringList sizesString; + sizesString.reserve( sizes.count() ); + + for( const auto size : sizes ) + { + sizesString.append( QString::number( size ) ); + } + + m_data->sizeView->setEntries( sizesString ); + } ); + + auto displaySample = [this]() + { + const auto family = m_data->familyView->selectedEntry(); + const auto style = m_data->styleView->selectedEntry(); + const auto size = m_data->sizeView->selectedEntry(); + + if( !family.isNull() && !style.isNull() && !size.isNull() ) + { + auto& f = m_data->selectedFont; + + f = QFont( family, size.toInt() ); + f.setStyleName( style ); + + m_data->outputLabel->setSkinHint( QskTextLabel::Text | QskAspect::FontRole, f ); + m_data->outputLabel->resetImplicitSize(); + m_data->outputLabel->setText( "The quick brown fox jumps over the lazy dog" ); + } + else + { + m_data->outputLabel->setText( {} ); + } + }; + + QObject::connect( m_data->familyView, &QskSimpleListBox::selectedEntryChanged, this, displaySample ); + QObject::connect( m_data->styleView, &QskSimpleListBox::selectedEntryChanged, this, displaySample ); + QObject::connect( m_data->sizeView, &QskSimpleListBox::selectedEntryChanged, this, displaySample ); +} + +template class QskFontSelectionWindow< QskDialogWindow >; +template class QskFontSelectionWindow< QskDialogSubWindow >; diff --git a/src/dialogs/QskFontSelectionWindow.h b/src/dialogs/QskFontSelectionWindow.h new file mode 100644 index 00000000..e50c0d1b --- /dev/null +++ b/src/dialogs/QskFontSelectionWindow.h @@ -0,0 +1,32 @@ +/****************************************************************************** + * QSkinny - Copyright (C) The authors + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#ifndef QSK_FONT_SELECTION_WINDODW_H +#define QSK_FONT_SELECTION_WINDODW_H + +#include "QskWindowOrSubWindow.h" + +template< typename W > +class QskFontSelectionWindow : public QskWindowOrSubWindow< W > +{ + using Inherited = QskWindowOrSubWindow< W >; + + public: + + QskFontSelectionWindow( QObject* parent, const QString& title, + QskDialog::Actions actions, QskDialog::Action defaultAction ); + ~QskFontSelectionWindow(); + + QFont selectedFont() const; + + private: + void setupControls( QQuickItem* ); + void connectSignals(); + + class PrivateData; + std::unique_ptr< PrivateData > m_data; +}; + +#endif diff --git a/src/dialogs/QskWindowOrSubWindow.cpp b/src/dialogs/QskWindowOrSubWindow.cpp new file mode 100644 index 00000000..be6c4191 --- /dev/null +++ b/src/dialogs/QskWindowOrSubWindow.cpp @@ -0,0 +1,130 @@ +/****************************************************************************** + * QSkinny - Copyright (C) The authors + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#include "QskWindowOrSubWindow.h" + +#include "QskDialogButtonBox.h" +#include "QskFocusIndicator.h" + +// copied from QskDialog.cpp: +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(); +} + + +template< typename W > +QskWindowOrSubWindow< W >::QskWindowOrSubWindow( QObject* parent, const QString& title, + QskDialog::Actions actions, QskDialog::Action defaultAction ) + : W( parent, title, actions, defaultAction ) +{ +} + + +QskWindowOrSubWindow< QskDialogWindow >::QskWindowOrSubWindow( QObject* parent, const QString& title, + QskDialog::Actions actions, QskDialog::Action defaultAction ) + : 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 ); + } + + auto adjustSize = [this]() + { + const QSize size = sizeConstraint(); + + if ( size.isValid() ) + { + setFlags( flags() | Qt::MSWindowsFixedSizeDialogHint ); + setFixedSize( size ); + } + }; + + connect( contentItem(), &QQuickItem::widthChanged, this, adjustSize ); + connect( contentItem(), &QQuickItem::heightChanged, this, adjustSize ); +} + +QskDialog::DialogCode QskWindowOrSubWindow< QskDialogWindow >::exec() +{ + return qskExec( this ); +} + +void QskWindowOrSubWindow< QskDialogWindow >::setContentItem( QQuickItem* item ) +{ + QskDialogWindow::setDialogContentItem( item ); +} + +QskWindowOrSubWindow< QskDialogSubWindow >::QskWindowOrSubWindow( QObject* parent, const QString& title, + QskDialog::Actions actions, QskDialog::Action defaultAction ) + : 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 QskWindowOrSubWindow< QskDialogSubWindow >::exec() +{ + return QskDialogSubWindow::exec(); +} + +void QskWindowOrSubWindow< QskDialogSubWindow >::setContentItem( QQuickItem* item ) +{ + QskDialogSubWindow::setContentItem( item ); +} diff --git a/src/dialogs/QskWindowOrSubWindow.h b/src/dialogs/QskWindowOrSubWindow.h new file mode 100644 index 00000000..6475f803 --- /dev/null +++ b/src/dialogs/QskWindowOrSubWindow.h @@ -0,0 +1,44 @@ +/****************************************************************************** + * QSkinny - Copyright (C) The authors + * SPDX-License-Identifier: BSD-3-Clause + *****************************************************************************/ + +#ifndef QSK_WINDOW_OR_SUBWINDOW_H +#define QSK_WINDOW_OR_SUBWINDOW_H + +#include "QskDialogSubWindow.h" +#include "QskDialogWindow.h" + +template< typename W > +class QskWindowOrSubWindow : public W +{ + public: + QskWindowOrSubWindow( QObject* parent, const QString& title, + QskDialog::Actions actions, QskDialog::Action defaultAction ); +}; + +template<> +class QskWindowOrSubWindow< QskDialogWindow > : public QskDialogWindow +{ + public: + QskWindowOrSubWindow( QObject* parent, const QString& title, + QskDialog::Actions actions, QskDialog::Action defaultAction ); + + QskDialog::DialogCode exec(); + + void setContentItem( QQuickItem* item ); +}; + +template<> +class QskWindowOrSubWindow< QskDialogSubWindow > : public QskDialogSubWindow +{ + public: + QskWindowOrSubWindow( QObject* parent, const QString& title, + QskDialog::Actions actions, QskDialog::Action defaultAction ); + + QskDialog::DialogCode exec(); + + void setContentItem( QQuickItem* item ); +}; + +#endif