summaryrefslogtreecommitdiffstats
path: root/src/dialogs/similarcategoriesdialog.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/dialogs/similarcategoriesdialog.cpp')
-rw-r--r--src/dialogs/similarcategoriesdialog.cpp383
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"