diff options
Diffstat (limited to 'src/dialogs/similarcategoriesdialog.cpp')
-rw-r--r-- | src/dialogs/similarcategoriesdialog.cpp | 383 |
1 files changed, 383 insertions, 0 deletions
diff --git a/src/dialogs/similarcategoriesdialog.cpp b/src/dialogs/similarcategoriesdialog.cpp new file mode 100644 index 0000000..75d9721 --- /dev/null +++ b/src/dialogs/similarcategoriesdialog.cpp @@ -0,0 +1,383 @@ +/*************************************************************************** +* Copyright (C) 2003-2006 Jason Kivlighn ([email protected]) * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +***************************************************************************/ + +#include "similarcategoriesdialog.h" + +#include <tqvariant.h> +#include <tqpushbutton.h> +#include <tqcombobox.h> +#include <tqslider.h> +#include <tqlabel.h> +#include <tqheader.h> +#include <tqlayout.h> +#include <tqtooltip.h> +#include <tqwhatsthis.h> + +#include <tdelistview.h> +#include <tdelocale.h> +#include <tdemessagebox.h> +#include <klineedit.h> + +#include "widgets/categorycombobox.h" +#include "backends/recipedb.h" + +SimilarCategoriesDialog::SimilarCategoriesDialog( ElementList &list, TQWidget* parent ) + : TQDialog( parent, "SimilarCategoriesDialog", true ), + m_elementList(list) +{ + SimilarCategoriesDialogLayout = new TQVBoxLayout( this, 11, 6, "SimilarCategoriesDialogLayout"); + + layout6 = new TQHBoxLayout( 0, 0, 6, "layout6"); + + layout4 = new TQGridLayout( 0, 1, 1, 0, 6, "layout4"); + + categoriesBox = new KLineEdit( this ); + + layout4->addWidget( categoriesBox, 0, 1 ); + + thresholdSlider = new TQSlider( this, "thresholdSlider" ); + thresholdSlider->setValue( 40 ); + thresholdSlider->setOrientation( TQSlider::Horizontal ); + + layout4->addWidget( thresholdSlider, 1, 1 ); + + thresholdLabel = new TQLabel( this, "thresholdLabel" ); + + layout4->addWidget( thresholdLabel, 1, 0 ); + + categoryLabel = new TQLabel( this, "categoryLabel" ); + + layout4->addWidget( categoryLabel, 0, 0 ); + layout6->addLayout( layout4 ); + + layout5 = new TQVBoxLayout( 0, 0, 6, "layout5"); + + searchButton = new TQPushButton( this, "searchButton" ); + layout5->addWidget( searchButton ); + spacer4 = new TQSpacerItem( 20, 51, TQSizePolicy::Minimum, TQSizePolicy::Expanding ); + layout5->addItem( spacer4 ); + layout6->addLayout( layout5 ); + SimilarCategoriesDialogLayout->addLayout( layout6 ); + + layout9 = new TQHBoxLayout( 0, 0, 6, "layout9"); + + layout8 = new TQVBoxLayout( 0, 0, 6, "layout8"); + + allLabel = new TQLabel( this, "allLabel" ); + layout8->addWidget( allLabel ); + + allListView = new TDEListView( this, "allListView" ); + allListView->setSizePolicy( TQSizePolicy( (TQSizePolicy::SizeType)7, (TQSizePolicy::SizeType)7, 0, 1, allListView->sizePolicy().hasHeightForWidth() ) ); + layout8->addWidget( allListView ); + layout9->addLayout( layout8 ); + + layout1 = new TQVBoxLayout( 0, 0, 6, "layout1"); + + removeButton = new TQPushButton( this, "removeButton" ); + layout1->addWidget( removeButton ); + + addButton = new TQPushButton( this, "addButton" ); + layout1->addWidget( addButton ); + spacer1 = new TQSpacerItem( 20, 61, TQSizePolicy::Minimum, TQSizePolicy::Expanding ); + layout1->addItem( spacer1 ); + layout9->addLayout( layout1 ); + + layout7 = new TQVBoxLayout( 0, 0, 6, "layout7"); + + toMergeLabel = new TQLabel( this, "toMergeLabel" ); + layout7->addWidget( toMergeLabel ); + + toMergeListView = new TDEListView( this, "toMergeListView" ); + toMergeListView->setSizePolicy( TQSizePolicy( (TQSizePolicy::SizeType)7, (TQSizePolicy::SizeType)7, 0, 1, toMergeListView->sizePolicy().hasHeightForWidth() ) ); + layout7->addWidget( toMergeListView ); + layout9->addLayout( layout7 ); + SimilarCategoriesDialogLayout->addLayout( layout9 ); + + layout10 = new TQHBoxLayout( 0, 0, 6, "layout10"); + spacer2 = new TQSpacerItem( 310, 20, TQSizePolicy::Expanding, TQSizePolicy::Minimum ); + layout10->addItem( spacer2 ); + + mergeButton = new TQPushButton( this, "mergeButton" ); + layout10->addWidget( mergeButton ); + + cancelButton = new TQPushButton( this, "cancelButton" ); + layout10->addWidget( cancelButton ); + SimilarCategoriesDialogLayout->addLayout( layout10 ); + languageChange(); + resize( TQSize(573, 429).expandedTo(minimumSizeHint()) ); + clearWState( WState_Polished ); + + connect( searchButton, TQ_SIGNAL(clicked()), this, TQ_SLOT(findMatches()) ); + connect( mergeButton, TQ_SIGNAL(clicked()), this, TQ_SLOT(mergeMatches()) ); + connect( cancelButton, TQ_SIGNAL(clicked()), this, TQ_SLOT(reject()) ); + connect( addButton, TQ_SIGNAL(clicked()), this, TQ_SLOT(addCategory()) ); + connect( removeButton, TQ_SIGNAL(clicked()), this, TQ_SLOT(removeCategory()) ); +} + +/* + * Destroys the object and frees any allocated resources + */ +SimilarCategoriesDialog::~SimilarCategoriesDialog() +{ + // no need to delete child widgets, TQt does it all for us +} + +/* + * Sets the strings of the subwidgets using the current + * language. + */ +void SimilarCategoriesDialog::languageChange() +{ + setCaption( i18n( "Similar Categories" ) ); + thresholdLabel->setText( i18n( "Threshold:" ) ); + categoryLabel->setText( i18n( "Category:" ) ); + searchButton->setText( i18n( "Search" ) ); + allLabel->setText( i18n( "Similar Categories:" ) ); + removeButton->setText( i18n( "<<" ) ); + addButton->setText( i18n( ">>" ) ); + toMergeLabel->setText( i18n( "Categories to Merge:" ) ); + mergeButton->setText( i18n( "Merge" ) ); + cancelButton->setText( i18n( "Cancel" ) ); + + allListView->addColumn( i18n( "Category" ) ); + //allListView->addColumn( i18n( "Id" ) ); + toMergeListView->addColumn( i18n( "Category" ) ); + //toMergeListView->addColumn( i18n( "Id" ) ); +} + +/*****************************************************/ +/*Function prototypes and libraries needed to compile*/ +/*****************************************************/ + +#include <stdlib.h> +#include <malloc.h> +#include <string.h> +int levenshtein_distance(const char *s,const char*t); +int minimum(int a,int b,int c); + +/****************************************/ +/*Implementation of Levenshtein distance*/ +/****************************************/ + +int levenshtein_distance(const char *s,const char*t) +/*Compute levenshtein distance between s and t*/ +{ + //Step 1 + int k,i,j,n,m,cost,*d,distance; + n=strlen(s); + m=strlen(t); + if(n!=0&&m!=0) + { + d=(int*)malloc((sizeof(int))*(m+1)*(n+1)); + m++; + n++; + //Step 2 + for(k=0;k<n;k++) + d[k]=k; + for(k=0;k<m;k++) + d[k*n]=k; + //Step 3 and 4 + for(i=1;i<n;i++) + for(j=1;j<m;j++) + { + //Step 5 + if(s[i-1]==t[j-1]) + cost=0; + else + cost=1; + //Step 6 + d[j*n+i]=minimum(d[(j-1)*n+i]+1,d[j*n+i-1]+1,d[(j-1)*n+i-1]+cost); + } + distance=d[n*m-1]; + free(d); + return distance; + } + else + return -1; //a negative return value means that one or both strings are empty. +} + +int minimum(int a,int b,int c) +/*Gets the minimum of three values*/ +{ + int min=a; + if(b<min) + min=b; + if(c<min) + min=c; + return min; +} + +/** @return an array of adjacent letter pairs contained in the input string */ +TQStringList letterPairs(const TQString& str) { + int numPairs = str.length()-1; + TQStringList pairs; + for (int i=0; i<numPairs; i++) { + pairs << str.mid(i,2); + } + return pairs; +} + +/** @return an ArrayList of 2-character Strings. */ +TQValueList<TQStringList> wordLetterPairs(const TQString &str) { + TQValueList<TQStringList> allPairs; + // Tokenize the string and put the tokens/words into an array + TQStringList words = TQStringList::split("\\s",str); + // For each word + for (uint w=0; w < words.count(); w++) { + // Find the pairs of characters + TQStringList pairsInWord = letterPairs(words[w]); + for (uint p=0; p < pairsInWord.count(); p++) { + allPairs.append(pairsInWord[p]); + } + } + return allPairs; +} + +/** @return lexical similarity value in the range [0,1] */ +double compareStrings(const TQString &str1, const TQString &str2) { + TQValueList<TQStringList> pairs1 = wordLetterPairs(str1.upper()); + TQValueList<TQStringList> pairs2 = wordLetterPairs(str2.upper()); + int intersection = 0; + int size_union = pairs1.count() + pairs2.count(); + for (uint i=0; i<pairs1.count(); i++) { + TQStringList pair1=pairs1[i]; + for(uint j=0; j<pairs2.count(); j++) { + TQStringList pair2=pairs2[j]; + if (pair1 == pair2) { + intersection++; + pairs2.remove( pairs2.at(j) ); + break; + } + } + } + return (2.0*intersection)/size_union; +} + + + +#include <kdebug.h> + +#if 0 +void RecipeActionsHandler::mergeSimilar() +{ + TQPtrList<TQListViewItem> items = parentListView->selectedItems(); + if ( items.count() > 1 ) + KMessageBox::sorry( kapp->mainWidget(), i18n("Please select only one category."), TQString::null ); + else if ( items.count() == 1 && items.at(0)->rtti() == 1001 ) { + CategoryListItem * cat_it = ( CategoryListItem* ) items.at(0); + TQString name = cat_it->categoryName(); + const double max_allowed_distance = 0.60; + const int length = name.length(); + ElementList categories; + database->loadCategories( &categories ); + + ElementList matches; + for ( ElementList::const_iterator it = categories.begin(); it != categories.end(); ++it ) { + #if 0 + if ( levenshtein_distance(name.latin1(),(*it).name.latin1())/double(TQMAX(length,(*it).name.length())) >= max_allowed_distance ) { + #else + if ( compareStrings(name,(*it).name) >= max_allowed_distance ) { + #endif + kdDebug()<<(*it).name<<" matches"<<endl; + if ( cat_it->categoryId() != (*it).id ) + matches.append(*it); + } + } + + + for ( ElementList::const_iterator it = categories.begin(); it != categories.end(); ++it ) { + database->mergeCategories(cat_it->categoryId(),(*it).id); + } + + } + else //either nothing was selected or a recipe was selected + KMessageBox::sorry( kapp->mainWidget(), i18n("No recipes selected."), i18n("Edit Recipe") ); +} +#endif + +void SimilarCategoriesDialog::findMatches() +{ + allListView->clear(); + toMergeListView->clear(); + + const double threshold = (100 - thresholdSlider->value())/100.0; + const TQString name = categoriesBox->text(); + + for ( ElementList::const_iterator it = m_elementList.begin(); it != m_elementList.end(); ++it ) { + //kdDebug()<<(*it).name<<" (result/threshold): "<<compareStrings(name,(*it).name)<<"/"<<threshold<<endl; + #if 0 + if ( levenshtein_distance(name.latin1(),(*it).name.latin1())/double(TQMAX(length,(*it).name.length())) >= max_allowed_distance ) { + #else + if ( compareStrings(name,(*it).name) >= threshold ) { + #endif + kdDebug()<<(*it).name<<" matches"<<endl; + //if ( id != (*it).id ) { + (void) new TQListViewItem(allListView,(*it).name,TQString::number((*it).id)); + (void) new TQListViewItem(toMergeListView,(*it).name,TQString::number((*it).id)); + //} + } + } +} + +void SimilarCategoriesDialog::mergeMatches() +{ + if ( !toMergeListView->firstChild() ) { + KMessageBox::sorry( this, i18n("No categories selected to merge."), TQString::null ); + return; + } + + //const int id = categoriesBox->id(categoriesBox->currentItem()); + //for ( TQListViewItem *item = toMergeListView->firstChild(); item; item = item->nextSibling() ) { + // m_database->mergeCategories(id,item->text(1).toInt()); + //} + + allListView->clear(); + //toMergeListView->clear(); + + TQDialog::accept(); +} + +TQValueList<int> SimilarCategoriesDialog::matches() const +{ + TQValueList<int> ids; + for ( TQListViewItem *item = toMergeListView->firstChild(); item; item = item->nextSibling() ) { + ids << item->text(1).toInt(); + } + + return ids; +} + +TQString SimilarCategoriesDialog::element() const +{ + return categoriesBox->text(); +} + +void SimilarCategoriesDialog::addCategory() +{ + TQListViewItem *item = allListView->selectedItem(); + if ( item ) + { + //make sure it isn't already in the list + for ( TQListViewItem *search_it = toMergeListView->firstChild(); search_it; search_it = search_it->nextSibling() ) { + if ( search_it->text(0) == item->text(0) ) + return; + } + + (void) new TQListViewItem(toMergeListView,item->text(0),item->text(1)); + } +} + +void SimilarCategoriesDialog::removeCategory() +{ + TQListViewItem *item = toMergeListView->selectedItem(); + if ( item ) + delete item; +} + +#include "similarcategoriesdialog.moc" |