/*************************************************************************** * Copyright (C) 2004 by * * Jason Kivlighn (jkivlighn@gmail.com) * * Unai Garro (ugarro@users.sourceforge.net) * * * * 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 #include #include #include #include #include #include #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, SIGNAL( spacePressed(TQListViewItem*) ), SLOT( open(TQListViewItem*) ) ); //connect( this, SIGNAL( returnPressed(TQListViewItem*) ), SLOT( open(TQListViewItem*) ) ); //connect( this, SIGNAL( executed(TQListViewItem*) ), SLOT( open(TQListViewItem*) ) ); connect( this, SIGNAL( expanded(TQListViewItem*) ), SLOT( open(TQListViewItem*) ) ); setRootIsDecorated( true ); setAllColumnsShowFocus( true ); setDefaultRenameAction( TQListView::Reject ); } void CategoryListView::init() { connect( database, SIGNAL( categoryCreated( const Element &, int ) ), SLOT( checkCreateCategory( const Element &, int ) ) ); connect( database, SIGNAL( categoryRemoved( int ) ), SLOT( removeCategory( int ) ) ); connect( database, SIGNAL( categoryModified( const Element & ) ), SLOT( modifyCategory( const Element & ) ) ); connect( database, SIGNAL( categoryModified( int, int ) ), SLOT( modifyCategory( int, int ) ) ); connect( database, SIGNAL( categoriesMerged( int, int ) ), 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(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::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::iterator parent_item_it = items_map.find(parent_id); if ( parent_item_it != items_map.end() && dynamic_cast(*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(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, SLOT( createNew() ), CTRL + Key_C ); kpop->insertItem( il->loadIcon( "edit-delete", TDEIcon::NoGroup, 16 ), i18n( "&Delete" ), this, SLOT( remove () ), Key_Delete ); kpop->insertItem( il->loadIcon( "edit", TDEIcon::NoGroup, 16 ), i18n( "&Rename" ), this, SLOT( rename() ), CTRL + Key_R ); kpop->insertSeparator(); kpop->insertItem( il->loadIcon( "edit-cut", TDEIcon::NoGroup, 16 ), i18n( "Cu&t" ), this, SLOT( cut() ), CTRL + Key_X ); kpop->insertItem( il->loadIcon( "edit-paste", TDEIcon::NoGroup, 16 ), i18n( "&Paste" ), this, SLOT( paste() ), CTRL + Key_V ); kpop->insertItem( il->loadIcon( "edit-paste", TDEIcon::NoGroup, 16 ), i18n( "Paste as Subcategory" ), this, SLOT( pasteAsSub() ), CTRL + SHIFT + Key_V ); kpop->polish(); delete il; connect( kpop, SIGNAL( aboutToShow() ), SLOT( preparePopup() ) ); connect( this, SIGNAL( contextMenu( TDEListView *, TQListViewItem *, const TQPoint & ) ), SLOT( showPopup( TDEListView *, TQListViewItem *, const TQPoint & ) ) ); connect( this, SIGNAL( doubleClicked( TQListViewItem*, const TQPoint &, int ) ), SLOT( modCategory( TQListViewItem* ) ) ); connect( this, SIGNAL( itemRenamed ( TQListViewItem* ) ), SLOT( saveCategory( TQListViewItem* ) ) ); connect( this, SIGNAL( moved( TQListViewItem *, TQListViewItem *, TQListViewItem * ) ), 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( SIGNAL( moved( TQListViewItem *, TQListViewItem *, TQListViewItem * ) ) ); database->modCategory( cat_id, new_parent_id ); connect( this, SIGNAL( moved( TQListViewItem *, TQListViewItem *, TQListViewItem * ) ), 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::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"