diff options
Diffstat (limited to 'src/widgets/categorylistview.cpp')
-rw-r--r-- | src/widgets/categorylistview.cpp | 637 |
1 files changed, 637 insertions, 0 deletions
diff --git a/src/widgets/categorylistview.cpp b/src/widgets/categorylistview.cpp new file mode 100644 index 0000000..2ebe6e0 --- /dev/null +++ b/src/widgets/categorylistview.cpp @@ -0,0 +1,637 @@ + +/*************************************************************************** +* Copyright (C) 2004 by * +* Jason Kivlighn ([email protected]) * +* Unai Garro ([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 "categorylistview.h" + +#include <tdelocale.h> +#include <tdemessagebox.h> +#include <kiconloader.h> +#include <tdepopupmenu.h> +#include <tdeconfig.h> +#include <tdeglobal.h> +#include <kdebug.h> + +#include "backends/recipedb.h" +#include "datablocks/categorytree.h" +#include "dialogs/createcategorydialog.h" +#include "dialogs/dependanciesdialog.h" + +CategoryCheckListItem::CategoryCheckListItem( CategoryCheckListView* klv, const Element &category, bool _exclusive ) : TQCheckListItem( klv, TQString::null, TQCheckListItem::CheckBox ), CategoryItemInfo( category ), + locked( false ), + exclusive( _exclusive ), + m_listview(klv) +{ + setOn( false ); // Set unchecked by default +} + +CategoryCheckListItem::CategoryCheckListItem( TQListViewItem* it, const Element &category, bool _exclusive ) : TQCheckListItem( it, TQString::null, TQCheckListItem::CheckBox ), CategoryItemInfo( category ), + locked( false ), + exclusive( _exclusive ), + m_listview((CategoryCheckListView*)it->listView()) +{ + setOn( false ); // Set unchecked by default +} + +CategoryCheckListItem::CategoryCheckListItem( CategoryCheckListView* klv, TQListViewItem* it, const Element &category, bool _exclusive ) : TQCheckListItem( klv, it, TQString::null, TQCheckListItem::CheckBox ), CategoryItemInfo( category ), + locked( false ), + exclusive( _exclusive ), + m_listview(klv) +{ + setOn( false ); // Set unchecked by default +} + +TQString CategoryCheckListItem::text( int column ) const +{ + if ( column == 1 ) + return ( TQString::number( ctyStored.id ) ); + else + return ( ctyStored.name ); +} + +void CategoryCheckListItem::setText( int column, const TQString &text ) +{ + switch ( column ) { + case 0: + ctyStored.name = text; + break; + default: + break; + } +} + +void CategoryCheckListItem::stateChange( bool on ) +{ + m_listview->stateChange(this,on); + + if ( locked ) + return; + + if ( on && exclusive ) { + setParentsState( false ); + setChildrenState( false ); + } +} + +void CategoryCheckListItem::setChildrenState( bool on ) +{ + if ( !isPopulated() ) + return; + + for ( CategoryCheckListItem * cat_it = ( CategoryCheckListItem* ) firstChild(); cat_it; cat_it = ( CategoryCheckListItem* ) cat_it->nextSibling() ) { + cat_it->locked = true; + cat_it->setOn( on ); + cat_it->setChildrenState( on ); + cat_it->locked = false; + } +} + +void CategoryCheckListItem::setParentsState( bool on ) +{ + locked = true; + + CategoryCheckListItem *cat_it; + for ( cat_it = ( CategoryCheckListItem* ) parent(); cat_it; cat_it = ( CategoryCheckListItem* ) cat_it->parent() ) + cat_it->setOn( on ); + + locked = false; +} + + + + +CategoryListItem::CategoryListItem( TQListView* klv, const Element &category ) : TQListViewItem( klv ), + CategoryItemInfo(category) +{} + +CategoryListItem::CategoryListItem( TQListViewItem* it, const Element &category ) : TQListViewItem( it ), + CategoryItemInfo(category) +{} + +CategoryListItem::CategoryListItem( TQListView* klv, TQListViewItem* it, const Element &category ) : TQListViewItem( klv, it ), + CategoryItemInfo(category) +{} + +TQString CategoryListItem::text( int column ) const +{ + if ( column == 1 ) + return ( TQString::number( ctyStored.id ) ); + else + return ( ctyStored.name ); +} + +void CategoryListItem::setText( int column, const TQString &text ) +{ + if ( column == 0 ) + ctyStored.name = text; +} + + + +CategoryListView::CategoryListView( TQWidget *parent, RecipeDB *db ) : DBListViewBase( parent, db, db->categoryTopLevelCount() ), + m_item_to_delete(0) +{ + //connect( this, TQ_SIGNAL( spacePressed(TQListViewItem*) ), TQ_SLOT( open(TQListViewItem*) ) ); + //connect( this, TQ_SIGNAL( returnPressed(TQListViewItem*) ), TQ_SLOT( open(TQListViewItem*) ) ); + //connect( this, TQ_SIGNAL( executed(TQListViewItem*) ), TQ_SLOT( open(TQListViewItem*) ) ); + + connect( this, TQ_SIGNAL( expanded(TQListViewItem*) ), TQ_SLOT( open(TQListViewItem*) ) ); + + setRootIsDecorated( true ); + setAllColumnsShowFocus( true ); + setDefaultRenameAction( TQListView::Reject ); +} + +void CategoryListView::init() +{ + connect( database, TQ_SIGNAL( categoryCreated( const Element &, int ) ), TQ_SLOT( checkCreateCategory( const Element &, int ) ) ); + connect( database, TQ_SIGNAL( categoryRemoved( int ) ), TQ_SLOT( removeCategory( int ) ) ); + connect( database, TQ_SIGNAL( categoryModified( const Element & ) ), TQ_SLOT( modifyCategory( const Element & ) ) ); + connect( database, TQ_SIGNAL( categoryModified( int, int ) ), TQ_SLOT( modifyCategory( int, int ) ) ); + connect( database, TQ_SIGNAL( categoriesMerged( int, int ) ), TQ_SLOT( mergeCategories( int, int ) ) ); +} + +// (Re)loads the data from the database +void CategoryListView::load( int limit, int offset ) +{ + items_map.clear(); + + CategoryTree list; + CategoryTree *p_list = &list; + database->loadCachedCategories( &p_list, limit, offset, -1, false ); + + setTotalItems(p_list->count()); + + for ( CategoryTree * child_it = p_list->firstChild(); child_it; child_it = child_it->nextSibling() ) { + createCategory( child_it->category, -1 ); + } +} + +void CategoryListView::populate( TQListViewItem *item ) +{ + CategoryItemInfo *cat_item = dynamic_cast<CategoryItemInfo*>(item); + if ( !cat_item || cat_item->isPopulated() ) return; + + if ( item->firstChild() && item->firstChild()->rtti() != PSEUDOLISTITEM_RTTI ) + return; + + delete item->firstChild(); //delete the "pseudo item" + + int id = cat_item->categoryId(); + cat_item->setPopulated(true); + + CategoryTree categoryTree; + database->loadCategories( &categoryTree, -1, 0, id, false ); + + for ( CategoryTree * child_it = categoryTree.firstChild(); child_it; child_it = child_it->nextSibling() ) { + createCategory( child_it->category, id ); + } +} + +void CategoryListView::populateAll( TQListViewItem *parent ) +{ + if ( !parent ) + parent = firstChild(); + + for ( TQListViewItem *item = parent; item; item = item->nextSibling() ) { + populate( item ); + if ( item->firstChild() ) + populateAll( item->firstChild() ); + } +} + +void CategoryListView::open( TQListViewItem *item ) +{ + Q_ASSERT( item ); + if ( !item->firstChild() || item->firstChild()->rtti() != PSEUDOLISTITEM_RTTI ) return; + + populate(item); + + item->setOpen(true); +} + +void CategoryListView::checkCreateCategory( const Element &el, int parent_id ) +{ + if ( parent_id != -1 || handleElement(el.name) ) { //only create this category if the base class okays it; allow all non-top-level items + createCategory(el,parent_id); + } +} + +void CategoryListView::modifyCategory( const Element &category ) +{ + TQListViewItem * item = items_map[ category.id ]; + + if ( item ) + item->setText( 0, category.name ); +} + +void CategoryListView::modifyCategory( int id, int parent_id ) +{ + TQMap<int,TQListViewItem*>::iterator item_it = items_map.find(id); + if ( item_it != items_map.end() ) { + TQListViewItem *item = *item_it; + Q_ASSERT( item ); + + removeElement(item,false); + if ( !item->parent() ) + takeItem( item ); + else + item->parent() ->takeItem( item ); + + if ( parent_id == -1 ) { + insertItem(item); + createElement(item); + } + else { + TQMap<int,TQListViewItem*>::iterator parent_item_it = items_map.find(parent_id); + if ( parent_item_it != items_map.end() && + dynamic_cast<CategoryItemInfo*>(*parent_item_it)->isPopulated() ) { + (*parent_item_it)->insertItem( item ); + createElement(item); + } + else { + if ( !(*parent_item_it)->firstChild() ) + new PseudoListItem( *parent_item_it ); + + //removeElement() was already called on this item, so we just delete it + //we can't delete it just yet because this function is called by a slot + delete m_item_to_delete; + m_item_to_delete = item; + } + } + } +} + +void CategoryListView::mergeCategories( int id1, int id2 ) +{ + TQListViewItem * to_item = items_map[ id1 ]; + TQListViewItem *from_item = items_map[ id2 ]; + + CategoryItemInfo *info_item = dynamic_cast<CategoryItemInfo*>(to_item); + + if ( to_item && info_item->isPopulated() && from_item ) { + //note that this takes care of any recipes that may be children as well + TQListViewItem *next_sibling; + for ( TQListViewItem * it = from_item->firstChild(); it; it = next_sibling ) { + next_sibling = it->nextSibling(); //get the sibling before we move the item + + removeElement(it,false); + from_item->takeItem( it ); + + to_item->insertItem( it ); + createElement(it); + } + } + + removeCategory( id2 ); +} + + +StdCategoryListView::StdCategoryListView( TQWidget *parent, RecipeDB *db, bool editable ) : CategoryListView( parent, db ), + clipboard_item( 0 ), + clipboard_parent( 0 ) +{ + addColumn( i18n( "Category" ) ); + + TDEConfig *config = TDEGlobal::config(); + config->setGroup( "Advanced" ); + bool show_id = config->readBoolEntry( "ShowID", false ); + addColumn( i18n( "Id" ), show_id ? -1 : 0 ); + + if ( editable ) { + setRenameable( 0, true ); + setDragEnabled( true ); + setAcceptDrops( true ); + + TDEIconLoader *il = new TDEIconLoader; + + kpop = new TDEPopupMenu( this ); + kpop->insertItem( il->loadIcon( "document-new", TDEIcon::NoGroup, 16 ), i18n( "&Create" ), this, TQ_SLOT( createNew() ), CTRL + Key_C ); + kpop->insertItem( il->loadIcon( "edit-delete", TDEIcon::NoGroup, 16 ), i18n( "&Delete" ), this, TQ_SLOT( remove + () ), Key_Delete ); + kpop->insertItem( il->loadIcon( "edit", TDEIcon::NoGroup, 16 ), i18n( "&Rename" ), this, TQ_SLOT( rename() ), CTRL + Key_R ); + kpop->insertSeparator(); + kpop->insertItem( il->loadIcon( "edit-cut", TDEIcon::NoGroup, 16 ), i18n( "Cu&t" ), this, TQ_SLOT( cut() ), CTRL + Key_X ); + kpop->insertItem( il->loadIcon( "edit-paste", TDEIcon::NoGroup, 16 ), i18n( "&Paste" ), this, TQ_SLOT( paste() ), CTRL + Key_V ); + kpop->insertItem( il->loadIcon( "edit-paste", TDEIcon::NoGroup, 16 ), i18n( "Paste as Subcategory" ), this, TQ_SLOT( pasteAsSub() ), CTRL + SHIFT + Key_V ); + kpop->polish(); + + delete il; + + connect( kpop, TQ_SIGNAL( aboutToShow() ), TQ_SLOT( preparePopup() ) ); + connect( this, TQ_SIGNAL( contextMenu( TDEListView *, TQListViewItem *, const TQPoint & ) ), TQ_SLOT( showPopup( TDEListView *, TQListViewItem *, const TQPoint & ) ) ); + connect( this, TQ_SIGNAL( doubleClicked( TQListViewItem*, const TQPoint &, int ) ), TQ_SLOT( modCategory( TQListViewItem* ) ) ); + connect( this, TQ_SIGNAL( itemRenamed ( TQListViewItem* ) ), TQ_SLOT( saveCategory( TQListViewItem* ) ) ); + connect( this, TQ_SIGNAL( moved( TQListViewItem *, TQListViewItem *, TQListViewItem * ) ), TQ_SLOT( changeCategoryParent( TQListViewItem *, TQListViewItem *, TQListViewItem * ) ) ); + } +} + +StdCategoryListView::~StdCategoryListView() +{ + delete clipboard_item; +} + +void StdCategoryListView::setPixmap( const TQPixmap &icon ) +{ + m_folder_icon = icon; +} + +void StdCategoryListView::preparePopup() +{ + //only enable the paste items if clipboard_item isn't null + kpop->setItemEnabled( kpop->idAt( 5 ), clipboard_item ); + kpop->setItemEnabled( kpop->idAt( 6 ), clipboard_item ); +} + +void StdCategoryListView::showPopup( TDEListView * /*l*/, TQListViewItem *i, const TQPoint &p ) +{ + if ( i ) + kpop->exec( p ); +} + +void StdCategoryListView::createNew() +{ + ElementList categories; + database->loadCategories( &categories ); + CreateCategoryDialog* categoryDialog = new CreateCategoryDialog( this, categories ); + + if ( categoryDialog->exec() == TQDialog::Accepted ) { + TQString result = categoryDialog->newCategoryName(); + int subcategory = categoryDialog->subcategory(); + + //check bounds first + if ( checkBounds( result ) ) + database->createNewCategory( result, subcategory ); // Create the new category in the database + } + delete categoryDialog; +} + +void StdCategoryListView::remove + () +{ + TQListViewItem * item = currentItem(); + + if ( item ) { + int id = item->text( 1 ).toInt(); + + ElementList recipeDependancies; + database->findUseOfCategoryInRecipes( &recipeDependancies, id ); + + if ( recipeDependancies.isEmpty() ) { + switch ( KMessageBox::warningContinueCancel( this, i18n( "Are you sure you want to delete this category and all its subcategories?" ) ) ) { + case KMessageBox::Continue: + database->removeCategory( id ); + break; + } + return; + } + else { // need warning! + ListInfo info; + info.list = recipeDependancies; + info.name = i18n("Recipes"); + DependanciesDialog warnDialog( this, info, false ); + + if ( warnDialog.exec() == TQDialog::Accepted ) + database->removeCategory( id ); + } + } +} + +void StdCategoryListView::rename() +{ + TQListViewItem * item = currentItem(); + + if ( item ) + CategoryListView::rename( item, 0 ); +} + +void StdCategoryListView::cut() +{ + //restore a never used cut + if ( clipboard_item ) { + if ( clipboard_parent ) + clipboard_parent->insertItem( clipboard_item ); + else + insertItem( clipboard_item ); + clipboard_item = 0; + } + + TQListViewItem *item = currentItem(); + + if ( item ) { + clipboard_item = item; + clipboard_parent = item->parent(); + + if ( item->parent() ) + item->parent() ->takeItem( item ); + else + takeItem( item ); + } +} + +void StdCategoryListView::paste() +{ + TQListViewItem * item = currentItem(); + if ( item && clipboard_item ) { + if ( item->parent() ) + item->parent() ->insertItem( clipboard_item ); + else + insertItem( clipboard_item ); + + database->modCategory( clipboard_item->text( 1 ).toInt(), item->parent() ? item->parent() ->text( 1 ).toInt() : -1 ); + clipboard_item = 0; + } +} + +void StdCategoryListView::pasteAsSub() +{ + TQListViewItem * item = currentItem(); + + if ( item && clipboard_item ) { + item->insertItem( clipboard_item ); + database->modCategory( clipboard_item->text( 1 ).toInt(), item->text( 1 ).toInt() ); + clipboard_item = 0; + } +} + +void StdCategoryListView::changeCategoryParent( TQListViewItem *item, TQListViewItem * /*afterFirst*/, TQListViewItem * /*afterNow*/ ) +{ + int new_parent_id = -1; + if ( TQListViewItem * parent = item->parent() ) + new_parent_id = parent->text( 1 ).toInt(); + + int cat_id = item->text( 1 ).toInt(); + + disconnect( TQ_SIGNAL( moved( TQListViewItem *, TQListViewItem *, TQListViewItem * ) ) ); + database->modCategory( cat_id, new_parent_id ); + connect( this, TQ_SIGNAL( moved( TQListViewItem *, TQListViewItem *, TQListViewItem * ) ), TQ_SLOT( changeCategoryParent( TQListViewItem *, TQListViewItem *, TQListViewItem * ) ) ); +} + +void StdCategoryListView::removeCategory( int id ) +{ + TQListViewItem * item = items_map[ id ]; + + items_map.remove( id ); + removeElement(item); +} + +void StdCategoryListView::createCategory( const Element &category, int parent_id ) +{ + CategoryListItem * new_item = 0; + if ( parent_id == -1 ) { + new_item = new CategoryListItem( this, category ); + } + else { + CategoryListItem *parent = (CategoryListItem*)items_map[ parent_id ]; + + if ( parent ) { + if ( parent->isPopulated() ) + new_item = new CategoryListItem( parent, category ); + else if ( !parent->firstChild() ) { + new PseudoListItem( parent ); + parent->setOpen(true); + } + } + } + + if ( new_item ) { + items_map.insert( category.id, new_item ); + new_item->setPixmap( 0, m_folder_icon ); + createElement(new_item);//new TQListViewItem(new_item); + + CategoryTree list; + CategoryTree *p_list = &list; + database->loadCachedCategories( &p_list, 1, 0, category.id, false ); + + if ( p_list->firstChild() ) + new PseudoListItem( new_item ); + } +} + +void StdCategoryListView::modCategory( TQListViewItem* i ) +{ + if ( i ) + CategoryListView::rename( i, 0 ); +} + +void StdCategoryListView::saveCategory( TQListViewItem* i ) +{ + CategoryListItem * cat_it = ( CategoryListItem* ) i; + + if ( !checkBounds( cat_it->categoryName() ) ) { + reload(ForceReload); //reset the changed text + return ; + } + + int existing_id = database->findExistingCategoryByName( cat_it->categoryName() ); + int cat_id = cat_it->categoryId(); + if ( existing_id != -1 && existing_id != cat_id ) //category already exists with this label... merge the two + { + switch ( KMessageBox::warningContinueCancel( this, i18n( "This category already exists. Continuing will merge these two categories into one. Are you sure?" ) ) ) + { + case KMessageBox::Continue: { + database->mergeCategories( existing_id, cat_id ); + break; + } + default: + reload(ForceReload); + break; + } + } + else + database->modCategory( cat_id, cat_it->categoryName() ); +} + +bool StdCategoryListView::checkBounds( const TQString &name ) +{ + if ( name.length() > uint(database->maxCategoryNameLength()) ) { + KMessageBox::error( this, TQString( i18n( "Category name cannot be longer than %1 characters." ) ).arg( database->maxCategoryNameLength() ) ); + return false; + } + + return true; +} + + + +CategoryCheckListView::CategoryCheckListView( TQWidget *parent, RecipeDB *db, bool _exclusive, const ElementList &init_items_checked ) : CategoryListView( parent, db ), + exclusive(_exclusive) +{ + addColumn( i18n( "Category" ) ); + + TDEConfig *config = TDEGlobal::config(); + config->setGroup( "Advanced" ); + bool show_id = config->readBoolEntry( "ShowID", false ); + addColumn( i18n( "Id" ), show_id ? -1 : 0 ); + + for ( ElementList::const_iterator it = init_items_checked.begin(); it != init_items_checked.end(); ++it ) + m_selections.append(*it); +} + +void CategoryCheckListView::removeCategory( int id ) +{ + TQListViewItem * item = items_map[ id ]; + + items_map.remove( id ); + removeElement(item); +} + +void CategoryCheckListView::createCategory( const Element &category, int parent_id ) +{ + CategoryCheckListItem * new_item = 0; + if ( parent_id == -1 ) { + new_item = new CategoryCheckListItem( this, category, exclusive ); + } + else { + TQListViewItem *parent = items_map[ parent_id ]; + if ( parent ) + new_item = new CategoryCheckListItem( parent, category, exclusive ); + } + + if ( new_item ) { + items_map.insert( category.id, new_item ); + createElement(new_item); + + CategoryTree list; + CategoryTree *p_list = &list; + database->loadCachedCategories( &p_list, 1, 0, category.id, false ); + + if ( p_list->firstChild() ) + new PseudoListItem( new_item ); + + + new_item->setOpen( false ); + } +} + +void CategoryCheckListView::stateChange( CategoryCheckListItem* it, bool on ) +{ + if ( !reloading() ) { + if ( on ) + m_selections.append(it->element()); + else + m_selections.remove(it->element()); + } +} + +void CategoryCheckListView::load( int limit, int offset ) +{ + CategoryListView::load(limit,offset); + + for ( TQValueList<Element>::const_iterator it = m_selections.begin(); it != m_selections.end(); ++it ) { + TQCheckListItem * item = ( TQCheckListItem* ) findItem( TQString::number( (*it).id ), 1 ); + if ( item ) { + item->setOn(true); + } + } +} + +#include "categorylistview.moc" |