1
mirror of https://code.videolan.org/videolan/vlc synced 2024-07-21 07:24:15 +02:00

qt: add expert preferences mode

This gives a table listing all options, similar to and inspired by the
`about:config` interface in Firefox.

Unique benefits:
 - It highlights which options have been modified from default state.
 - It allows selective resetting of individual options to default values.

Support is included to toggle booleans with a simple double-click.
Double-clicking on other types opens the edit dialog.

For colour selection items, the modify action directly opens the Qt
colour selection dialog rather than present the colour control used in
the advanced preferences view, since this is much cleaner.

Note that the existing simple and advanced views are not linked; if you
change an option in one view, that change is not reflected in the other,
and saving changes only uses the state from the selected view. The same
is currently true of the new expert mode, though I plan to later change
this behaviour (for all three).

Note also that hotkey items are deliberately excluded from this view.
The dedicated hotkey editor is best suited to managing hotkeys. It does
not work well to include the set of 224 unique hotkey options within this
table, especially since we'd have to duplicate the code checking for
duplicate assignments if we allow editing of them as with all other
option types within this interface. It may seem odd for the 'expert'
mode to be the only one without hotkey editing, however the hotkey editor
does not really fit well into 'advanced' mode either, and I have plans to
propose separating the hotkey editor entirely from within the set of
three views in a small redesign.

Fixes #18607.
This commit is contained in:
Lyndon Brown 2019-04-28 20:55:00 +01:00 committed by Jean-Baptiste Kempf
parent 3eb4edb38e
commit 8b75d973bb
10 changed files with 1100 additions and 5 deletions

View File

@ -110,6 +110,10 @@ libqt_plugin_la_SOURCES = \
gui/qt/dialogs/podcast/podcast_configuration.hpp \
gui/qt/dialogs/preferences/complete_preferences.cpp \
gui/qt/dialogs/preferences/complete_preferences.hpp \
gui/qt/dialogs/preferences/expert_model.cpp \
gui/qt/dialogs/preferences/expert_model.hpp \
gui/qt/dialogs/preferences/expert_view.cpp \
gui/qt/dialogs/preferences/expert_view.hpp \
gui/qt/dialogs/preferences/preferences.cpp \
gui/qt/dialogs/preferences/preferences.hpp \
gui/qt/dialogs/preferences/preferences_widgets.cpp \
@ -368,6 +372,8 @@ nodist_libqt_plugin_la_SOURCES = \
gui/qt/dialogs/plugins/plugins.moc.cpp \
gui/qt/dialogs/podcast/podcast_configuration.moc.cpp \
gui/qt/dialogs/preferences/complete_preferences.moc.cpp \
gui/qt/dialogs/preferences/expert_model.moc.cpp \
gui/qt/dialogs/preferences/expert_view.moc.cpp \
gui/qt/dialogs/preferences/preferences.moc.cpp \
gui/qt/dialogs/preferences/preferences_widgets.moc.cpp \
gui/qt/dialogs/preferences/simple_preferences.moc.cpp \

View File

