Encapsulate hidden file detection into a class that can be used to generalize

and replace hard-coded "dotfile" detection. This starts to address issue # 273.

Hidden files can be identified using one or more match strings, with matching
governed either by wildcard (default) or regexp (optional). Matching is either
case-sensitive (default) or case-INsensitive (optional). A user interface for
inspecting and modifying match criteria is provided.

Match criteria is passed from/to applications or views as TQString that starts
with a single character:
  'w' - match strings are wildcards, match is case-sensitive
  'W' - match strings are wildcards, match is case-INsensitive
  'r' - match strings are regexps, match is case-sensitive
  'R' - match strings are regexps, match is case-INsensitive
The remainder of the string is a '/'-separated list of one or more match strings.

The TDEHiddenFileMatcher class is derived from a more general TDEStringMatcher
class which is available to applications to conduct other string matching tasks.

Signed-off-by: Vincent Reher <tde@4reher.org>
pull/178/head
Vincent Reher 3 years ago
parent 682b8acae4
commit 42fd81138d

@ -72,7 +72,7 @@ install( FILES
kcalendarsystem.h kcalendarsystemfactory.h kmacroexpander.h
kmanagerselection.h kmountpoint.h kuser.h klockfile.h
kidna.h ktempdir.h kshell.h fixx11h.h kxerrorhandler.h
tdelibs_export.h kde_file.h ktimezones.h
tdelibs_export.h kde_file.h ktimezones.h tdestringmatcher.h
${CMAKE_CURRENT_BINARY_DIR}/kdemacros.h
DESTINATION ${INCLUDE_INSTALL_DIR} )
@ -135,6 +135,7 @@ set( ${target}_SRCS
kprotocolinfo_tdecore.cpp kprotocolinfofactory.cpp kxerrorhandler.cpp
kuser.cpp tdeconfigskeleton.cpp tdeconfigdialogmanager.cpp klockfile.cpp
kqiodevicegzip_p.cpp ktimezones.cpp ksimpledirwatch.cpp
tdestringmatcher.cpp tdestringmatcher-dialog.cpp
)
tde_add_library( ${target} SHARED AUTOMOC

@ -259,6 +259,7 @@
7041 KTar
7042 KAr
7043 tdeio (bookmarks)
7077 HiddenFileDef
# 71xx are for tdeioslaves
7101 tdeio_file

@ -0,0 +1,234 @@
#include "tdestringmatcher-dialog.h"
#include "tdestringmatcher.h"
#include "tdeglobal.h"
#include "tdelocale.h"
#include "kdebug.h"
Customize_TDEStringMatcher_Dialog::Customize_TDEStringMatcher_Dialog(
TQStringList initPatternList,
bool initPatternIsRegex,
bool initPatternIsCaseInsensitive,
TQString callerTitle
)
{
// Initialize our "output" variables
patternList.clear();
patternIsRegex = false;
patternIsCaseInsensitive = false;
dialogResult = CriteriaApplied;
setModal( TRUE );
setCaption( i18n( "Specify string match criteria" ) );
// Create main layout and subordinate grid to position the widgets
TQHBoxLayout *topLayout = new TQHBoxLayout( this, 10 );
TQGridLayout *grid = new TQGridLayout( 0, 2 ); // 2 wide and autodetect number of rows
topLayout->addLayout( grid, 1 );
int gridrow = 1;
//=== Set up dialog title ===//
//TQString titleGeneral = i18n( "Define 'Hidden' files for" ) ;
//setCaption( titleGeneral + " " + callerTitle );
TQLabel *dialogTitle = new TQLabel( this );
dialogTitle->setText( "<b>" + callerTitle );
dialogTitle->setFrameStyle( TQFrame::Box | TQFrame::Plain );
dialogTitle->setAlignment( TQt::AlignHCenter | TQt::AlignVCenter );
grid->addMultiCellWidget( dialogTitle, gridrow, gridrow, 0, 1 );
gridrow++;
//=== Set up match pattern edit boxes ===/
TQLabel *patterns_label = new TQLabel( this );
patterns_label->setText( i18n( "Match any of these patterns" ) );
patterns_label->setAlignment( TQt::AlignHCenter );
grid->addMultiCellWidget( patterns_label, gridrow, gridrow, 0, 1 );
gridrow++;
// Limit the number of patterns to display to something reasonable
maxPatterns = initPatternList.size() + 2;
if ( maxPatterns > MAX_PATTERNS )
maxPatterns = MAX_PATTERNS;
// Set up edit boxes with initial patterns
short i = 0 ;
for (
TQStringList::iterator it = initPatternList.begin();
i < maxPatterns, it != initPatternList.end() ;
++it, i++, gridrow++
) {
le_Patterns[i] = new TQLineEdit( this );
le_Patterns[i]->setText(it->latin1()) ;
grid->addMultiCellWidget( le_Patterns[i], gridrow, gridrow, 0, 1 );
}
short first_empty_editbox = i;
for ( ; i < maxPatterns; i++, gridrow++ ) {
le_Patterns[i] = new TQLineEdit( this );
grid->addMultiCellWidget( le_Patterns[i], gridrow, gridrow, 0, 1 );
}
le_Patterns[first_empty_editbox]->setFocus();
//=== Set up match option check boxes ===//
// Create a group of check boxes
bg_Options = new TQButtonGroup( this, "checkGroup" );
bg_Options->setTitle( i18n( "Match options" ) );
grid->addMultiCellWidget( bg_Options, gridrow, gridrow, 0, 1 );
gridrow++;
// Create a layout for the check boxes
TQVBoxLayout *vbox = new TQVBoxLayout(bg_Options, 10);
vbox->addSpacing( bg_Options->fontMetrics().height() );
// Set up first checkbox
cb_patternIsRegex = new TQCheckBox( bg_Options );
cb_patternIsRegex->setChecked( initPatternIsRegex );
cb_patternIsRegex->setText( i18n( "&Regex (vs Wildcard)" ) );
TQToolTip::add( cb_patternIsRegex, i18n( "Regex (vs Wildcard)" ) );
vbox->addWidget( cb_patternIsRegex );
// Set up second checkbox
cb_patternIsCaseInsensitive = new TQCheckBox( bg_Options );
cb_patternIsCaseInsensitive->setChecked( initPatternIsCaseInsensitive );
cb_patternIsCaseInsensitive->setText( i18n( "&Case insensitive" ) );
TQToolTip::add( cb_patternIsCaseInsensitive, i18n( "Case insensitive" ) );
vbox->addWidget( cb_patternIsCaseInsensitive );
//=== Set up final dispostion radio buttons ===//
// Create a group of radio buttons
bg_Disposition = new TQButtonGroup( this, "radioGroup" );
bg_Disposition->setTitle( i18n( "Disposition of Match Criteria:" ) );
bg_Disposition->setRadioButtonExclusive(true);
grid->addMultiCellWidget( bg_Disposition, gridrow, gridrow, 0, 1 );
gridrow++;
// Create a layout for the radio buttons
TQVBoxLayout *vbox2 = new TQVBoxLayout(bg_Disposition, 10);
vbox2->addSpacing( bg_Disposition->fontMetrics().height() );
// Set up radio button 0
rb_Disposition = new TQRadioButton( bg_Disposition );
rb_Disposition->setText( i18n( "&Apply but do not save" ) );
rb_Disposition->setChecked( TRUE );
TQToolTip::add( rb_Disposition, i18n( "Changes will be applied temporarily" ) );
vbox2->addWidget( rb_Disposition );
// Set up radio button 1
rb_Disposition = new TQRadioButton( bg_Disposition );
rb_Disposition->setText( i18n( "Apply and &save as default" ) );
TQToolTip::add( rb_Disposition, i18n( "Changes will be applied and saved to disk" ) );
vbox2->addWidget( rb_Disposition );
// Set up radio button 2
rb_Disposition = new TQRadioButton( bg_Disposition );
rb_Disposition->setText( i18n( "Ignore and restore &default" ) );
TQToolTip::add( rb_Disposition, i18n( "Changes will be ignored and saved default restored" ) );
vbox2->addWidget( rb_Disposition );
connect( bg_Disposition, SIGNAL(clicked(int)), SLOT(setExitDisposition(int)) );
//=== Set up dialog exit buttons ===/
pb_OK = new TQPushButton( this, "pb_OK" );
pb_OK->setDefault( FALSE );
pb_OK->setText( i18n( "&OK" ) );
TQToolTip::add( pb_OK, i18n( "Save settings as default" ) );
pb_OK->setAccel( TQKeySequence( "Return" ) );
grid->addWidget( pb_OK, gridrow, 0 );
pb_Cancel = new TQPushButton( this, "pb_Cancel" );
pb_Cancel->setDefault( FALSE );
pb_Cancel->setText( i18n( "&Cancel" ) );
pb_Cancel->setAccel( TQKeySequence( "Esc" ) );
grid->addWidget( pb_Cancel, gridrow, 1 );
connect( pb_OK, SIGNAL( clicked() ), this, SLOT( exitOK() ) );
connect( pb_Cancel, SIGNAL( clicked() ), this, SLOT( exitCancel() ) );
}
//=== Dialog event handlers ===/
bool Customize_TDEStringMatcher_Dialog::event( TQEvent * e ) {
if ( e->type() != TQEvent::KeyPress ) {
TQKeyEvent * ke = (TQKeyEvent*) e;
if ( ke->key() == Key_Escape ) {
ke->accept();
exitCancel();
return TRUE;
}
else if ( ke->key() == Key_Return ) {
ke->accept();
exitOK();
return TRUE;
}
}
return TQWidget::event( e );
}
void Customize_TDEStringMatcher_Dialog::setExitDisposition(int buttonNum )
{
switch( buttonNum ) {
case 0:
dialogResult = CriteriaApplied;
break;
case 1:
dialogResult = SaveCriteria;
break;
case 2:
dialogResult = ReloadCriteria;
break;
}
}
//=== Dialog exit functions ===//
void Customize_TDEStringMatcher_Dialog::exitOK()
{
if ( dialogResult > CriteriaUnchanged ) {
patternList.clear();
for ( int i = 0; i < maxPatterns; i++ ) {
TQString pattern = le_Patterns[i]->text();
if ( ! ( pattern.isEmpty() ) ) {
patternList += pattern;
}
}
patternIsRegex = cb_patternIsRegex->isChecked();
patternIsCaseInsensitive = cb_patternIsCaseInsensitive->isChecked();
}
close();
}
void Customize_TDEStringMatcher_Dialog::exitCancel()
{
dialogResult = CriteriaUnchanged;
close();
}
//=== Object property accessors ===//
int Customize_TDEStringMatcher_Dialog::result()
{
return dialogResult;
}
bool Customize_TDEStringMatcher_Dialog::isPatternRegex()
{
return patternIsRegex;
}
bool Customize_TDEStringMatcher_Dialog::isPatternCaseInsensitive()
{
return patternIsCaseInsensitive;
}
TQStringList Customize_TDEStringMatcher_Dialog::getPatternList()
{
return patternList;
}
#include "tdestringmatcher-dialog.moc"

@ -0,0 +1,74 @@
#ifndef HIDDEN_FILE_DIALOG_H
#define HIDDEN_FILE_DIALOG_H
#include <tqapplication.h>
#include <tqdialog.h>
#include <tqlayout.h>
#include <tqvbox.h>
#include <tqbuttongroup.h>
#include <tqlabel.h>
#include <tqlineedit.h>
#include <tqcheckbox.h>
#include <tqradiobutton.h>
#include <tqpushbutton.h>
#include <tqtooltip.h>
#include "tdeglobal.h"
class TDECORE_EXPORT Customize_TDEStringMatcher_Dialog : public TQDialog
{
Q_OBJECT
public:
Customize_TDEStringMatcher_Dialog (
TQStringList initPatternList,
bool initPatternIsRegex,
bool initPatternIsCaseInsensitive,
TQString dialogTitle = TQString("Define String Matching Criteria")
);
int result();
TQStringList getPatternList();
bool isPatternRegex();
bool isPatternCaseInsensitive();
protected:
bool event( TQEvent * e );
private slots:
void setExitDisposition(int buttonNum );
void exitOK();
void exitCancel();
private:
TQLabel *patterns_label;
#define MAX_PATTERNS 10
TQLineEdit *le_Patterns[MAX_PATTERNS] ;
TQButtonGroup *bg_Options;
TQCheckBox *cb_patternIsRegex;
TQCheckBox *cb_patternIsCaseInsensitive;
TQButtonGroup *bg_Disposition;
TQRadioButton *rb_Disposition;
TQPushButton *pb_OK;
TQPushButton *pb_Apply;
TQPushButton *pb_Reload;
TQPushButton *pb_Cancel;
unsigned short maxPatterns ;
TQStringList patternList;
bool patternIsRegex;
bool patternIsCaseInsensitive;
int dialogResult;
};
#endif

@ -0,0 +1,128 @@
#include "tdestringmatcher.h"
#include "tdestringmatcher-dialog.h"
#include <tqregexp.h>
#include "tdeconfig.h"
#include "kdebug.h"
TDEStringMatcher::TDEStringMatcher()
{
patternsDivider = patternsDividerDefault;
setCriteria( criteriaMatchNothing );
}
TDEStringMatcher::~TDEStringMatcher()
{
}
bool TDEStringMatcher::match( const TQString& filename )
{
TQRegExp rxMatcher;
if ( filename.isEmpty() )
return FALSE;
rxMatcher.setWildcard( ! isRegex );
rxMatcher.setCaseSensitive( ! isCaseInsensitive );
for ( TQStringList::iterator it = patternList.begin(); it != patternList.end() ; ++it )
{
rxMatcher.setPattern( *it );
if ( rxMatcher.exactMatch( filename ) ) {
return( TRUE ) ;
}
}
return FALSE;
}
TQString TDEStringMatcher::getCriteria()
{
return Criteria;
}
void TDEStringMatcher::generateCriteria()
{
TQString newCriteria = patternList.join( TQString ( patternsDivider ) ) ;
TQChar leadingChar;
if ( isRegex )
leadingChar = 'r';
else
leadingChar = 'w' ;
if ( isCaseInsensitive )
leadingChar = leadingChar.upper();
newCriteria.prepend( leadingChar );
Criteria = newCriteria;
}
bool TDEStringMatcher::setCriteria( TQString newCriteria )
{
if ( newCriteria.length() == 0 ) {
return TRUE; // We always succeed when asked to do nothing!
}
TQChar optionsChar = newCriteria[0];
switch( optionsChar ) {
case 'w': isRegex = FALSE; isCaseInsensitive = FALSE; break;
case 'W': isRegex = FALSE; isCaseInsensitive = TRUE; break;
case 'r': isRegex = TRUE; isCaseInsensitive = FALSE; break;
case 'R': isRegex = TRUE; isCaseInsensitive = TRUE; break;
default :
kdWarning(7077)
<< "TDEStringMatcher::setCriteria: invalid leading character in criteria string: '"
<< newCriteria << "'" << endl;
return FALSE;
};
patternList.clear();
patternList = TQStringList::split( patternsDivider, newCriteria.mid(1), TRUE );
Criteria = newCriteria;
return TRUE;
}
int TDEStringMatcher::getMatchPropertiesFromUser( TQString dialogTitle )
{
Customize_TDEStringMatcher_Dialog defineHiddenUI =
Customize_TDEStringMatcher_Dialog( patternList, isRegex, isCaseInsensitive, dialogTitle );
defineHiddenUI.exec();
if ( defineHiddenUI.result() > CriteriaUnchanged ) {
patternList = defineHiddenUI.getPatternList();
isRegex = defineHiddenUI.isPatternRegex();
isCaseInsensitive = defineHiddenUI.isPatternCaseInsensitive();
generateCriteria();
kdDebug(7077)
<< "TDEStringMatcher::getMatchPropertiesFromUser: updated criteria string: '"
<< Criteria << "'" << endl;
}
return defineHiddenUI.result();
}
void TDEStringMatcher::setPatternsDivider( TQChar newPatternsDivider )
{
patternsDivider = newPatternsDivider;
}
TDEHiddenFileMatcher::TDEHiddenFileMatcher()
{
setPatternsDivider( patternsDivider4fileNames );
setCriteria( getGlobalCriteria() );
}
TDEHiddenFileMatcher::~TDEHiddenFileMatcher()
{
}
TQString TDEHiddenFileMatcher::getGlobalCriteria()
{
TDEConfig *config = TDEGlobal::config();
TDEConfigGroupSaver saver( config, globalSettingsGroup );
TQString defaultCriteria = config->readEntry( globalSettingsKey, criteriaMatchDotfiles );
return defaultCriteria;
}
void TDEHiddenFileMatcher::setGlobalCriteria()
{
TDEConfig *config = TDEGlobal::config();
TDEConfigGroupSaver saver( config, globalSettingsGroup );
config->writeEntry( globalSettingsKey, Criteria );
}

@ -0,0 +1,150 @@
#ifndef TDESTRINGMATCHER_H
#define TDESTRINGMATCHER_H
#include <tqstring.h>
#include <tqstringlist.h>
#include "tdeglobal.h"
/************
*
* Generic string matcher class.
*/
class TDECORE_EXPORT TDEStringMatcher
{
public:
TDEStringMatcher();
~TDEStringMatcher();
/**
Perform string matching based on the match-controlling
@properties described below.
*/
bool match( const TQString& );
/**
@return value of @property Criteria described below.
*/
TQString getCriteria();
/**
Process @param newCriteria to initialize / update the
@property patternsDivider described below.
*/
bool setCriteria(TQString newCriteria);
/**
Present dialog that allows user to view/update current match-
controlling @properties described below. @return one of the
values of @enum UpdateResult listed below. @param dialogTitle
can be used to describe the application/ the software component that is using
the TDEStringMatcher. Examples:
"Hidden File Definition<b>for Konqueror Listview"
"Global Default Hidden File Definition"
*/
int getMatchPropertiesFromUser( TQString dialogTitle = TQString("Define String Matching Criteria") );
enum class Status : int {
reloadCriteria = -1, // Criteria was not changed, request reloading default
criteriaUnchanged = 0, // Criteria was not changed
criteriaApplied = 1, // New criteria applied, should not be saved
saveCriteria = 2 // New criteria applied, request saving as default
};
#define ReloadCriteria -1
#define CriteriaUnchanged 0
#define CriteriaApplied 1
#define SaveCriteria 2
/**
Process @param newPatternsDivider to initialize / update
the match-controlling @properties that are described below.
*/
void setPatternsDivider(TQChar newPatternsDivider);
protected:
/**
Derive value of @property Criteria from the current values
of the match-controlling @properties described below
*/
void generateCriteria();
/**
Match-controlling properties:
@property patternList is a list of zero or more string patterns each
of which will be used as a wildcard or regex to match a string. If
the list is empty, then no strings will match.
@property isCaseInsensitive determines whether or not matching
will be done in "case insensitive" fashion.
@property isRegex determines whether or not the matching will
be regex-based (versus wildcard/globbing-based).
*/
bool isCaseInsensitive = FALSE;
bool isRegex = FALSE;
TQStringList patternList; // FIXME: Use STL?
/**
@property patternsDivider is a character used to separate multiple
match patterns in @property Criteria described below. This should be
a character that will not be used in a match pattern.
*/
TQChar patternsDivider;
/**
@property Criteria is a string which encodes the match-controlling
@properties described above. It has the format ^([wWrR])(.+). The
leading character's case determines @property isCaseInsensitive and
its value determines @property isRegex (rR: TRUE, wW: FALSE).
Remainder of string is a @property patternsDivider separated list of
match patterns that determines the content of @property patternList.
*/
TQString Criteria;
private:
static constexpr const char patternsDividerDefault = ',';
static constexpr const char * criteriaMatchNothing = "w";
};
/************
*
* Hidden file matcher class.
*/
class TDECORE_EXPORT TDEHiddenFileMatcher : public TDEStringMatcher {
friend class TDEGlobal; // for initInstance()
public:
TDEHiddenFileMatcher();
~TDEHiddenFileMatcher();
private:
TQString getGlobalCriteria();
void setGlobalCriteria();
/**
@property patternsDivider4fileNames specifies a reasonable
default for separating multiple file name match patterns.
*/
static constexpr const char patternsDivider4fileNames = '/';
/**
@property criteriaMatchDotfiles encodes the traditional
unix-style hidden file matching of so-called dotfiles.
It is default value of @property Criteria unless overridden.
*/
static constexpr const char * criteriaMatchDotfiles = "w.*";
// Default Criteria setting location in kdeglobals
static constexpr const char * globalSettingsGroup = "General";
static constexpr const char * globalSettingsKey = "globalHiddenFileSpec";
};
#endif
Loading…
Cancel
Save