@ -0,0 +1,434 @@
/*****************************************************************************
* expert_model.cpp : Detailed preferences overview - model
*****************************************************************************
* Copyright (C) 2019-2022 VLC authors and VideoLAN
*
* Authors: Lyndon Brown <jnqnfe@gmail.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <QVariant>
#include <QString>
#include <QFont>
#include <QGuiApplication>
#include <QClipboard>
#include <QMenu>
#include <QDialogButtonBox>
#include <QPushButton>
#include <QColorDialog>
#include <QVBoxLayout>
#include <QAction>
#include <QContextMenuEvent>
#include "expert_model.hpp"
#include "preferences_widgets.hpp"
#include <vlc_config_cat.h>
#include <vlc_modules.h>
#define COLUMN_COUNT 4
/*********************************************************************
* Model Item
*********************************************************************/
ExpertPrefsTableItem::ExpertPrefsTableItem( module_config_t *config,
const QString &mod_name,
const QString &mod_name_pretty,
bool is_core )
{
cfg_item = config;
if( CONFIG_CLASS( getType() ) == CONFIG_ITEM_STRING &&
config->value.psz != nullptr )
{
config->value.psz = strdup( config->value.psz );
}
/* Create table entry name from dot-prefixing module name to option name.
Many plugins dash-prefix their name to their option names, which we must
skip over here to replace the dash with a dot. */
QString option_name = config->psz_name;
int mod_name_len = mod_name.length();
if( !is_core && option_name.length() > mod_name_len &&
option_name.startsWith( mod_name ) && option_name[mod_name_len] == '-' )
option_name.remove( 0, mod_name_len + 1 );
name = QString( "%1.%2" ).arg( mod_name ).arg( option_name );
/* Some options use underscores when they shouldn't. Let's not pointlessly
leak that imperfection into our interface. */
name.replace( "_", "-" );
/* Create "title" text label for info panel. */
title = QString( "%1 :: %2" ).arg( mod_name_pretty ).arg( config->psz_text );
description = QString(); /* We'll use lazy creation */
updateMatchesDefault();
updateValueDisplayString();
}
ExpertPrefsTableItem::~ExpertPrefsTableItem()
{
if( CONFIG_CLASS( getType() ) == CONFIG_ITEM_STRING )
{
free( cfg_item->value.psz );
cfg_item->value.psz = nullptr;
}
}
const QString &ExpertPrefsTableItem::getDescription()
{
if( description.isNull() ) /* Lazy creation */
{
if( cfg_item->psz_longtext )
description = qfut( cfg_item->psz_longtext );
else if( cfg_item->psz_text )
description = qfut( cfg_item->psz_text );
else
description = QStringLiteral( u"" );
}
return description;
}
void ExpertPrefsTableItem::updateMatchesDefault()
{
switch ( CONFIG_CLASS( getType() ) )
{
case CONFIG_ITEM_FLOAT:
matches_default = (cfg_item->value.f == cfg_item->orig.f);
break;
case CONFIG_ITEM_BOOL:
case CONFIG_ITEM_INTEGER:
matches_default = (cfg_item->value.i == cfg_item->orig.i);
break;
case CONFIG_ITEM_STRING:
{
bool orig_is_empty = (cfg_item->orig.psz == nullptr || cfg_item->orig.psz[0] == '\0');
bool curr_is_empty = (cfg_item->value.psz == nullptr || cfg_item->value.psz[0] == '\0');
if (orig_is_empty)
matches_default = curr_is_empty;
else if( curr_is_empty )
matches_default = false;
else
matches_default = (strcmp( cfg_item->value.psz, cfg_item->orig.psz ) == 0);
break;
}
default:
vlc_assert_unreachable();
break;
}
}
void ExpertPrefsTableItem::updateValueDisplayString()
{
switch ( CONFIG_CLASS( getType() ) )
{
case CONFIG_ITEM_BOOL:
/* Do nothing - set at the model level to allow reusing cached translation lookup */
break;
case CONFIG_ITEM_FLOAT:
displayed_value = QString( "%L1" ).arg( cfg_item->value.f, 0, 'g' );
break;
case CONFIG_ITEM_INTEGER:
if( cfg_item->i_type == CONFIG_ITEM_RGB )
{
QString hex_upper = QString( "%1" ).arg( cfg_item->value.i, 0, 16 ).toUpper();
displayed_value = QString( "0x" ).append( hex_upper );
}
else
displayed_value = QString( "%L1" ).arg( cfg_item->value.i );
break;
case CONFIG_ITEM_STRING:
if( cfg_item->i_type != CONFIG_ITEM_PASSWORD )
displayed_value = cfg_item->value.psz;
else if( cfg_item->value.psz && *cfg_item->value.psz != '\0' )
displayed_value = QStringLiteral( u"•••••" );
else
displayed_value = QStringLiteral( u"" );
break;
default:
break;
}
}
void ExpertPrefsTableItem::setToDefault()
{
/* Note, this modifies our local copy of the item only */
if( CONFIG_CLASS( getType() ) == CONFIG_ITEM_STRING )
{
free( cfg_item->value.psz );
cfg_item->value.psz = (cfg_item->orig.psz) ? strdup( cfg_item->orig.psz ) : nullptr;
}
else
cfg_item->value = cfg_item->orig;
matches_default = true;
updateValueDisplayString();
}
void ExpertPrefsTableItem::toggleBoolean()
{
assert( CONFIG_CLASS( getType() ) == CONFIG_ITEM_BOOL );
/* Note, this modifies our local copy of the item only */
cfg_item->value.i = !cfg_item->value.i;
updateMatchesDefault();
/* Note, display text is updated by the model for efficiency */
}
/* search name and value columns */
bool ExpertPrefsTableItem::contains( const QString &text, Qt::CaseSensitivity cs )
{
return ( name.contains( text, cs ) || displayed_value.contains( text, cs ) );
}
/*********************************************************************
* Model
*********************************************************************/
ExpertPrefsTableModel::ExpertPrefsTableModel( module_t **mod_list, size_t mod_count,
QWidget *parent_ ) :
QAbstractListModel( parent_ )
{
/* Cache translations of common text */
state_same_text = qtr( "default" );
state_different_text = qtr( "modified" );
true_text = qtr( "true" );
false_text = qtr( "false" );
type_boolean_text = qtr( "boolean" );
type_float_text = qtr( "float" );
type_integer_text = qtr( "integer" );
type_color_text = qtr( "color" );
type_string_text = qtr( "string" );
type_password_text = qtr( "password" );
type_module_text = qtr( "module" );
type_module_list_text = qtr( "module-list" );
type_file_text = qtr( "file" );
type_directory_text = qtr( "directory" );
type_font_text = qtr( "font" );
type_unknown_text = qtr( "unknown" );
items.reserve( 1400 );
for( size_t i = 0; i < mod_count; i++ )
{
module_t *mod = mod_list[i];
unsigned confsize;
module_config_t *const config = module_config_get( mod, &confsize );
if( confsize == 0 )
continue;
config_sets.append( config );
bool is_core = module_is_main( mod );
QString mod_name = module_get_object( mod );
QString mod_name_pretty = is_core ? qtr( "Core" ) : module_GetShortName( mod );
enum vlc_config_subcat subcat = SUBCAT_UNKNOWN;
for( size_t j = 0; j < confsize; j++ )
{
module_config_t *cfg_item = config + j;
if( cfg_item->i_type == CONFIG_SUBCATEGORY )
{
subcat = (enum vlc_config_subcat) cfg_item->value.i;
continue;
}
if( subcat == SUBCAT_UNKNOWN || subcat == SUBCAT_HIDDEN )
continue;
/* Exclude hotkey items in favour of them being edited exclusively
via the dedicated hotkey editor. */
if( cfg_item->i_type == CONFIG_ITEM_KEY )
continue;
if( CONFIG_ITEM( cfg_item->i_type ) )
{
ExpertPrefsTableItem *item =
new ExpertPrefsTableItem( cfg_item, mod_name, mod_name_pretty, is_core );
if( CONFIG_CLASS( cfg_item->i_type ) == CONFIG_ITEM_BOOL )
{
/* Set the translated display text from cached lookup */
item->displayed_value = ( cfg_item->value.i == 0 ) ? false_text : true_text;
}
/* Sorted list insertion */
int insert_index = 0;
while( insert_index < items.count() )
{
if( item->name.compare( items[insert_index]->name ) < 0 )
break;
insert_index++;
}
items.insert( insert_index, item );
}
}
}
};
ExpertPrefsTableModel::~ExpertPrefsTableModel()
{
items.clear(); /* We must destroy the items before releasing the config set */
foreach ( module_config_t *config_set, config_sets )
module_config_free( config_set );
}
QVariant ExpertPrefsTableModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if( orientation == Qt::Horizontal && role == Qt::DisplayRole )
{
switch ( section )
{
case NameField: return qtr( "Option" );
case StateField: return qtr( "Status" );
case TypeField: return qtr( "Type" );
case ValueField: return qtr( "Value" );
}
}
return QVariant();
}
int ExpertPrefsTableModel::rowCount( const QModelIndex &parent ) const
{
return parent.isValid() ? 0 : items.count();
}
int ExpertPrefsTableModel::columnCount( const QModelIndex &parent ) const
{
return parent.isValid() ? 0 : COLUMN_COUNT;
}
QVariant ExpertPrefsTableModel::data( const QModelIndex &index, int role ) const
{
ExpertPrefsTableItem *item = itemAt( index );
switch ( role )
{
case Qt::DisplayRole:
{
switch ( index.column() )
{
case NameField: return item->name;
case ValueField: return item->displayed_value;
case StateField: return (item->matches_default) ? state_same_text
: state_different_text;
case TypeField:
{
switch ( item->cfg_item->i_type )
{
case CONFIG_ITEM_BOOL: return type_boolean_text; break;
case CONFIG_ITEM_FLOAT: return type_float_text; break;
case CONFIG_ITEM_INTEGER: return type_integer_text; break;
case CONFIG_ITEM_RGB: return type_color_text; break;
case CONFIG_ITEM_STRING: return type_string_text; break;
case CONFIG_ITEM_PASSWORD: return type_password_text; break;
case CONFIG_ITEM_MODULE_CAT:
case CONFIG_ITEM_MODULE: return type_module_text; break;
case CONFIG_ITEM_MODULE_LIST_CAT:
case CONFIG_ITEM_MODULE_LIST: return type_module_list_text; break;
case CONFIG_ITEM_LOADFILE:
case CONFIG_ITEM_SAVEFILE: return type_file_text; break;
case CONFIG_ITEM_DIRECTORY: return type_directory_text; break;
case CONFIG_ITEM_FONT: return type_font_text; break;
default: return type_unknown_text; break;
}
}
}
break;
}
case Qt::FontRole:
{
QFont font = QFont();
font.setBold( (item->matches_default) ? false : true );
return font;
}
case TypeClassRole:
return CONFIG_CLASS( item->getType() );
case CopyValueRole:
{
switch ( CONFIG_CLASS( item->getType() ) )
{
case CONFIG_ITEM_BOOL:
// Note, no translation wanted here!
return (item->cfg_item->value.i == 0) ? QStringLiteral( u"false")
: QStringLiteral( u"true" );
case CONFIG_ITEM_FLOAT:
return QString( "%1" ).arg( item->cfg_item->value.f );
case CONFIG_ITEM_INTEGER:
// For RGB it is presumably more useful to give a hex form that can be
// copy-pasted into a colour selection dialog rather than the raw int.
if( item->getType() == CONFIG_ITEM_RGB )
return QString( "#%1" ).arg( item->cfg_item->value.i, 0, 16 );
return QString( "%1" ).arg( item->cfg_item->value.i );
case CONFIG_ITEM_STRING:
return item->cfg_item->value.psz;
default:
break;
}
}
default: break;
}
return QVariant();
}
void ExpertPrefsTableModel::toggleBoolean( const QModelIndex &index )
{
ExpertPrefsTableItem *item = itemAt( index );
item->toggleBoolean();
/* Set the translated display text from cached lookup */
item->displayed_value = ( item->cfg_item->value.i == 0 ) ? false_text : true_text;
notifyUpdatedRow( index.row() );
}
void ExpertPrefsTableModel::setItemToDefault( const QModelIndex &index )
{
ExpertPrefsTableItem *item = itemAt( index );
item->setToDefault();
if( CONFIG_CLASS( item->cfg_item->i_type ) == CONFIG_ITEM_BOOL )
{
/* Set the translated display text from cached lookup */
item->displayed_value = ( item->cfg_item->value.i == 0 ) ? false_text : true_text;
}
notifyUpdatedRow( index.row() );
}
void ExpertPrefsTableModel::notifyUpdatedRow( int row )
{
emit dataChanged( index( row, 0 ), index( row, COLUMN_COUNT - 1 ) );
}
bool ExpertPrefsTableModel::submit()
{
for( int i= 0 ; i < items.count(); i++ )
{
ExpertPrefsTableItem *item = items[i];
/* save from copy to actual */
switch ( CONFIG_CLASS( item->getType() ) )
{
case CONFIG_ITEM_BOOL:
case CONFIG_ITEM_INTEGER:
config_PutInt( item->cfg_item->psz_name, item->cfg_item->value.i );
break;
case CONFIG_ITEM_FLOAT:
config_PutFloat( item->cfg_item->psz_name, item->cfg_item->value.f );
break;
case CONFIG_ITEM_STRING:
config_PutPsz( item->cfg_item->psz_name, item->cfg_item->value.psz );
break;
}
}
return true;
}

View File

@ -0,0 +1,133 @@
/*****************************************************************************
* expert_model.hpp : Detailed preferences overview - model
*****************************************************************************
* Copyright (C) 2019-2022 VLC authors and VideoLAN
*
* Authors: Lyndon Brown <jnqnfe@gmail.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#ifndef VLC_QT_EXPERT_PREFERENCES_MODEL_HPP_
#define VLC_QT_EXPERT_PREFERENCES_MODEL_HPP_
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <assert.h>
#include <QAbstractListModel>
#include "qt.hpp"
class ConfigControl;
class ExpertPrefsTableModel;
class ExpertPrefsTableItem
{
friend ExpertPrefsTableModel;
public:
ExpertPrefsTableItem( module_config_t *, const QString &, const QString &, bool );
~ExpertPrefsTableItem();
int getType() const { return cfg_item->i_type; }
const QString &getTitle() const { return title; }
const QString &getDescription();
module_config_t *getConfig() const { return cfg_item; }
bool matchesDefault() const { return matches_default; }
void updateMatchesDefault();
void updateValueDisplayString();
/** Search filter helper */
bool contains( const QString &text, Qt::CaseSensitivity cs );
private:
void setToDefault();
void toggleBoolean();
/** Name shown in table entry, combining module name and option name */
QString name;
/** The displayed value text */
QString displayed_value;
/** "Pretty" name for info panel */
QString title;
/** Description for info panel */
QString description;
/** The local copy of the corresponding option */
module_config_t *cfg_item;
/** Is item state different to the default value? */
bool matches_default;
};
class ExpertPrefsTableModel : public QAbstractListModel
{
Q_OBJECT
public:
ExpertPrefsTableModel( module_t **, size_t, QWidget * = nullptr );
~ExpertPrefsTableModel();
enum ItemField
{
NameField,
StateField,
TypeField,
ValueField,
};
enum DataRoles
{
TypeClassRole = Qt::UserRole,
CopyValueRole
};
ExpertPrefsTableItem *itemAt( int row ) const
{
assert( row < items.count() );
return items[ row ];
}
ExpertPrefsTableItem *itemAt( const QModelIndex &index ) const
{
return itemAt( index.row() );
}
void toggleBoolean( const QModelIndex & );
void setItemToDefault( const QModelIndex & );
void notifyUpdatedRow( int );
bool submit();
/* Standard model interface */
QVariant headerData( int section, Qt::Orientation orientation, int role = Qt::DisplayRole ) const override;
int rowCount( const QModelIndex &parent = QModelIndex() ) const override;
int columnCount( const QModelIndex &parent = QModelIndex() ) const override;
QVariant data( const QModelIndex &index, int role = Qt::DisplayRole ) const override;
private:
QList<module_config_t *> config_sets;
QList<ExpertPrefsTableItem*> items;
/* Cached translations of common text */
QString state_same_text;
QString state_different_text;
QString true_text;
QString false_text;
QString type_boolean_text;
QString type_float_text;
QString type_integer_text;
QString type_color_text;
QString type_string_text;
QString type_password_text;
QString type_module_text;
QString type_module_list_text;
QString type_file_text;
QString type_directory_text;
QString type_font_text;
QString type_unknown_text;
};
#endif

View File

@ -0,0 +1,281 @@
/*****************************************************************************
* expert_view.cpp : Detailed preferences overview - view
*****************************************************************************
* Copyright (C) 2019-2022 VLC authors and VideoLAN
*
* Authors: Lyndon Brown <jnqnfe@gmail.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <QString>
#include <QFont>
#include <QGuiApplication>
#include <QClipboard>
#include <QMenu>
#include <QDialogButtonBox>
#include <QPushButton>
#include <QColorDialog>
#include <QVBoxLayout>
#include <QAction>
#include <QContextMenuEvent>
#include "expert_view.hpp"
#include "preferences_widgets.hpp"
#include <vlc_config_cat.h>
#include <vlc_modules.h>
#define COLUMN_COUNT 4
/*********************************************************************
* The Table
*********************************************************************/
ExpertPrefsTable::ExpertPrefsTable( QWidget *parent ) :
QTreeView( parent )
{
setSelectionBehavior( QAbstractItemView::SelectRows );
setSelectionMode( QAbstractItemView::SingleSelection );
setAlternatingRowColors( true );
setStyleSheet( "QTreeView::item { padding: 9px 0; }" );
/* edit sub-dialog (reusable) */
expert_edit = new ExpertPrefsEditDialog( this );
connect( this, &QAbstractItemView::doubleClicked, this, &ExpertPrefsTable::doubleClicked );
/* context menu actions */
reset_action = new QAction( qtr( "&Reset" ), this );
toggle_action = new QAction( qtr( "&Toggle" ), this );
modify_action = new QAction( qtr( "&Modify" ), this );
copy_name_action = new QAction( qtr( "Copy &name" ), this );
copy_value_action = new QAction( qtr( "Copy &value" ), this );
connect( reset_action, &QAction::triggered, this, &ExpertPrefsTable::resetItem );
connect( toggle_action, &QAction::triggered, this, QOverload<>::of(&ExpertPrefsTable::toggleItem) );
connect( modify_action, &QAction::triggered, this, QOverload<>::of(&ExpertPrefsTable::modifyItem) );
connect( copy_name_action, &QAction::triggered, this, &ExpertPrefsTable::copyItemName );
connect( copy_value_action, &QAction::triggered, this, &ExpertPrefsTable::copyItemValue );
}
void ExpertPrefsTable::applyAll()
{
model()->submit();
}
/* apply filter on tree */
void ExpertPrefsTable::filter( const QString &text, bool modified_only )
{
bool text_nonempty = !text.isEmpty();
ExpertPrefsTableModel *model = myModel();
for( int i = 0 ; i < model->rowCount(); i++ )
{
ExpertPrefsTableItem *item = model->itemAt( i );
bool hide = ( ( modified_only && item->matchesDefault() ) ||
( text_nonempty && !item->contains( text, Qt::CaseInsensitive ) ) );
setRowHidden( i, QModelIndex(), hide );
}
}
#ifndef QT_NO_CONTEXTMENU
void ExpertPrefsTable::contextMenuEvent( QContextMenuEvent *event )
{
QModelIndex index = currentIndex();
if( !index.isValid() || isRowHidden( index.row(), QModelIndex() ) )
return;
/* Avoid menu from right-click on empty space after last item */
if( event->reason() == QContextMenuEvent::Mouse &&
!indexAt( viewport()->mapFromGlobal( event->globalPos() ) ).isValid() )
return;
ExpertPrefsTableItem *item = myModel()->itemAt( index );
QMenu *menu = new QMenu();
menu->setAttribute(Qt::WA_DeleteOnClose);
if( CONFIG_CLASS( item->getType() ) == CONFIG_ITEM_BOOL )
menu->addAction( toggle_action );
else
menu->addAction( modify_action );
menu->addSeparator();
menu->addAction( copy_name_action );
menu->addAction( copy_value_action );
copy_value_action->setEnabled( item->getType() != CONFIG_ITEM_PASSWORD );
menu->addSeparator();
menu->addAction( reset_action );
reset_action->setEnabled( !item->matchesDefault() );
menu->popup( event->globalPos() );
}
#endif // QT_NO_CONTEXTMENU
void ExpertPrefsTable::resetItem()
{
QModelIndex index = currentIndex();
if( !index.isValid() )
return;
myModel()->setItemToDefault( index );
}
void ExpertPrefsTable::toggleItem()
{
toggleItem( currentIndex() );
}
/* this obviously only applies to boolean options! */
void ExpertPrefsTable::toggleItem( const QModelIndex &index )
{
if( !index.isValid() )
return;
myModel()->toggleBoolean( index );
}
void ExpertPrefsTable::modifyItem()
{
QModelIndex index = currentIndex();
if( !index.isValid() )
return;
modifyItem( index );
}
void ExpertPrefsTable::modifyItem( const QModelIndex &index )
{
ExpertPrefsTableItem *item = myModel()->itemAt( index );
module_config_t *cfg_item = item->getConfig();
/* For colour items it's much cleaner here to directly show a `QColorDialog`
than provide indirect access to one via an `ExpertPrefsEditDialog` with a
`ColorConfigControl`. */
if( item->getType() == CONFIG_ITEM_RGB )
{
QColor color = QColorDialog::getColor( QColor( cfg_item->value.i ) );
if( color.isValid() )
{
cfg_item->value.i = (color.red() << 16) + (color.green() << 8) + color.blue();
item->updateMatchesDefault();
item->updateValueDisplayString();
myModel()->notifyUpdatedRow( index.row() );
}
}
else
{
ConfigControl *control = ConfigControl::createControl( cfg_item, nullptr );
expert_edit->setControl( control, item );
expert_edit->exec();
}
}
void ExpertPrefsTable::copyItemName()
{
QModelIndex index = currentIndex();
if( !index.isValid() )
return;
QClipboard *clipboard = QGuiApplication::clipboard();
QModelIndex name_index = myModel()->index( index.row(), ExpertPrefsTableModel::NameField );
clipboard->setText( name_index.data( Qt::DisplayRole ).toString() );
}
void ExpertPrefsTable::copyItemValue()
{
QModelIndex index = currentIndex();
if( !index.isValid() )
return;
QClipboard *clipboard = QGuiApplication::clipboard();
clipboard->setText( index.data( ExpertPrefsTableModel::CopyValueRole ).toString() );
}
void ExpertPrefsTable::doubleClicked( const QModelIndex &index )
{
if( index.data( ExpertPrefsTableModel::TypeClassRole ).toInt() == CONFIG_ITEM_BOOL )
{
toggleItem( index );
myModel()->notifyUpdatedRow( index.row() );
}
else
modifyItem( index );
}
/*********************************************************************
* The Edit Dialog
*********************************************************************/
ExpertPrefsEditDialog::ExpertPrefsEditDialog( ExpertPrefsTable *_table ) :
QDialog( _table ), table( _table )
{
table_item = nullptr;
control = nullptr;
control_widget = nullptr;
setWindowTitle( qtr( "Set option value" ) );
setWindowRole( "vlc-preferences" );
setWindowModality( Qt::WindowModal );
setMinimumSize( 380, 110 );
layout = new QVBoxLayout( this );
QDialogButtonBox *buttonBox = new QDialogButtonBox();
QPushButton *ok = new QPushButton( qtr( "&Ok" ) );
QPushButton *cancel = new QPushButton( qtr( "&Cancel" ) );
buttonBox->addButton( ok, QDialogButtonBox::AcceptRole );
buttonBox->addButton( cancel, QDialogButtonBox::RejectRole );
layout->addWidget( buttonBox );
connect( buttonBox, &QDialogButtonBox::accepted, this, &ExpertPrefsEditDialog::accept );
connect( buttonBox, &QDialogButtonBox::rejected, this, &ExpertPrefsEditDialog::reject );
setLayout( layout );
}
void ExpertPrefsEditDialog::setControl( ConfigControl *control_, ExpertPrefsTableItem *table_item_ )
{
table_item = table_item_;
control = control_;
control_widget = new QWidget( this );
control_widget->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed );
QVBoxLayout *control_layout = new QVBoxLayout( control_widget );
control->insertInto( control_layout );
layout->insertWidget( 0, control_widget );
}
void ExpertPrefsEditDialog::clearControl()
{
delete control;
delete control_widget;
control = nullptr;
control_widget = nullptr;
table_item = nullptr;
}
void ExpertPrefsEditDialog::accept()
{
control->storeValue();
table_item->updateMatchesDefault();
table_item->updateValueDisplayString();
clearControl();
QDialog::accept();
}
void ExpertPrefsEditDialog::reject()
{
clearControl();
QDialog::reject();
}

View File

@ -0,0 +1,101 @@
/*****************************************************************************
* expert_view.hpp : Detailed preferences overview - view
*****************************************************************************
* Copyright (C) 2019-2022 VLC authors and VideoLAN
*
* Authors: Lyndon Brown <jnqnfe@gmail.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#ifndef VLC_QT_EXPERT_PREFERENCES_VIEW_HPP_
#define VLC_QT_EXPERT_PREFERENCES_VIEW_HPP_
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "expert_model.hpp"
#include <QTreeView>
#include <QDialog>
#include "qt.hpp"
class QVBoxLayout;
class QAction;
class QContextMenuEvent;
class ConfigControl;
class ExpertPrefsEditDialog;
class ExpertPrefsTable : public QTreeView
{
Q_OBJECT
public:
ExpertPrefsTable( QWidget *parent = nullptr );
ExpertPrefsTableModel *myModel()
{
return static_cast<ExpertPrefsTableModel *>( model() );
}
void applyAll();
void filter( const QString &text, bool );
protected:
#ifndef QT_NO_CONTEXTMENU
void contextMenuEvent(QContextMenuEvent *event) Q_DECL_OVERRIDE;
#endif // QT_NO_CONTEXTMENU
private:
void modifyItem( const QModelIndex & );
void toggleItem( const QModelIndex & );
QAction *reset_action;
QAction *toggle_action;
QAction *modify_action;
QAction *copy_name_action;
QAction *copy_value_action;
ExpertPrefsEditDialog *expert_edit;
private slots:
void resetItem();
void toggleItem();
void modifyItem();
void copyItemName();
void copyItemValue();
void doubleClicked( const QModelIndex & );
};
class ExpertPrefsEditDialog : public QDialog
{
Q_OBJECT
public:
ExpertPrefsEditDialog( ExpertPrefsTable * );
void setControl( ConfigControl *, ExpertPrefsTableItem * );
private:
void clearControl();
ExpertPrefsTable *table;
ExpertPrefsTableItem *table_item;
QVBoxLayout *layout;
QWidget *control_widget;
ConfigControl *control;
private slots:
void accept();
void reject();
};
#endif

View File

@ -29,6 +29,7 @@
#include "widgets/native/qvlcframe.hpp"
#include "dialogs/errors/errors.hpp"
#include "expert_view.hpp"
#include "dialogs/preferences/complete_preferences.hpp"
#include "dialogs/preferences/simple_preferences.hpp"
#include "widgets/native/searchlineedit.hpp"
@ -73,8 +74,11 @@ PrefsDialog::PrefsDialog( QWindow *parent, qt_intf_t *_p_intf )
simple->setToolTip( qtr( "Switch to simple preferences view" ) );
all = new QRadioButton( qtr("All"), types );
all->setToolTip( qtr( "Switch to full preferences view" ) );
expert = new QRadioButton( qtr("Expert"), types );
expert->setToolTip( qtr( "Switch to expert preferences view" ) );
types_l->addWidget( simple );
types_l->addWidget( all );
types_l->addWidget( expert );
types->setLayout( types_l );
simple->setChecked( true );
@ -131,6 +135,30 @@ PrefsDialog::PrefsDialog( QWindow *parent, qt_intf_t *_p_intf )
stack->insertWidget( ADVANCED, advanced_split_widget );
/* Expert view (panel) */
expert_widget = new QWidget;
expert_widget_layout = new QGridLayout;
expert_widget->setLayout( expert_widget_layout );
expert_table_filter = nullptr;
expert_table = nullptr;
expert_text = new QLabel;
expert_longtext = new QLabel;
expert_text->setWordWrap(true);
expert_longtext->setWordWrap(true);
QFont textFont = QApplication::font();
textFont.setPointSize( textFont.pointSize() + 2 );
textFont.setUnderline( true );
expert_text->setFont( textFont );
expert_widget_layout->addWidget( expert_text, 2, 0, 1, 2 );
expert_widget_layout->addWidget( expert_longtext, 3, 0, 1, 2 );
stack->insertWidget( EXPERT, expert_widget );
/* Layout */
main_layout->addWidget( stack, 0, 0, 3, 3 );
main_layout->addWidget( types, 3, 0, 2, 1 );
@ -149,6 +177,7 @@ PrefsDialog::PrefsDialog( QWindow *parent, qt_intf_t *_p_intf )
BUTTONACT( simple, &PrefsDialog::setSimple );
BUTTONACT( all, &PrefsDialog::setAdvanced );
BUTTONACT( expert, &PrefsDialog::setExpert );
QVLCTools::restoreWidgetPosition( p_intf, "Preferences", this, QSize( 850, 700 ) );
}
@ -158,12 +187,55 @@ PrefsDialog::~PrefsDialog()
module_list_free( p_list );
}
void PrefsDialog::setExpert()
{
/* Lazy creation */
if( !expert_table )
{
if ( !p_list )
p_list = module_list_get( &count );
expert_table_filter = new SearchLineEdit( expert_widget );
expert_table_filter->setMinimumHeight( 26 );
expert_table_filter_modified = new QCheckBox( qtr( "Modified only" ), expert_widget );
QShortcut *search = new QShortcut( QKeySequence( QKeySequence::Find ), expert_table_filter );
ExpertPrefsTableModel *table_model = new ExpertPrefsTableModel( p_list, count, this );
expert_table = new ExpertPrefsTable( expert_widget );
expert_table->setModel( table_model );
expert_table->resizeColumnToContents( ExpertPrefsTableModel::NameField );
expert_widget_layout->addWidget( expert_table_filter, 0, 0 );
expert_widget_layout->addWidget( expert_table_filter_modified, 0, 1 );
expert_widget_layout->addWidget( expert_table, 1, 0, 1, 2 );
connect( expert_table->selectionModel(), &QItemSelectionModel::currentChanged,
this, &PrefsDialog::changeExpertDesc );
connect( expert_table_filter, &SearchLineEdit::textChanged,
this, &PrefsDialog::expertTableFilterChanged );
connect( expert_table_filter_modified, &QCheckBox::toggled,
this, &PrefsDialog::expertTableFilterModifiedToggled );
connect( search, &QShortcut::activated,
expert_table_filter, QOverload<>::of(&SearchLineEdit::setFocus) );
/* Set initial selection */
expert_table->setCurrentIndex(
expert_table->model()->index( 0, 0, QModelIndex() ) );
}
expert->setChecked( true );
stack->setCurrentIndex( EXPERT );
setWindowTitle( qtr( "Expert Preferences" ) );
}
void PrefsDialog::setAdvanced()
{
/* Lazy creation */
if( !advanced_tree )
{
p_list = module_list_get( &count );
if ( !p_list )
p_list = module_list_get( &count );
advanced_tree = new PrefsTree( p_intf, advanced_tree_panel, p_list, count );
@ -237,12 +309,23 @@ void PrefsDialog::changeAdvPanel( QTreeWidgetItem *item )
if( !node->panel )
{
node->panel = new AdvPrefsPanel( p_intf, advanced_panels_stack, node );
advanced_panels_stack->insertWidget( advanced_panels_stack->count(),
node->panel );
advanced_panels_stack->addWidget( node->panel );
}
advanced_panels_stack->setCurrentWidget( node->panel );
}
/* Changing from one Expert item description to another */
void PrefsDialog::changeExpertDesc( const QModelIndex &current, const QModelIndex &previous )
{
Q_UNUSED( previous );
if( !current.isValid() )
return;
ExpertPrefsTableItem *item = expert_table->myModel()->itemAt( current );
expert_text->setText( item->getTitle() );
expert_longtext->setText( item->getDescription() );
}
/* Actual apply and save for the preferences */
void PrefsDialog::save()
{
@ -259,6 +342,11 @@ void PrefsDialog::save()
msg_Dbg( p_intf, "Saving the advanced preferences" );
advanced_tree->applyAll();
}
else if( expert->isChecked() && expert_table->isVisible() )
{
msg_Dbg( p_intf, "Saving the expert preferences" );
expert_table->applyAll();
}
/* Save to file */
if( config_SaveConfigFile( p_intf ) != 0 )
@ -309,6 +397,16 @@ void PrefsDialog::reset()
}
}
void PrefsDialog::expertTableFilterModifiedToggled( bool checked )
{
expert_table->filter( expert_table_filter->text(), checked );
}
void PrefsDialog::expertTableFilterChanged( const QString & text )
{
expert_table->filter( text, expert_table_filter_modified->isChecked() );
}
void PrefsDialog::advancedTreeFilterChanged( const QString & text )
{
advanced_tree->filter( text );

View File

@ -26,6 +26,7 @@
#include "widgets/native/qvlcframe.hpp"
#include "dialogs/preferences/simple_preferences.hpp"
class ExpertPrefsTable;
class PrefsTree;
class SPrefsCatList;
class SPrefsPanel;
@ -53,9 +54,9 @@ private:
QStackedWidget *stack;
/* View selection */
enum { SIMPLE, ADVANCED };
enum { SIMPLE, ADVANCED, EXPERT };
QGroupBox *types;
QRadioButton *simple, *all;
QRadioButton *simple, *all, *expert;
/* Simple view components */
QWidget *simple_split_widget;
@ -72,13 +73,26 @@ private:
PrefsTree *advanced_tree;
QStackedWidget *advanced_panels_stack;
/* Expert view components */
QWidget *expert_widget;
QGridLayout *expert_widget_layout;
SearchLineEdit *expert_table_filter;
QCheckBox *expert_table_filter_modified;
ExpertPrefsTable *expert_table;
QLabel *expert_text;
QLabel *expert_longtext;
private slots:
void setExpert();
void setAdvanced();
void setSimple();
void changeExpertDesc( const QModelIndex &, const QModelIndex & );
void changeAdvPanel( QTreeWidgetItem * );
void changeSimplePanel( int );
void advancedTreeFilterChanged( const QString & );
void expertTableFilterChanged( const QString & );
void expertTableFilterModifiedToggled( bool );
void onlyLoadedToggled();
void save();

View File

@ -214,6 +214,13 @@ VStringConfigControl::doApply()
config_PutPsz( getName(), qtu( getValue() ) );
}
void VStringConfigControl::storeValue()
{
/* Note, this modifies our local copy of the item only */
free( p_item->value.psz );
p_item->value.psz = strdup( qtu( getValue() ) );
}
/*********** String **************/
StringConfigControl::StringConfigControl( module_config_t *_p_item,
QWidget *_parent ) :
@ -851,6 +858,12 @@ VIntConfigControl::doApply()
config_PutInt( getName(), getValue() );
}
void VIntConfigControl::storeValue()
{
/* Note, this modifies our local copy of the item only */
p_item->value.i = getValue();
}
/*********** Integer **************/
IntegerConfigControl::IntegerConfigControl( module_config_t *_p_item,
QWidget *p ) :
@ -1179,6 +1192,12 @@ VFloatConfigControl::doApply()
config_PutFloat( getName(), getValue() );
}
void VFloatConfigControl::storeValue()
{
/* Note, this modifies our local copy of the item only */
p_item->value.f = getValue();
}
/*********** Float **************/
FloatConfigControl::FloatConfigControl( module_config_t *_p_item,
QWidget *p ) :

View File

@ -95,6 +95,7 @@ public:
/** Inserts control into an existing box layout */
virtual void insertInto( QBoxLayout*, int index = 0 ) { Q_UNUSED( index ); }
virtual void doApply() = 0;
virtual void storeValue() = 0;
protected:
ConfigControl( module_config_t *_p_conf ) : p_item( _p_conf ) {}
virtual void changeVisibility( bool ) { }
@ -112,6 +113,7 @@ class VIntConfigControl : public ConfigControl
public:
virtual int getValue() const = 0;
virtual void doApply() Q_DECL_OVERRIDE;
virtual void storeValue() Q_DECL_OVERRIDE;
protected:
VIntConfigControl( module_config_t *i ) : ConfigControl(i) {}
};
@ -222,6 +224,7 @@ class VFloatConfigControl : public ConfigControl
public:
virtual float getValue() const = 0;
void doApply() Q_DECL_OVERRIDE;
void storeValue() Q_DECL_OVERRIDE;
protected:
VFloatConfigControl( module_config_t *i ) : ConfigControl(i) {}
};
@ -264,6 +267,7 @@ class VStringConfigControl : public ConfigControl
public:
virtual QString getValue() const = 0;
void doApply() Q_DECL_OVERRIDE;
void storeValue() Q_DECL_OVERRIDE;
protected:
VStringConfigControl( module_config_t *i ) : ConfigControl(i) {}
};
@ -418,6 +422,7 @@ public:
KeySelectorControl( QWidget * );
void insertInto( QGridLayout*, int row = 0 ) Q_DECL_OVERRIDE;
void doApply() Q_DECL_OVERRIDE;
void storeValue() {};
enum ColumnIndex
{
ACTION_COL = 0,

View File

@ -681,6 +681,10 @@ modules/gui/qt/dialogs/fingerprint/chromaprint.cpp
modules/gui/qt/dialogs/fingerprint/chromaprint.hpp
modules/gui/qt/dialogs/preferences/complete_preferences.cpp
modules/gui/qt/dialogs/preferences/complete_preferences.hpp
modules/gui/qt/dialogs/preferences/expert_model.cpp
modules/gui/qt/dialogs/preferences/expert_model.hpp
modules/gui/qt/dialogs/preferences/expert_view.cpp
modules/gui/qt/dialogs/preferences/expert_view.hpp
modules/gui/qt/menus/custom_menus.cpp
modules/gui/qt/menus/custom_menus.hpp
modules/gui/qt/menus/qml_menu_wrapper.cpp