diff options
author | Michele Calgaro <[email protected]> | 2024-10-13 11:56:14 +0900 |
---|---|---|
committer | Michele Calgaro <[email protected]> | 2024-10-21 09:29:11 +0900 |
commit | 0c8ed6c9a4000af8f48581a81c4b5c2f5b9fd502 (patch) | |
tree | 10f9d3223f0a0904a0748a28ca44da52ee1092b7 /src/widgets | |
parent | 7d5ba3180a82a0827c1fbd6dc93a2abf4f882c37 (diff) | |
download | krecipes-0c8ed6c9a4000af8f48581a81c4b5c2f5b9fd502.tar.gz krecipes-0c8ed6c9a4000af8f48581a81c4b5c2f5b9fd502.zip |
Rearrange folders structure to remove unnecessary 'krecipes' second level subfolder
Signed-off-by: Michele Calgaro <[email protected]>
Diffstat (limited to 'src/widgets')
62 files changed, 11630 insertions, 0 deletions
diff --git a/src/widgets/Makefile.am b/src/widgets/Makefile.am new file mode 100644 index 0000000..b92e241 --- /dev/null +++ b/src/widgets/Makefile.am @@ -0,0 +1,28 @@ +## Makefile.am for krecipes + +# this is the program that gets installed. it's name is used for all +# of the other Makefile.am variables + +# set the include path for X, tqt and TDE +INCLUDES = -I$(srcdir)/.. $(all_includes) + +noinst_LTLIBRARIES=libkrecipeswidgets.la + +libkrecipeswidgets_la_SOURCES= \ + krelistview.cpp kremenu.cpp \ + paneldeco.cpp ingredientlistview.cpp unitlistview.cpp \ + propertylistview.cpp prepmethodlistview.cpp categorylistview.cpp \ + authorlistview.cpp recipelistview.cpp categorycombobox.cpp \ + kretextedit.cpp dblistviewbase.cpp \ + conversiontable.cpp fractioninput.cpp ingredientcombobox.cpp \ + headercombobox.cpp prepmethodcombobox.cpp \ + inglistviewitem.cpp kdateedit.cpp kdatepickerpopup.cpp \ + headerlistview.cpp ratingwidget.cpp kwidgetlistbox.cpp \ + ratingdisplaywidget.ui criteriacombobox.cpp ingredientinputwidget.cpp \ + unitcombobox.cpp amountunitinput.cpp weightinput.cpp + + +libkrecipeswidgets_la_METASOURCES=AUTO + +#the library search path. +libkrecipeswidgets_la_LDFLAGS = $(KDE_RPATH) $(all_libraries) diff --git a/src/widgets/amountunitinput.cpp b/src/widgets/amountunitinput.cpp new file mode 100644 index 0000000..699f76d --- /dev/null +++ b/src/widgets/amountunitinput.cpp @@ -0,0 +1,68 @@ +/*************************************************************************** +* Copyright (C) 2006 by * +* 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 "amountunitinput.h" + +#include <tqheader.h> +#include <tqlistview.h> + +#include "fractioninput.h" +#include "unitcombobox.h" +#include "backends/recipedb.h" +#include "datablocks/mixednumber.h" + +AmountUnitInput::AmountUnitInput( TQWidget *parent, RecipeDB *database, Unit::Type type, MixedNumber::Format format ) : TQHBox(parent), + m_item(0), m_database(database) +{ + amountInput = new FractionInput(this,format); + unitBox = new UnitComboBox(this,database,type); + unitBox->reload(); + + connect( amountInput, TQ_SIGNAL(valueChanged(const MixedNumber &)), TQ_SLOT(emitValueChanged()) ); + connect( unitBox, TQ_SIGNAL(activated(int)), TQ_SLOT(emitValueChanged()) ); + connect( amountInput, TQ_SIGNAL(returnPressed()), TQ_SIGNAL(doneEditing()) ); +} + +void AmountUnitInput::emitValueChanged() +{ + emit valueChanged( amount(), unit() ); +} + +void AmountUnitInput::setAmount( const MixedNumber &amount ) +{ + amountInput->disconnect( this ); + if ( amount.toDouble() < 0 ) + amountInput->clear(); + else + amountInput->setValue( amount, 0 ); + connect( amountInput, TQ_SIGNAL(valueChanged(const MixedNumber &)), TQ_SLOT(emitValueChanged()) ); +} + +void AmountUnitInput::setUnit( const Unit &unit ) +{ + if ( unit.id == -1 ) + unitBox->setCurrentItem(0); + else + unitBox->setSelected( unit.id ); + +} + +MixedNumber AmountUnitInput::amount() const +{ + return amountInput->value(); +} + +Unit AmountUnitInput::unit() const +{ + //TODO Potential for optimization here... avoid the database call + return m_database->unitName( unitBox->id( unitBox->currentItem() ) ); +} + +#include "amountunitinput.moc" diff --git a/src/widgets/amountunitinput.h b/src/widgets/amountunitinput.h new file mode 100644 index 0000000..911e67c --- /dev/null +++ b/src/widgets/amountunitinput.h @@ -0,0 +1,59 @@ +/*************************************************************************** +* Copyright (C) 2003 by * +* Unai Garro ([email protected]) * +* * +* Copyright (C) 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. * +***************************************************************************/ + +#ifndef AMOUNTUNITINPUT_H +#define AMOUNTUNITINPUT_H + +#include <tqhbox.h> + +#include "datablocks/unit.h" +#include "datablocks/mixednumber.h" + +class TQListViewItem; + +class RecipeDB; +class FractionInput; +class UnitComboBox; + +class AmountUnitInput : public TQHBox +{ +TQ_OBJECT + +public: + AmountUnitInput( TQWidget *parent, RecipeDB *database, Unit::Type type = Unit::All, MixedNumber::Format f = MixedNumber::MixedNumberFormat ); + + void setAmount( const MixedNumber &amount ); + void setUnit( const Unit &unit ); + + MixedNumber amount() const; + Unit unit() const; + TQListViewItem *item() const { return m_item; } + void setItem( TQListViewItem *it ){ m_item = it; } + + void insertIntoListview( TQListViewItem *it, int col ); + +public slots: + void emitValueChanged(); + +signals: + void valueChanged( const MixedNumber &, const Unit &unit ); + void doneEditing(); + +private: + FractionInput *amountInput; + UnitComboBox *unitBox; + + TQListViewItem *m_item; + + RecipeDB *m_database; +}; +#endif diff --git a/src/widgets/authorlistview.cpp b/src/widgets/authorlistview.cpp new file mode 100644 index 0000000..7ef696d --- /dev/null +++ b/src/widgets/authorlistview.cpp @@ -0,0 +1,275 @@ +/*************************************************************************** +* Copyright (C) 2004 by * +* 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 "authorlistview.h" + +#include <tdemessagebox.h> +#include <tdeconfig.h> +#include <tdelocale.h> +#include <tdeglobal.h> +#include <kiconloader.h> +#include <tdepopupmenu.h> + +#include "backends/recipedb.h" +#include "dialogs/createelementdialog.h" +#include "dialogs/dependanciesdialog.h" + +AuthorListView::AuthorListView( TQWidget *parent, RecipeDB *db ) : DBListViewBase( parent, db, db->authorCount() ) +{ + setAllColumnsShowFocus( true ); + setDefaultRenameAction( TQListView::Reject ); +} + +void AuthorListView::init() +{ + connect( database, TQ_SIGNAL( authorCreated( const Element & ) ), TQ_SLOT( checkCreateAuthor( const Element & ) ) ); + connect( database, TQ_SIGNAL( authorRemoved( int ) ), TQ_SLOT( removeAuthor( int ) ) ); +} + +void AuthorListView::load( int limit, int offset ) +{ + ElementList authorList; + database->loadAuthors( &authorList, limit, offset ); + + setTotalItems(authorList.count()); + + for ( ElementList::const_iterator ing_it = authorList.begin(); ing_it != authorList.end(); ++ing_it ) + createAuthor( *ing_it ); +} + +void AuthorListView::checkCreateAuthor( const Element &el ) +{ + if ( handleElement(el.name) ) { //only create this author if the base class okays it + createAuthor(el); + } +} + + +StdAuthorListView::StdAuthorListView( TQWidget *parent, RecipeDB *db, bool editable ) : AuthorListView( parent, db ) +{ + addColumn( i18n( "Author" ) ); + + 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 ); + + 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_N ); + 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->polish(); + + delete il; + + connect( this, TQ_SIGNAL( contextMenu( TDEListView *, TQListViewItem *, const TQPoint & ) ), TQ_SLOT( showPopup( TDEListView *, TQListViewItem *, const TQPoint & ) ) ); + connect( this, TQ_SIGNAL( doubleClicked( TQListViewItem* ) ), this, TQ_SLOT( modAuthor( TQListViewItem* ) ) ); + connect( this, TQ_SIGNAL( itemRenamed( TQListViewItem* ) ), this, TQ_SLOT( saveAuthor( TQListViewItem* ) ) ); + } +} + +void StdAuthorListView::showPopup( TDEListView * /*l*/, TQListViewItem *i, const TQPoint &p ) +{ + if ( i ) + kpop->exec( p ); +} + +void StdAuthorListView::createNew() +{ + CreateElementDialog * elementDialog = new CreateElementDialog( this, i18n( "New Author" ) ); + + if ( elementDialog->exec() == TQDialog::Accepted ) { + TQString result = elementDialog->newElementName(); + + //check bounds first + if ( checkBounds( result ) ) + database->createNewAuthor( result ); // Create the new author in the database + } +} + +void StdAuthorListView::remove + () +{ + TQListViewItem * item = currentItem(); + + if ( item ) { + int id = item->text( 1 ).toInt(); + + ElementList recipeDependancies; + database->findUseOfAuthorInRecipes( &recipeDependancies, id ); + + if ( recipeDependancies.isEmpty() ) { + switch ( KMessageBox::warningContinueCancel( this, i18n( "Are you sure you want to delete this author?" ) ) ) { + case KMessageBox::Continue: + database->removeAuthor( 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->removeAuthor( id ); + } + } +} + +void StdAuthorListView::rename() +{ + TQListViewItem * item = currentItem(); + + if ( item ) + AuthorListView::rename( item, 0 ); +} + +void StdAuthorListView::createAuthor( const Element &author ) +{ + createElement(new TQListViewItem( this, author.name, TQString::number( author.id ) )); +} + +void StdAuthorListView::removeAuthor( int id ) +{ + TQListViewItem * item = findItem( TQString::number( id ), 1 ); + removeElement(item); +} + +void StdAuthorListView::modAuthor( TQListViewItem* i ) +{ + if ( i ) + AuthorListView::rename( i, 0 ); +} + +void StdAuthorListView::saveAuthor( TQListViewItem* i ) +{ + if ( !checkBounds( i->text( 0 ) ) ) { + reload(ForceReload); //reset the changed text + return ; + } + + int existing_id = database->findExistingAuthorByName( i->text( 0 ) ); + int author_id = i->text( 1 ).toInt(); + if ( existing_id != -1 && existing_id != author_id ) //category already exists with this label... merge the two + { + switch ( KMessageBox::warningContinueCancel( this, i18n( "This author already exists. Continuing will merge these two authors into one. Are you sure?" ) ) ) + { + case KMessageBox::Continue: { + database->mergeAuthors( existing_id, author_id ); + break; + } + default: + reload(ForceReload); + break; + } + } + else { + database->modAuthor( ( i->text( 1 ) ).toInt(), i->text( 0 ) ); + } +} + +bool StdAuthorListView::checkBounds( const TQString &name ) +{ + if ( name.length() > uint(database->maxAuthorNameLength()) ) { + KMessageBox::error( this, TQString( i18n( "Author name cannot be longer than %1 characters." ) ).arg( database->maxAuthorNameLength() ) ); + return false; + } + + return true; +} + + +AuthorCheckListItem::AuthorCheckListItem( AuthorCheckListView* qlv, const Element &author ) : TQCheckListItem( qlv, TQString::null, TQCheckListItem::CheckBox ), + authorStored(author), + m_listview(qlv) +{ +} + +AuthorCheckListItem::AuthorCheckListItem( AuthorCheckListView* qlv, TQListViewItem *after, const Element &author ) : TQCheckListItem( qlv, after, TQString::null, TQCheckListItem::CheckBox ), + authorStored(author), + m_listview(qlv) +{ +} + +Element AuthorCheckListItem::author() const +{ + return authorStored; +} + +TQString AuthorCheckListItem::text( int column ) const +{ + switch ( column ) { + case 0: + return ( authorStored.name ); + case 1: + return ( TQString::number( authorStored.id ) ); + default: + return TQString::null; + } +} + +void AuthorCheckListItem::stateChange( bool on ) +{ + m_listview->stateChange(this,on); +} + + +AuthorCheckListView::AuthorCheckListView( TQWidget *parent, RecipeDB *db ) : AuthorListView( parent, db ) +{ + addColumn( i18n( "Author" ) ); + + TDEConfig *config = TDEGlobal::config(); + config->setGroup( "Advanced" ); + bool show_id = config->readBoolEntry( "ShowID", false ); + addColumn( i18n( "Id" ), show_id ? -1 : 0 ); +} + +void AuthorCheckListView::createAuthor( const Element &author ) +{ + createElement(new AuthorCheckListItem( this, author )); +} + +void AuthorCheckListView::removeAuthor( int id ) +{ + TQListViewItem * item = findItem( TQString::number( id ), 1 ); + removeElement(item); +} + +void AuthorCheckListView::load( int limit, int offset ) +{ + AuthorListView::load(limit,offset); + + for ( TQValueList<Element>::const_iterator author_it = m_selections.begin(); author_it != m_selections.end(); ++author_it ) { + TQCheckListItem * item = ( TQCheckListItem* ) findItem( TQString::number( (*author_it).id ), 1 ); + if ( item ) { + item->setOn(true); + } + } +} + +void AuthorCheckListView::stateChange(AuthorCheckListItem *it,bool on) +{ + if ( !reloading() ) { + if ( on ) + m_selections.append(it->author()); + else + m_selections.remove(it->author()); + } +} + +#include "authorlistview.moc" diff --git a/src/widgets/authorlistview.h b/src/widgets/authorlistview.h new file mode 100644 index 0000000..a1cdc49 --- /dev/null +++ b/src/widgets/authorlistview.h @@ -0,0 +1,107 @@ +/*************************************************************************** +* 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. * +***************************************************************************/ + +#ifndef AUTHORLISTVIEW_H +#define AUTHORLISTVIEW_H + +#include "dblistviewbase.h" +#include "datablocks/element.h" + +class RecipeDB; +class TDEPopupMenu; + +class AuthorCheckListView; + +class AuthorCheckListItem: public TQCheckListItem +{ +public: + AuthorCheckListItem( AuthorCheckListView* qlv, const Element &author ); + AuthorCheckListItem( AuthorCheckListView* qlv, TQListViewItem *after, const Element &author ); + + Element author() const; + + virtual TQString text( int column ) const; + +protected: + virtual void stateChange( bool on ); + +private: + Element authorStored; + AuthorCheckListView *m_listview; +}; + + +class AuthorListView : public DBListViewBase +{ + TQ_OBJECT + +public: + AuthorListView( TQWidget *parent, RecipeDB *db ); + +protected slots: + void checkCreateAuthor( const Element &el ); + virtual void createAuthor( const Element & ) = 0; + virtual void removeAuthor( int ) = 0; + virtual void load( int curr_limit, int curr_offset ); + +protected: + virtual void init(); +}; + +class StdAuthorListView : public AuthorListView +{ + TQ_OBJECT + +public: + StdAuthorListView( TQWidget *parent, RecipeDB *db, bool editable = false ); + +protected: + virtual void createAuthor( const Element & ); + virtual void removeAuthor( int ); + +private slots: + void showPopup( TDEListView *, TQListViewItem *, const TQPoint & ); + + void createNew(); + void remove + (); + void rename(); + + void modAuthor( TQListViewItem* i ); + void saveAuthor( TQListViewItem* i ); + +private: + bool checkBounds( const TQString &name ); + + TDEPopupMenu *kpop; +}; + + +class AuthorCheckListView : public AuthorListView +{ +public: + AuthorCheckListView( TQWidget *parent, RecipeDB *db ); + + virtual void stateChange(AuthorCheckListItem *,bool); + + TQValueList<Element> selections() const{ return m_selections; } + +protected: + virtual void createAuthor( const Element &ing ); + virtual void removeAuthor( int ); + + virtual void load( int limit, int offset ); + +private: + TQValueList<Element> m_selections; +}; + +#endif //AUTHORLISTVIEW_H diff --git a/src/widgets/categorycombobox.cpp b/src/widgets/categorycombobox.cpp new file mode 100644 index 0000000..7935cbb --- /dev/null +++ b/src/widgets/categorycombobox.cpp @@ -0,0 +1,182 @@ +/*************************************************************************** +* Copyright (C) 2003-2004 by * +* Unai Garro ([email protected]) * +* Cyril Bosselut ([email protected]) * +* 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 "categorycombobox.h" + +#include <tqlistbox.h> + +#include <tdelocale.h> +#include <tdeconfig.h> +#include <tdeglobal.h> + +#include "backends/recipedb.h" +#include "backends/progressinterface.h" +#include "datablocks/elementlist.h" +#include "datablocks/categorytree.h" + +CategoryComboBox::CategoryComboBox( TQWidget *parent, RecipeDB *db ) : KComboBox( parent ), + database( db ), + m_offset(0) +{ + connect( database, TQ_SIGNAL( categoryCreated( const Element &, int ) ), TQ_SLOT( createCategory( 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( categoriesMerged( int, int ) ), TQ_SLOT( mergeCategories( int, int ) ) ); + + // Insert default "All Categories" (row 0, which will be translated to -1 as category in the filtering process) + // the rest of the items are loaded when needed in order to significantly speed up startup + insertItem( i18n( "All Categories" ) ); +} + +void CategoryComboBox::popup() +{ + if ( count() == 1 ) + reload(); + KComboBox::popup(); +} + +void CategoryComboBox::reload() +{ + TQString remember_cat_filter = currentText(); + + TDEConfig * config = TDEGlobal::config();config->setGroup( "Performance" ); + int limit = config->readNumEntry( "CategoryLimit", -1 ); + + //ProgressInterface pi(this); + //pi.listenOn(database); + + CategoryTree categoryList; + database->loadCategories( &categoryList, limit, m_offset, -1 ); + + clear(); + categoryComboRows.clear(); + + // Insert default "All Categories" (row 0, which will be translated to -1 as category in the filtering process) + insertItem( i18n( "All Categories" ) ); + + //Now load the categories + int row = 1; + loadCategories(&categoryList,row); + + if ( listBox() ->findItem( remember_cat_filter, TQt::ExactMatch ) ) { + setCurrentText( remember_cat_filter ); + } +} + +void CategoryComboBox::loadCategories( CategoryTree *categoryTree, int &row ) +{ + for ( CategoryTree * child_it = categoryTree->firstChild(); child_it; child_it = child_it->nextSibling() ) { + insertItem( child_it->category.name ); + categoryComboRows.insert( row, child_it->category.id ); // store category id's in the combobox position to obtain the category id later + row++; + loadCategories( child_it, row ); + } +} + +void CategoryComboBox::loadNextGroup() +{ + TDEConfig * config = TDEGlobal::config();config->setGroup( "Performance" ); + int limit = config->readNumEntry( "CategoryLimit", -1 ); + + m_offset += limit; + + reload(); +} + +void CategoryComboBox::loadPrevGroup() +{ + TDEConfig * config = TDEGlobal::config();config->setGroup( "Performance" ); + int limit = config->readNumEntry( "CategoryLimit", -1 ); + + m_offset -= limit; + + reload(); +} + +int CategoryComboBox::id( int row ) +{ + if ( row ) + return categoryComboRows[ row ]; + else + return -1; // No category filtering +} + +void CategoryComboBox::createCategory( const Element &element, int /*parent_id*/ ) +{ + int row = findInsertionPoint( element.name ); + + insertItem( element.name, row ); + + //now update the map by pushing everything after this item down + TQMap<int, int> new_map; + for ( TQMap<int, int>::iterator it = categoryComboRows.begin(); it != categoryComboRows.end(); ++it ) { + if ( it.key() >= row ) { + new_map.insert( it.key() + 1, it.data() ); + } + else + new_map.insert( it.key(), it.data() ); + } + categoryComboRows = new_map; + categoryComboRows.insert( row, element.id ); +} + +void CategoryComboBox::removeCategory( int id ) +{ + int row = -1; + for ( TQMap<int, int>::iterator it = categoryComboRows.begin(); it != categoryComboRows.end(); ++it ) { + if ( it.data() == id ) { + row = it.key(); + removeItem( row ); + categoryComboRows.remove( it ); + break; + } + } + + if ( row == -1 ) + return ; + + //now update the map by pushing everything after this item up + TQMap<int, int> new_map; + for ( TQMap<int, int>::iterator it = categoryComboRows.begin(); it != categoryComboRows.end(); ++it ) { + if ( it.key() > row ) { + new_map.insert( it.key() - 1, it.data() ); + } + else + new_map.insert( it.key(), it.data() ); + } + categoryComboRows = new_map; +} + +void CategoryComboBox::modifyCategory( const Element &element ) +{ + for ( TQMap<int, int>::const_iterator it = categoryComboRows.begin(); it != categoryComboRows.end(); ++it ) { + if ( it.data() == element.id ) + changeItem( element.name, it.key() ); + } +} + +void CategoryComboBox::mergeCategories( int /*to_id*/, int from_id ) +{ + removeCategory( from_id ); +} + +int CategoryComboBox::findInsertionPoint( const TQString &name ) +{ + for ( int i = 1; i < count(); i++ ) { + if ( TQString::localeAwareCompare( name, text( i ) ) < 0 ) + return i; + } + + return count(); +} + +#include "categorycombobox.moc" diff --git a/src/widgets/categorycombobox.h b/src/widgets/categorycombobox.h new file mode 100644 index 0000000..4cc99aa --- /dev/null +++ b/src/widgets/categorycombobox.h @@ -0,0 +1,59 @@ +/*************************************************************************** +* Copyright (C) 2003 by * +* Unai Garro ([email protected]) * +* Cyril Bosselut ([email protected]) * +* 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. * +***************************************************************************/ + +#ifndef CATEGORYCOMBOBOX_H +#define CATEGORYCOMBOBOX_H + +#include <kcombobox.h> + +#include <tqmap.h> + +#include "datablocks/element.h" + +class RecipeDB; +class CategoryTree; + +class CategoryComboBox : public KComboBox +{ + TQ_OBJECT + +public: + CategoryComboBox( TQWidget *parent, RecipeDB *db ); + + void reload(); + int id( int row ); + +public slots: + void loadNextGroup(); + void loadPrevGroup(); + +protected: + virtual void popup(); + +private slots: + void createCategory( const Element &element, int /*parent_id*/ ); + void removeCategory( int id ); + void modifyCategory( const Element &element ); + void mergeCategories( int /*to_id*/, int from_id ); + + int findInsertionPoint( const TQString &name ); + +private: + void loadCategories( CategoryTree *categoryList, int &row ); + + RecipeDB *database; + TQMap<int, int> categoryComboRows; // Contains the category id for every given row in the category combobox + int m_offset; +}; + +#endif //CATEGORYCOMBOBOX_H + 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" diff --git a/src/widgets/categorylistview.h b/src/widgets/categorylistview.h new file mode 100644 index 0000000..1dcabba --- /dev/null +++ b/src/widgets/categorylistview.h @@ -0,0 +1,288 @@ +/*************************************************************************** +* 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. * +***************************************************************************/ + +#ifndef CATEGORYLISTVIEW_H +#define CATEGORYLISTVIEW_H + +#include <tqmap.h> +#include <tqpixmap.h> + +#include "dblistviewbase.h" + +#include "datablocks/elementlist.h" + +class TDEPopupMenu; + +class RecipeDB; +class CategoryTree; +class CategoryCheckListView; + +#define CATEGORYCHECKLISTITEM_RTTI 1005 +#define CATEGORYLISTITEM_RTTI 1001 +#define PSEUDOLISTITEM_RTTI 1008 + +/** Category listitems inherit this class to provide a common interface for accessing this information. + */ +class CategoryItemInfo +{ +public: + CategoryItemInfo( const Element &category ) : ctyStored( category ), populated(false){} + bool isPopulated() const { return populated; } + void setPopulated( bool b ){ populated = b; } + + Element element() const + { + return ctyStored; + } + + int categoryId() const + { + return ctyStored.id; + } + TQString categoryName() const + { + return ctyStored.name; + } + +protected: + Element ctyStored; + +private: + bool populated; +}; + +class CategoryCheckListItem : public TQCheckListItem, public CategoryItemInfo +{ +public: + CategoryCheckListItem( CategoryCheckListView* klv, const Element &category, bool exclusive = true ); + CategoryCheckListItem( TQListViewItem* it, const Element &category, bool exclusive = true ); + CategoryCheckListItem( CategoryCheckListView* klv, TQListViewItem* it, const Element &category, bool exclusive = true ); + + virtual TQString text( int column ) const; + virtual void setText( int column, const TQString &text ); + + int rtti() const + { + return CATEGORYCHECKLISTITEM_RTTI; + } + +protected: + virtual void stateChange( bool ); + void setChildrenState( bool ); + void setParentsState( bool ); + + bool locked; + bool exclusive; + +private: + CategoryCheckListView *m_listview; +}; + + +class CategoryListItem : public TQListViewItem, public CategoryItemInfo +{ +public: + CategoryListItem( TQListView* klv, const Element &category ); + CategoryListItem( TQListViewItem* it, const Element &category ); + CategoryListItem( TQListView* klv, TQListViewItem* it, const Element &category ); + + virtual TQString text( int column ) const; + virtual void setText( int column, const TQString &text ); + + int rtti() const + { + return CATEGORYLISTITEM_RTTI; + } +}; + + + +class CategoryListView : public DBListViewBase +{ + TQ_OBJECT + +public: + CategoryListView( TQWidget *parent, RecipeDB * ); + + void populateAll( TQListViewItem *parent = 0 ); + +public slots: + void open( TQListViewItem *item ); + +protected: + virtual void init(); + + virtual void load( int limit, int offset ); + + /** so that it allows dropping into + * subchildren that aren't expandable. The code is taken from TDE's TDEListView with + * one line commented out. + */ + void findDrop( const TQPoint &pos, TQListViewItem *&parent, TQListViewItem *&after ) + { + TQPoint p ( contentsToViewport( pos ) ); + + // Get the position to put it in + TQListViewItem *atpos = itemAt( p ); + + TQListViewItem *above; + if ( !atpos ) // put it at the end + above = lastItem(); + else { + // Get the closest item before us ('atpos' or the one above, if any) + if ( p.y() - itemRect( atpos ).topLeft().y() < ( atpos->height() / 2 ) ) + above = atpos->itemAbove(); + else + above = atpos; + } + + if ( above ) { + // if above has children, I might need to drop it as the first item there + + if ( above->firstChild() && above->isOpen() ) { + parent = above; + after = 0; + return ; + } + + // Now, we know we want to go after "above". But as a child or as a sibling ? + // We have to ask the "above" item if it accepts children. + // ### NOTE: Here is the one line commented out so that "above" always accepts children + //if (above->isExpandable()) + { + // The mouse is sufficiently on the right ? - doesn't matter if 'above' has visible children + if ( p.x() >= depthToPixels( above->depth() + 1 ) || + ( above->isOpen() && above->childCount() > 0 ) ) + { + parent = above; + after = 0L; + return ; + } + } + + // Ok, there's one more level of complexity. We may want to become a new + // sibling, but of an upper-level group, rather than the "above" item + TQListViewItem * betterAbove = above->parent(); + TQListViewItem * last = above; + while ( betterAbove ) { + // We are allowed to become a sibling of "betterAbove" only if we are + // after its last child + if ( last->nextSibling() == 0 ) { + if ( p.x() < depthToPixels ( betterAbove->depth() + 1 ) ) + above = betterAbove; // store this one, but don't stop yet, there may be a better one + else + break; // not enough on the left, so stop + last = betterAbove; + betterAbove = betterAbove->parent(); // up one level + } + else + break; // we're among the child of betterAbove, not after the last one + } + } + // set as sibling + after = above; + parent = after ? after->parent() : 0L ; + } + +protected slots: + virtual void removeCategory( int id ) = 0; + virtual void createCategory( const Element &category, int parent_id ) = 0; + virtual void modifyCategory( const Element &category ); + virtual void modifyCategory( int id, int parent_id ); + virtual void mergeCategories( int id1, int id2 ); + + virtual void checkCreateCategory( const Element &, int parent_id ); + virtual void populate( TQListViewItem *item ); + + TQMap<int, TQListViewItem*> items_map; + +private: + TQListViewItem *m_item_to_delete; +}; + + +class StdCategoryListView : public CategoryListView +{ + TQ_OBJECT + +public: + StdCategoryListView( TQWidget *parent, RecipeDB *, bool editable = false ); + ~StdCategoryListView(); + +protected: + virtual void removeCategory( int id ); + virtual void createCategory( const Element &category, int parent_id ); + + void setPixmap( const TQPixmap &pixmap ); + +private slots: + void preparePopup(); + void showPopup( TDEListView *, TQListViewItem *, const TQPoint & ); + + void createNew(); + void remove + (); + void rename(); + void cut(); + void paste(); + void pasteAsSub(); + + void changeCategoryParent( TQListViewItem *item, TQListViewItem * /*afterFirst*/, TQListViewItem * /*afterNow*/ ); + + void modCategory( TQListViewItem* i ); + void saveCategory( TQListViewItem* i ); + +private: + bool checkBounds( const TQString &name ); + + TDEPopupMenu *kpop; + TQListViewItem *clipboard_item; + TQListViewItem *clipboard_parent; + + TQPixmap m_folder_icon; +}; + + +class CategoryCheckListView : public CategoryListView +{ + TQ_OBJECT + +public: + CategoryCheckListView( TQWidget *parent, RecipeDB *, bool exclusive=true, const ElementList &init_items_checked = ElementList() ); + + virtual void stateChange( CategoryCheckListItem*, bool ); + + ElementList selections() const{ return m_selections; } + +protected: + virtual void removeCategory( int id ); + virtual void createCategory( const Element &category, int parent_id ); + + virtual void load( int limit, int offset ); + + bool exclusive; + +private: + ElementList m_selections; +}; + + +class PseudoListItem : public TQListViewItem +{ +public: + PseudoListItem( TQListView* lv ) : TQListViewItem(lv){} + PseudoListItem( TQListViewItem* it ) : TQListViewItem(it){} + +protected: + int rtti() const { return PSEUDOLISTITEM_RTTI; } +}; + +#endif //CATEGORYLISTVIEW_H diff --git a/src/widgets/conversiontable.cpp b/src/widgets/conversiontable.cpp new file mode 100644 index 0000000..a48b67e --- /dev/null +++ b/src/widgets/conversiontable.cpp @@ -0,0 +1,426 @@ +/*************************************************************************** +* Copyright (C) 2003-2004 by * +* Unai Garro ([email protected]) * +* * +* Copyright (C) 2003-2006 by * +* 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 "conversiontable.h" +#include "datablocks/mixednumber.h" +#include "widgets/fractioninput.h" + +#include <tqtooltip.h> + +#include <tdeglobal.h> +#include <tdelocale.h> + +class ConversionTableToolTip : public TQToolTip +{ +public: + ConversionTableToolTip( ConversionTable *t ) : TQToolTip( t->viewport() ), + table( t ) + {} + + void maybeTip( const TQPoint &pos ) + { + if ( !table ) + return ; + + TQPoint cp = table->viewportToContents( pos ); + + int row = table->rowAt( cp.y() ); + int col = table->columnAt( cp.x() ); + + if ( row == col ) + return ; + + TQString row_unit = table->verticalHeader() ->label( row ); + TQString col_unit = table->horizontalHeader() ->label( col ); + TQString text = table->text( row, col ); + if ( text.isEmpty() ) + text = "X"; //### Is this i18n friendly??? + + TQRect cr = table->cellGeometry( row, col ); + cr.moveTopLeft( table->contentsToViewport( cr.topLeft() ) ); + tip( cr, TQString( "1 %1 = %2 %3" ).arg( row_unit ).arg( text ).arg( col_unit ) ); + } + +private: + ConversionTable *table; +}; + +ConversionTable::ConversionTable( TQWidget* parent, int maxrows, int maxcols ) : TQTable( maxrows, maxcols, parent, "table" ) +{ + editBoxValue = -1; + items.setAutoDelete( true ); + widgets.setAutoDelete( true ); + + ( void ) new ConversionTableToolTip( this ); +} + +ConversionTable::~ConversionTable() +{} +#include <kdebug.h> +void ConversionTable::unitRemoved( int id ) +{ + int index = *unitIDs.find( id ); + kdDebug() << "index:" << index << endl; + removeRow( index ); + removeColumn( index ); + kdDebug() << "done" << endl; +} + +void ConversionTable::unitCreated( const Unit &unit ) +{ + insertColumns( numCols() ); + insertRows( numRows() ); + unitIDs.append( unit.id ); + horizontalHeader() ->setLabel( numRows() - 1, unit.name ); + verticalHeader() ->setLabel( numCols() - 1, unit.name ); +} + +TQTableItem* ConversionTable::item( int r, int c ) const +{ + return items.find( indexOf( r, c ) ); +} + +void ConversionTable::setItem( int r, int c, TQTableItem *i ) +{ + items.replace( indexOf( r, c ), i ); + i->setRow( r ); // Otherwise the item + i->setCol( c ); //doesn't know where it is! + updateCell( r, c ); +} + +void ConversionTable::clearCell( int r, int c ) +{ + items.remove( indexOf( r, c ) ); +} + +void ConversionTable::takeItem( TQTableItem *item ) +{ + items.setAutoDelete( false ); + items.remove( indexOf( item->row(), item->col() ) ); + items.setAutoDelete( true ); +} + +void ConversionTable::insertWidget( int r, int c, TQWidget *w ) +{ + widgets.replace( indexOf( r, c ), w ); +} + +TQWidget* ConversionTable::cellWidget( int r, int c ) const +{ + return widgets.find( indexOf( r, c ) ); +} + +void ConversionTable::clearCellWidget( int r, int c ) +{ + TQWidget * w = widgets.take( indexOf( r, c ) ); + if ( w ) + w->deleteLater(); +} + + +ConversionTableItem::ConversionTableItem( TQTable *t, EditType et ) : TQTableItem( t, et, TQString::null ) +{ + //�we�do�not�want�this�item�to�be�replaced + setReplaceable( false ); +} + +void ConversionTableItem::paint( TQPainter *p, const TQColorGroup &cg, const TQRect &cr, bool selected ) +{ + TQColorGroup g( cg ); + + // Draw in gray all those cells which are not editable + + if ( row() == col() ) + g.setColor( TQColorGroup::Base, gray ); + TQTableItem::paint( p, g, cr, selected ); +} + +TQWidget* ConversionTableItem::createEditor() const +{ + FractionInput *editor = new FractionInput( table()->viewport(), MixedNumber::DecimalFormat ); + + MixedNumber current = MixedNumber::fromString(text()); + if ( current.toDouble() > 1e-8 ) + editor->setValue( current, 0 ); + + return editor; +} + +void ConversionTableItem::setContentFromEditor( TQWidget *w ) +{ + // the�user changed the value of the combobox, so synchronize the + // value of the item (its text), with the value of the combobox + if ( w->inherits( "FractionInput" ) ) { + FractionInput* editor = ( FractionInput* ) w; + if ( editor->isInputValid() && !editor->isEmpty() && editor->value().toDouble() > 1e-6 ) { + setText( editor->value().toString(MixedNumber::DecimalFormat) ); + emit ratioChanged( row(), col(), editor->value().toDouble() ); // Signal to store + } + else { + setText( TQString::null ); + emit ratioRemoved( row(), col() ); + } + } + else + TQTableItem::setContentFromEditor( w ); +} + +void ConversionTableItem::setText( const TQString &s ) +{ + TQTableItem::setText( s ); +} +TQString ConversionTable::text( int r, int c ) const // without this function, the usual (text(r,c)) won't work +{ + if ( item( r, c ) ) + return item( r, c ) ->text(); //Note that item(r,c) was reimplemented here for large sparse tables... + else + return TQString::null; +} + +void ConversionTable::initTable() +{ + + for ( int r = 0;r < numRows();r++ ) { + this->createNewItem( r, r, 1.0 ); + item( r, r ) ->setEnabled( false ); // Diagonal is not editable + } +} + +void ConversionTable::createNewItem( int r, int c, double amount ) +{ + + ConversionTableItem * ci = new ConversionTableItem( this, TQTableItem::WhenCurrent ); + ci->setText( beautify( TDEGlobal::locale() ->formatNumber( amount, 5 ) ) ); + setItem( r, c, ci ); + // connect signal (forward) to know when it's actually changed + connect( ci, TQ_SIGNAL( ratioChanged( int, int, double ) ), this, TQ_SIGNAL( ratioChanged( int, int, double ) ) ); + connect( ci, TQ_SIGNAL( ratioRemoved( int, int ) ), this, TQ_SIGNAL( ratioRemoved( int, int ) ) ); + connect( ci, TQ_SIGNAL( signalRepaintCell( int, int ) ), this, TQ_SLOT( repaintCell( int, int ) ) ); +} + +void ConversionTable::setUnitIDs( const IDList &idList ) +{ + unitIDs = idList; +} + +void ConversionTable::setRatio( int ingID1, int ingID2, double ratio ) +{ + int indexID1 = unitIDs.findIndex( ingID1 ); + int indexID2 = unitIDs.findIndex( ingID2 ); + + createNewItem( indexID1, indexID2, ratio ); +} + + +int ConversionTable::getUnitID( int rc ) +{ + return ( *( unitIDs.at( rc ) ) ); +} + +TQWidget * ConversionTable::beginEdit ( int row, int col, bool replace ) +{ + // If there's no item, create it first. + if ( !item( row, col ) ) { + createNewItem( row, col, 0 ); + } + + // Then call normal beginEdit + return TQTable::beginEdit( row, col, replace ); +} + +void ConversionTableItem::setTextAndSave( const TQString &s ) +{ + setText( s ); // Change text + emit signalRepaintCell( row(), col() ); // Indicate to update the cell to the table. Otherwise it's not repainted + emit ratioChanged( row(), col(), s.toDouble() ); // Signal to store +} + +void ConversionTable::repaintCell( int r, int c ) +{ + TQTable::updateCell( r, c ); +} + +void ConversionTable::resize( int r, int c ) +{ + setNumRows( r ); + setNumCols( c ); + initTable(); +} + +void ConversionTable::clear( void ) +{ + items.clear(); + widgets.clear(); + unitIDs.clear(); + resize( 0, 0 ); + +} + +//TODO this is incomplete/wrong +void ConversionTable::swapRows( int row1, int row2, bool /*swapHeader*/ ) +{ + //if ( swapHeader ) + //((TQTableHeader*)verticalHeader())->swapSections( row1, row2, FALSE ); + + TQPtrVector<TQTableItem> tmpContents; + tmpContents.resize( numCols() ); + TQPtrVector<TQWidget> tmpWidgets; + tmpWidgets.resize( numCols() ); + int i; + + items.setAutoDelete( FALSE ); + widgets.setAutoDelete( FALSE ); + for ( i = 0; i < numCols(); ++i ) { + TQTableItem *i1, *i2; + i1 = item( row1, i ); + i2 = item( row2, i ); + if ( i1 || i2 ) { + tmpContents.insert( i, i1 ); + items.remove( indexOf( row1, i ) ); + items.insert( indexOf( row1, i ), i2 ); + items.remove( indexOf( row2, i ) ); + items.insert( indexOf( row2, i ), tmpContents[ i ] ); + if ( items[ indexOf( row1, i ) ] ) + items[ indexOf( row1, i ) ] ->setRow( row1 ); + if ( items[ indexOf( row2, i ) ] ) + items[ indexOf( row2, i ) ] ->setRow( row2 ); + } + + TQWidget *w1, *w2; + w1 = cellWidget( row1, i ); + w2 = cellWidget( row2, i ); + if ( w1 || w2 ) { + tmpWidgets.insert( i, w1 ); + widgets.remove( indexOf( row1, i ) ); + widgets.insert( indexOf( row1, i ), w2 ); + widgets.remove( indexOf( row2, i ) ); + widgets.insert( indexOf( row2, i ), tmpWidgets[ i ] ); + } + } + items.setAutoDelete( FALSE ); + widgets.setAutoDelete( TRUE ); + + //updateRowWidgets( row1 ); + //updateRowWidgets( row2 ); + /* + if ( curRow == row1 ) + curRow = row2; + else if ( curRow == row2 ) + curRow = row1; + if ( editRow == row1 ) + editRow = row2; + else if ( editRow == row2 ) + editRow = row1;*/ +} + +//TODO this is incomplete/wrong +void ConversionTable::swapColumns( int col1, int col2, bool /*swapHeader*/ ) +{ + //if ( swapHeader ) + //((TQTableHeader*)horizontalHeader())->swapSections( col1, col2, FALSE ); + + TQPtrVector<TQTableItem> tmpContents; + tmpContents.resize( numRows() ); + TQPtrVector<TQWidget> tmpWidgets; + tmpWidgets.resize( numRows() ); + int i; + + items.setAutoDelete( FALSE ); + widgets.setAutoDelete( FALSE ); + for ( i = 0; i < numRows(); ++i ) { + TQTableItem *i1, *i2; + i1 = item( i, col1 ); + i2 = item( i, col2 ); + if ( i1 || i2 ) { + tmpContents.insert( i, i1 ); + items.remove( indexOf( i, col1 ) ); + items.insert( indexOf( i, col1 ), i2 ); + items.remove( indexOf( i, col2 ) ); + items.insert( indexOf( i, col2 ), tmpContents[ i ] ); + if ( items[ indexOf( i, col1 ) ] ) + items[ indexOf( i, col1 ) ] ->setCol( col1 ); + if ( items[ indexOf( i, col2 ) ] ) + items[ indexOf( i, col2 ) ] ->setCol( col2 ); + } + + TQWidget *w1, *w2; + w1 = cellWidget( i, col1 ); + w2 = cellWidget( i, col2 ); + if ( w1 || w2 ) { + tmpWidgets.insert( i, w1 ); + widgets.remove( indexOf( i, col1 ) ); + widgets.insert( indexOf( i, col1 ), w2 ); + widgets.remove( indexOf( i, col2 ) ); + widgets.insert( indexOf( i, col2 ), tmpWidgets[ i ] ); + } + } + items.setAutoDelete( FALSE ); + widgets.setAutoDelete( TRUE ); + + columnWidthChanged( col1 ); + columnWidthChanged( col2 ); + /* + if ( curCol == col1 ) + curCol = col2; + else if ( curCol == col2 ) + curCol = col1; + if ( editCol == col1 ) + editCol = col2; + else if ( editCol == col2 ) + editCol = col1;*/ +} + +//TODO this is incomplete/wrong +void ConversionTable::swapCells( int row1, int col1, int row2, int col2 ) +{ + items.setAutoDelete( FALSE ); + widgets.setAutoDelete( FALSE ); + TQTableItem *i1, *i2; + i1 = item( row1, col1 ); + i2 = item( row2, col2 ); + if ( i1 || i2 ) { + TQTableItem * tmp = i1; + items.remove( indexOf( row1, col1 ) ); + items.insert( indexOf( row1, col1 ), i2 ); + items.remove( indexOf( row2, col2 ) ); + items.insert( indexOf( row2, col2 ), tmp ); + if ( items[ indexOf( row1, col1 ) ] ) { + items[ indexOf( row1, col1 ) ] ->setRow( row1 ); + items[ indexOf( row1, col1 ) ] ->setCol( col1 ); + } + if ( items[ indexOf( row2, col2 ) ] ) { + items[ indexOf( row2, col2 ) ] ->setRow( row2 ); + items[ indexOf( row2, col2 ) ] ->setCol( col2 ); + } + } + + TQWidget *w1, *w2; + w1 = cellWidget( row1, col1 ); + w2 = cellWidget( row2, col2 ); + if ( w1 || w2 ) { + TQWidget * tmp = w1; + widgets.remove( indexOf( row1, col1 ) ); + widgets.insert( indexOf( row1, col1 ), w2 ); + widgets.remove( indexOf( row2, col2 ) ); + widgets.insert( indexOf( row2, col2 ), tmp ); + } + + //updateRowWidgets( row1 ); + //updateRowWidgets( row2 ); + //updateColWidgets( col1 ); + //updateColWidgets( col2 ); + items.setAutoDelete( FALSE ); + widgets.setAutoDelete( TRUE ); +} + +#include "conversiontable.moc" diff --git a/src/widgets/conversiontable.h b/src/widgets/conversiontable.h new file mode 100644 index 0000000..16f761c --- /dev/null +++ b/src/widgets/conversiontable.h @@ -0,0 +1,101 @@ +/*************************************************************************** +* Copyright (C) 2003-2004 by * +* Unai Garro ([email protected]) * +* 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. * +***************************************************************************/ +#ifndef CONVERSIONTABLE_H +#define CONVERSIONTABLE_H +#include <tqstring.h> +#include <tqtable.h> +#include <tqobject.h> + +#include "datablocks/unitratio.h" +#include "datablocks/elementlist.h" +#include "datablocks/unit.h" + +/** +@author Unai Garro +*/ + + +class ConversionTable: public TQTable +{ + TQ_OBJECT +public: + + ConversionTable( TQWidget* parent, int maxrows, int maxcols ); + ~ConversionTable(); + void createNewItem( int r, int c, double amount ); + void setUnitIDs( const IDList &idList ); + void setRatio( int ingID1, int ingID2, double ratio ); + void setRatio( const UnitRatio &r ) + { + setRatio( r.uID1, r.uID2, r.ratio ); + } + int getUnitID( int rc ); + TQString text( int r, int c ) const; //Reimplement, otherwise it won't work this way + void resize( int r, int c ); + void clear( void ); +private: + + //Internal Variables + double editBoxValue; + TQIntDict<TQTableItem> items; + TQIntDict<TQWidget> widgets; + IDList unitIDs; // unit ID list to know the units by ID, not name + //Internal Methods + void resizeData( int ) + {} + ; + TQTableItem *item( int r, int c ) const; + void setItem( int r, int c, TQTableItem *i ); + void clearCell( int r, int c ); + void takeItem( TQTableItem *item ); + void insertWidget( int r, int c, TQWidget *w ); + TQWidget *cellWidget( int r, int c ) const; + void clearCellWidget( int r, int c ); + void initTable(); + void swapRows( int, int, bool ); + void swapColumns( int, int, bool ); + void swapCells( int, int, int, int ); +protected: + TQWidget* beginEdit ( int row, int col, bool replace ); + +private slots: + void repaintCell( int r, int c ); + + void unitRemoved( int ); + void unitCreated( const Unit& ); +signals: + void ratioChanged( int row, int col, double value ); + void ratioRemoved( int row, int col ); +}; + +class ConversionTableItem: public TQObject, public TQTableItem +{ + TQ_OBJECT +public: + ConversionTableItem( TQTable *t, EditType et ); + TQWidget *createEditor() const; + void setContentFromEditor( TQWidget *w ); + void setText( const TQString &s ); + void paint( TQPainter *p, const TQColorGroup &cg, const TQRect &cr, bool selected ); + void setTextAndSave( const TQString &s ); + int alignment() const + { + return TQt::AlignRight; + } +signals: + void ratioChanged( int row, int col, double value ); + void ratioRemoved( int row, int col ); + void signalRepaintCell( int r, int c ); + +}; + + +#endif diff --git a/src/widgets/criteriacombobox.cpp b/src/widgets/criteriacombobox.cpp new file mode 100644 index 0000000..8fd911a --- /dev/null +++ b/src/widgets/criteriacombobox.cpp @@ -0,0 +1,49 @@ +/*************************************************************************** +* Copyright (C) 2005 by * +* 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 "criteriacombobox.h" + +#include <tqlistbox.h> + +#include "backends/recipedb.h" +#include "datablocks/elementlist.h" + +CriteriaComboBox::CriteriaComboBox( bool b, TQWidget *parent, RecipeDB *db ) : KComboBox( b, parent ), + database( db ) +{ + connect( db, TQ_SIGNAL(ratingCriteriaCreated(const Element &)), this, TQ_SLOT(addCriteria(const Element &)) ); +} + +void CriteriaComboBox::addCriteria( const Element &criteria ) +{ + idMap.insert(count(),criteria.id); + + insertItem(criteria.name); + completionObject()->addItem(criteria.name); +} + +void CriteriaComboBox::reload() +{ + ElementList criteriaList; + database->loadRatingCriterion( &criteriaList ); + + clear(); + + for ( ElementList::const_iterator it = criteriaList.begin(); it != criteriaList.end(); ++it ) { + addCriteria((*it)); + } +} + +int CriteriaComboBox::criteriaID( int index ) +{ + return idMap[index]; +} + +#include "criteriacombobox.moc" diff --git a/src/widgets/criteriacombobox.h b/src/widgets/criteriacombobox.h new file mode 100644 index 0000000..4e35bb4 --- /dev/null +++ b/src/widgets/criteriacombobox.h @@ -0,0 +1,41 @@ +/*************************************************************************** +* Copyright (C) 2005 by * +* 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. * +***************************************************************************/ + +#ifndef CRITERIACOMBOBOX_H +#define CRITERIACOMBOBOX_H + +#include <tqmap.h> + +#include <kcombobox.h> + +#include "datablocks/element.h" + +class RecipeDB; + +class CriteriaComboBox : public KComboBox +{ + TQ_OBJECT + +public: + CriteriaComboBox( bool, TQWidget *parent, RecipeDB *db ); + + void reload(); + int criteriaID( int index ); + +protected slots: + void addCriteria( const Element &criteria ); + +private: + RecipeDB *database; + TQMap< int, int > idMap; +}; + +#endif //CRITERIACOMBOBOX_H + diff --git a/src/widgets/dblistviewbase.cpp b/src/widgets/dblistviewbase.cpp new file mode 100644 index 0000000..4a22330 --- /dev/null +++ b/src/widgets/dblistviewbase.cpp @@ -0,0 +1,334 @@ +/*************************************************************************** +* Copyright (C) 2005 by * +* 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 "dblistviewbase.h" + +#include <tdeapplication.h> +#include <tdeconfig.h> +#include <kcursor.h> +#include <kdebug.h> +#include <tdeglobal.h> +#include <tdelocale.h> +#include <kprogress.h> + + +//These two classes are used to identify the "Next" and "Prev" items, which are identified through rtti(). This also prevents renaming, even if it is enabled. +class PrevListViewItem : public TQListViewItem +{ +public: + PrevListViewItem( TQListView *parent ) : TQListViewItem(parent){} + + virtual int rtti() const { return PREVLISTITEM_RTTI; } + + TQString text( int c ) const { + if ( c == 0 ) { + return TQString("<< %1").arg(i18n("Previous")); + } + else + return TQString::null; + } +}; + +class NextListViewItem : public TQListViewItem +{ +public: + NextListViewItem( TQListView *parent, TQListViewItem *after ) : TQListViewItem(parent,after){} + + virtual int rtti() const { return NEXTLISTITEM_RTTI; } + + TQString text( int c ) const { + if ( c == 0 ) { + return TQString("%1 >>").arg(i18n("Next")); + } + else + return TQString::null; + } +}; + +DBListViewBase::DBListViewBase( TQWidget *parent, RecipeDB *db, int t ) : TDEListView(parent), + database(db), + curr_limit(-1), + curr_offset(0), + total(t), + bulk_load(false), + delete_me_later(0), + m_progress(0), + m_totalSteps(0) +{ + setSorting(-1); + + if ( curr_limit == -1 ) { //only use the default limit if a subclass hasn't given curr_limit its own value + TDEConfig * config = TDEGlobal::config();config->setGroup( "Performance" ); + curr_limit = config->readNumEntry( "Limit", -1 ); + } + + connect(this,TQ_SIGNAL(executed(TQListViewItem*)),TQ_SLOT(slotDoubleClicked(TQListViewItem*))); +} + +DBListViewBase::~DBListViewBase() +{ + delete m_progress; +} + +void DBListViewBase::activatePrev() +{ + if ( curr_offset != 0 ) { + curr_offset -= curr_limit; + if ( curr_offset < 0 ) + curr_offset = 0; + + reload(ForceReload); + emit prevGroupLoaded(); + } +} + +void DBListViewBase::activateNext() +{ + curr_offset += curr_limit; + + reload(ForceReload); + emit nextGroupLoaded(); +} + +void DBListViewBase::rename( TQListViewItem *it, int c ) +{ + if ( it->rtti() == PREVLISTITEM_RTTI || it->rtti() == NEXTLISTITEM_RTTI ) { + return; + } + + TDEListView::rename(it,c); +} + +void DBListViewBase::slotDoubleClicked( TQListViewItem *it ) +{ + //we can't delete the item the was double clicked + //and yet these functions will clear() the listview. + //We'll take the item from the view so it isn't deleted + //and delete it ourselves. + delete delete_me_later; delete_me_later = 0; + + if ( it->rtti() == PREVLISTITEM_RTTI ) { + delete_me_later = it; + takeItem(it); + activatePrev(); + } + else if ( it->rtti() == NEXTLISTITEM_RTTI ) { + delete_me_later = it; + takeItem(it); + activateNext(); + } +} + +void DBListViewBase::keyPressEvent( TQKeyEvent *k ) +{ + if ( k->state() == TQt::ShiftButton ) { + switch ( k->key() ) { + case TQt::Key_N: { + if ( curr_offset + curr_limit >= total || curr_limit == -1 ) { + k->accept(); + return; + } + + kapp->processEvents(); //if auto-repeating, user won't otherwise see change in the listview + activateNext(); + k->accept(); + break; + } + case TQt::Key_P: { + kapp->processEvents(); //if auto-repeating, user won't otherwise see change in the listview + activatePrev(); + k->accept(); + break; + } + default: break; + } + } + + TDEListView::keyPressEvent(k); +} + +void DBListViewBase::reload( ReloadFlags flag ) +{ + if ( flag == ForceReload || (!firstChild() && flag == Load) || (firstChild() && flag == ReloadIfPopulated) ) { + TDEApplication::setOverrideCursor( KCursor::waitCursor() ); + + init(); + + //m_progress = new KProgressDialog(this,0,TQString::null,i18n("Loading..."),true); + //m_progress->setAllowCancel(false); + //m_progress->progressBar()->setPercentageVisible(false); + //m_progress->progressBar()->setTotalSteps( m_totalSteps ); + //m_progress->show(); + //kapp->processEvents(); + + //reset some things + clear(); + lastElementMap.clear(); + + bulk_load=true; + load(curr_limit,curr_offset); + bulk_load=false; + + if ( curr_limit != -1 && curr_offset + curr_limit < total ) + new NextListViewItem(this,lastElementMap[0]); + + if ( curr_offset != 0 ) + new PrevListViewItem(this); + + //delete m_progress; m_progress = 0; + + TDEApplication::restoreOverrideCursor(); + } +} + +void DBListViewBase::setTotalItems(int i) +{ + m_totalSteps = i; + if ( m_progress ) { + m_progress->progressBar()->setTotalSteps( m_totalSteps ); + } +} + +void DBListViewBase::createElement( TQListViewItem *it ) +{ + Q_ASSERT(it); + + TQListViewItem *lastElement; + TQMap<TQListViewItem*,TQListViewItem*>::iterator map_it = lastElementMap.find(it->parent()); + if ( map_it != lastElementMap.end() ) { + lastElement = map_it.data(); + } + else + lastElement = 0; + + if ( bulk_load ) { //this can be much faster if we know the elements are already in order + if ( lastElement ) it->moveItem(lastElement); + lastElementMap.insert(it->parent(),it); + if ( m_progress ) { m_progress->progressBar()->advance(1); } + } + else { + if ( lastElement == 0 ) { + lastElementMap.insert(it->parent(),it); + } + else { + + int c = 0;//FIXME: the column used should be variable (set by a subclass) + + if ( it->parent() == 0 ) { + //start it out below the "Prev" item... currently it will be at firstChild() + if ( firstChild()->nextSibling() && + ( firstChild()->nextSibling()->rtti() == PREVLISTITEM_RTTI || + firstChild()->nextSibling()->rtti() == 1006 ) ) { //A hack to skip the Uncategorized item + it->moveItem( firstChild()->nextSibling() ); + } + } + + if ( TQString::localeAwareCompare(it->text(c),lastElement->text(c)) >= 0 ) { + it->moveItem(lastElement); + lastElementMap.insert(it->parent(),it); + } + else { + TQListViewItem *last_it = 0; + + for ( TQListViewItem *search_it = it; search_it; search_it = search_it->nextSibling() ) { + if ( search_it->rtti() == NEXTLISTITEM_RTTI ) { + it->moveItem(lastElement); + lastElementMap.insert(it->parent(),it); + } + else if ( TQString::localeAwareCompare(it->text(c),search_it->text(c)) < 0 ) { //we assume the list is sorted, as it should stay + if ( last_it ) it->moveItem(last_it); + break; + } + last_it = search_it; + } + } + } + } +} + +void DBListViewBase::removeElement( TQListViewItem *it, bool delete_item ) +{ + total--; + if ( !it ) return; + + TQListViewItem *lastElement = lastElementMap[it->parent()]; + if ( it == lastElement ) { + for ( TQListViewItem *search_it = (it->parent())?it->parent()->firstChild():firstChild(); search_it->nextSibling(); search_it = search_it->nextSibling() ) { + if ( it == search_it->nextSibling() ) { + lastElementMap.insert(it->parent(),search_it); + lastElement = search_it; + break; + } + } + + if ( lastElement == it || lastElement->rtti() == PREVLISTITEM_RTTI ) { //there are no more items in the view if this happens + if ( firstChild() && firstChild()->rtti() == PREVLISTITEM_RTTI ) { + activatePrev(); + it = 0; //keep 'delete it' below from segfault'ing + } + else if ( lastElement->nextSibling() && lastElement->nextSibling()->rtti() == NEXTLISTITEM_RTTI ) { + reload(); + it = 0; //keep 'delete it' below from segfault'ing + } + else //the list is now empty, there is no last element + lastElementMap.remove(it->parent()); + } + } + + if ( delete_item ) + delete it; +} + +bool DBListViewBase::handleElement( const TQString &name ) +{ + total++; + + TQListViewItem *lastElement = lastElementMap[0]; + + int c = 0;//FIXME: the column used should be variable (set by a subclass) + + int child_count = childCount(); + if ( child_count == 0 ) return true; + + if ( firstChild()->rtti() == PREVLISTITEM_RTTI || firstChild()->rtti() == 1006 ){ child_count--; } //"Prev" item + if ( child_count == 0 ) return true; + + if ( lastElement->nextSibling() ){ child_count--; } //"Next" item + + if ( curr_limit != -1 && child_count >= curr_limit ) { + TQListViewItem *firstElement = firstChild(); + if (firstElement->rtti() == PREVLISTITEM_RTTI || firstElement->rtti() == 1006 ) { + firstElement = firstElement->nextSibling(); + } + else if ( name < firstElement->text(c) ) { //provide access to this new element if we need to + new PrevListViewItem(this); + curr_offset++; + return false; + } + + if ( name < firstElement->text(c) ) { + curr_offset++; + return false; + } + else if ( name >= lastElement->text(c) ) { + if ( lastElement->nextSibling() == 0 ) + new NextListViewItem(this,lastElement); + + return false; + } + else { + removeElement(lastElement); + } + } + + return true; +} + +#include "dblistviewbase.moc" diff --git a/src/widgets/dblistviewbase.h b/src/widgets/dblistviewbase.h new file mode 100644 index 0000000..d1514fe --- /dev/null +++ b/src/widgets/dblistviewbase.h @@ -0,0 +1,89 @@ +/*************************************************************************** +* Copyright (C) 2005 by * +* 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. * +***************************************************************************/ + +#ifndef LISTVIEWHANDLER_H +#define LISTVIEWHANDLER_H + +#include <tqobject.h> +#include <tqmap.h> + +#include <tdelistview.h> + +#define PREVLISTITEM_RTTI 1002 +#define NEXTLISTITEM_RTTI 1003 + +class KProgressDialog; + +class RecipeDB; + +enum ReloadFlags { + Load, /** Only performs the reload if the list hasn't already been loaded */ + ReloadIfPopulated, /** Only performs the reload if the list has been loaded */ + ForceReload /** Load/reload the list regardless of whether or not it's been loaded */ +}; + +class DBListViewBase : public TDEListView +{ +TQ_OBJECT + +public: + DBListViewBase( TQWidget *, RecipeDB *, int total ); + ~DBListViewBase(); + + void reload( ReloadFlags flag = Load ); + +signals: + void nextGroupLoaded(); + void prevGroupLoaded(); + +protected: + /** + * Called when the list view is ready to be used, i.e., it has been loaded with data. + * Until the list view has been loaded, we can ignore all database signals regarding changes + * of data. Therefore, subclasses should connect to these signals during this call. + */ + virtual void init(){} + virtual void load(int limit, int offset) = 0; + virtual void keyPressEvent( TQKeyEvent *e ); + bool handleElement( const TQString & ); + virtual void createElement( TQListViewItem * ); + void removeElement( TQListViewItem *, bool delete_item = true ); + + bool reloading(){ return bulk_load; } + void setSorting(int c){TDEListView::setSorting(c);} //don't do sorting, the database comes sorted from the database anyways + void setTotalItems(int); + + RecipeDB *database; + int curr_limit; + int curr_offset; + +protected slots: + void rename( TQListViewItem *, int c ); + void slotDoubleClicked( TQListViewItem * ); + +private: + void activatePrev(); + void activateNext(); + + //make this private because the data should always be synced with the database + void clear(){TDEListView::clear();} + + int total; + + bool bulk_load; + + TQMap<TQListViewItem*,TQListViewItem*> lastElementMap; + TQListViewItem *delete_me_later; + + KProgressDialog *m_progress; + int m_totalSteps; +}; + +#endif //LISTVIEWHANDLER_H diff --git a/src/widgets/fractioninput.cpp b/src/widgets/fractioninput.cpp new file mode 100644 index 0000000..68a3cfd --- /dev/null +++ b/src/widgets/fractioninput.cpp @@ -0,0 +1,121 @@ +/*************************************************************************** +* Copyright (C) 2003 by * +* 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 "fractioninput.h" + +#include <tqtimer.h> + +#include <tdeglobalsettings.h> + +#include "datablocks/ingredient.h" + +FractionInput::FractionInput( TQWidget *parent, MixedNumber::Format format ) : KLineEdit( parent ), + m_allowRange(false), + m_validateTimer(new TQTimer(this)), + m_format(format) +{ + setAlignment( TQt::AlignRight ); + + connect( this, TQ_SIGNAL(textChanged(const TQString&)), this, TQ_SLOT(slotStartValidateTimer()) ); + connect( m_validateTimer, TQ_SIGNAL(timeout()), this, TQ_SLOT(validate()) ); +} + +FractionInput::~FractionInput() +{ + delete m_validateTimer; +} + +void FractionInput::setValue( double d, double amount_offset ) +{ + MixedNumber m( d ); + setValue( m, amount_offset ); +} + +void FractionInput::setValue( const MixedNumber &m, double amount_offset ) +{ + TQString text = m.toString( m_format ); + if ( amount_offset > 0 ) { + text += "-" + MixedNumber(m+amount_offset).toString( MixedNumber::MixedNumberFormat ); + } + setText(text); +} + +void FractionInput::value( MixedNumber &amount, double &amount_offset ) const +{ + Ingredient i; i.setAmount( text() ); + + amount = MixedNumber(i.amount); + amount_offset = i.amount_offset; +} + +void FractionInput::value( double &amount, double &amount_offset ) const +{ + Ingredient i; i.setAmount( text() ); + + amount = i.amount; + amount_offset = i.amount_offset; +} + +MixedNumber FractionInput::value() const +{ + Ingredient i; i.setAmount( text() ); + + return MixedNumber(i.amount); +} + +MixedNumber FractionInput::minValue() const +{ + Ingredient i; i.setAmount( text() ); + + return MixedNumber(i.amount); +} + +MixedNumber FractionInput::maxValue() const +{ + Ingredient i; i.setAmount( text() ); + + return MixedNumber(i.amount_offset+i.amount); +} + +bool FractionInput::isInputValid() const +{ + if ( !m_allowRange && text().contains("-") ) + return false; + + bool ok; + Ingredient i; i.setAmount( text(), &ok ); + + return ok; +} + +void FractionInput::slotStartValidateTimer() +{ + if ( !m_validateTimer->isActive() ) + m_validateTimer->start( 1000, true ); + + if ( isInputValid() ) + emit valueChanged( value() ); +} + +void FractionInput::validate() +{ + if ( isInputValid() ) { + setPaletteForegroundColor( TDEGlobalSettings::textColor() ); + } + else + setPaletteForegroundColor( TQt::red ); +} + +bool FractionInput::isEmpty() const +{ + return text().isEmpty(); +} + +#include "fractioninput.moc" diff --git a/src/widgets/fractioninput.h b/src/widgets/fractioninput.h new file mode 100644 index 0000000..b682a32 --- /dev/null +++ b/src/widgets/fractioninput.h @@ -0,0 +1,62 @@ +/*************************************************************************** +* Copyright (C) 2003 by * +* 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. * +***************************************************************************/ + +#ifndef FRACTIONINPUT_H +#define FRACTIONINPUT_H + +#include <klineedit.h> + +#include "datablocks/mixednumber.h" + +class TQTimer; + +/** A KLineEdit widget extended to allow input of decimals and fractions or ranges of such. + * Input is returned as a @ref MixedNumber class. + * @author Jason Kivlighn + */ +class FractionInput : public KLineEdit +{ +TQ_OBJECT + +public: + FractionInput( TQWidget *parent = 0, MixedNumber::Format = MixedNumber::MixedNumberFormat ); + ~FractionInput(); + + void setAllowRange( bool b ){ m_allowRange = b; } + + void setValue( double amount, double amount_offset ); + void setValue( const MixedNumber &, double amount_offset ); + + void value( MixedNumber &amount, double &amount_offset ) const; + void value( double &amount, double &amount_offset ) const; + MixedNumber minValue() const; + MixedNumber maxValue() const; + MixedNumber value() const; + + bool isInputValid() const; + bool isEmpty() const; + +signals: + void valueChanged( const MixedNumber & ); + +public slots: + void validate(); + +private slots: + void slotStartValidateTimer(); + +private: + bool m_allowRange; + TQTimer *m_validateTimer; + MixedNumber::Format m_format; +}; + +#endif //FRACTIONINPUT_H + diff --git a/src/widgets/headercombobox.cpp b/src/widgets/headercombobox.cpp new file mode 100644 index 0000000..3c43cbc --- /dev/null +++ b/src/widgets/headercombobox.cpp @@ -0,0 +1,42 @@ +/*************************************************************************** +* Copyright (C) 2005 by * +* 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 "headercombobox.h" + +#include <tqlistbox.h> + +#include "backends/recipedb.h" +#include "datablocks/elementlist.h" + +HeaderComboBox::HeaderComboBox( bool b, TQWidget *parent, RecipeDB *db ) : KComboBox( b, parent ), + database( db ) +{ +} + +void HeaderComboBox::reload() +{ + TQString remember_text = currentText(); + + ElementList headerList; + database->loadIngredientGroups( &headerList ); + + clear(); + + for ( ElementList::const_iterator it = headerList.begin(); it != headerList.end(); ++it ) { + insertItem((*it).name); + completionObject()->addItem((*it).name); + } + + if ( listBox()->findItem( remember_text, TQt::ExactMatch ) ) { + setCurrentText( remember_text ); + } +} + +#include "headercombobox.moc" diff --git a/src/widgets/headercombobox.h b/src/widgets/headercombobox.h new file mode 100644 index 0000000..5e07b20 --- /dev/null +++ b/src/widgets/headercombobox.h @@ -0,0 +1,34 @@ +/*************************************************************************** +* Copyright (C) 2005 by * +* 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. * +***************************************************************************/ + +#ifndef HEADERCOMBOBOX_H +#define HEADERCOMBOBOX_H + +#include <kcombobox.h> + +#include "datablocks/element.h" + +class RecipeDB; + +class HeaderComboBox : public KComboBox +{ + TQ_OBJECT + +public: + HeaderComboBox( bool, TQWidget *parent, RecipeDB *db ); + + void reload(); + +private: + RecipeDB *database; +}; + +#endif //HEADERCOMBOBOX_H + diff --git a/src/widgets/headerlistview.cpp b/src/widgets/headerlistview.cpp new file mode 100644 index 0000000..51e9d4c --- /dev/null +++ b/src/widgets/headerlistview.cpp @@ -0,0 +1,196 @@ +/*************************************************************************** +* Copyright (C) 2004 by * +* Jason Kivlighn ([email protected]) * +* Unai Garro ([email protected]) * +* Cyril Bosselut ([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 "headerlistview.h" + +#include <tdemessagebox.h> +#include <tdeconfig.h> +#include <tdelocale.h> +#include <tdeglobal.h> +#include <kiconloader.h> +#include <tdepopupmenu.h> + +#include "backends/recipedb.h" +#include "dialogs/createelementdialog.h" +#include "dialogs/dependanciesdialog.h" + +HeaderListView::HeaderListView( TQWidget *parent, RecipeDB *db ) : DBListViewBase( parent,db,db->unitCount() ) +{ + setAllColumnsShowFocus( true ); + setDefaultRenameAction( TQListView::Reject ); +} + +void HeaderListView::init() +{ + connect( database, TQ_SIGNAL( ingGroupCreated( const Element & ) ), TQ_SLOT( checkCreateHeader( const Element & ) ) ); + connect( database, TQ_SIGNAL( ingGroupRemoved( int ) ), TQ_SLOT( removeHeader( int ) ) ); +} + +void HeaderListView::load( int /*limit*/, int /*offset*/ ) +{ + ElementList headerList; + database->loadIngredientGroups( &headerList ); + + setTotalItems(headerList.count()); + + for ( ElementList::const_iterator it = headerList.begin(); it != headerList.end(); ++it ) { + createHeader( *it ); + } +} + +void HeaderListView::checkCreateHeader( const Element &el ) +{ + if ( handleElement(el.name) ) { //only create this header if the base class okays it + createHeader(el); + } +} + + +StdHeaderListView::StdHeaderListView( TQWidget *parent, RecipeDB *db, bool editable ) : HeaderListView( parent, db ) +{ + addColumn( i18n( "Header" ) ); + + 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 ); + + 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->polish(); + + delete il; + + 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 ) ), this, TQ_SLOT( modHeader( TQListViewItem*, const TQPoint &, int ) ) ); + connect( this, TQ_SIGNAL( itemRenamed( TQListViewItem*, const TQString &, int ) ), this, TQ_SLOT( saveHeader( TQListViewItem*, const TQString &, int ) ) ); + } +} + +void StdHeaderListView::showPopup( TDEListView * /*l*/, TQListViewItem *i, const TQPoint &p ) +{ + if ( i ) + kpop->exec( p ); +} + +void StdHeaderListView::createNew() +{ + CreateElementDialog * headerDialog = new CreateElementDialog( this, i18n("Header") ); + + if ( headerDialog->exec() == TQDialog::Accepted ) { + TQString result = headerDialog->newElementName(); + + //check bounds first + if ( checkBounds( result ) ) + database->createNewIngGroup( result ); + } + delete headerDialog; +} + +void StdHeaderListView::remove() +{ + // Find selected header item + TQListViewItem * it = selectedItem(); + + if ( it ) { + int headerID = it->text( 1 ).toInt(); + + ElementList recipeDependancies; + database->findUseOfIngGroupInRecipes( &recipeDependancies, headerID ); + + if ( recipeDependancies.isEmpty() ) + database->removeIngredientGroup( headerID ); + else { // need warning! + ListInfo info; + info.list = recipeDependancies; + info.name = i18n( "Recipes" ); + + DependanciesDialog warnDialog( this, info ); + warnDialog.setCustomWarning( i18n("You are about to permanantly delete recipes from your database.") ); + if ( warnDialog.exec() == TQDialog::Accepted ) + database->removeIngredientGroup( headerID ); + } + } +} + +void StdHeaderListView::rename() +{ + TQListViewItem * item = currentItem(); + + if ( item ) + HeaderListView::rename( item, 0 ); +} + +void StdHeaderListView::createHeader( const Element &header ) +{ + createElement(new TQListViewItem( this, header.name, TQString::number( header.id ) )); +} + +void StdHeaderListView::removeHeader( int id ) +{ + TQListViewItem * item = findItem( TQString::number( id ), 1 ); + removeElement(item); +} + +void StdHeaderListView::modHeader( TQListViewItem* i, const TQPoint & /*p*/, int c ) +{ + if ( i ) + HeaderListView::rename( i, c ); +} + +void StdHeaderListView::saveHeader( TQListViewItem* i, const TQString &text, int /*c*/ ) +{ + if ( !checkBounds( text ) ) { + reload(ForceReload); //reset the changed text + return ; + } + + int existing_id = database->findExistingIngredientGroupByName( text ); + int header_id = i->text( 1 ).toInt(); + if ( existing_id != -1 && existing_id != header_id ) { //already exists with this label... merge the two + switch ( KMessageBox::warningContinueCancel( this, i18n( "This header already exists. Continuing will merge these two headers into one. Are you sure?" ) ) ) { + case KMessageBox::Continue: { + database->modIngredientGroup( header_id, i->text( 0 ) ); + database->mergeIngredientGroups( header_id, existing_id ); + break; + } + default: + reload(ForceReload); + break; + } + } + else { + database->modIngredientGroup( header_id, i->text( 0 ) ); + } +} + +bool StdHeaderListView::checkBounds( const TQString &header ) +{ + if ( header.length() > uint(database->maxIngGroupNameLength()) ) { + KMessageBox::error( this, TQString( i18n( "Header cannot be longer than %1 characters." ) ).arg( database->maxIngGroupNameLength() ) ); + return false; + } + else if ( header.stripWhiteSpace().isEmpty() ) + return false; + + return true; +} + +#include "headerlistview.moc" diff --git a/src/widgets/headerlistview.h b/src/widgets/headerlistview.h new file mode 100644 index 0000000..716d002 --- /dev/null +++ b/src/widgets/headerlistview.h @@ -0,0 +1,70 @@ +/*************************************************************************** +* Copyright (C) 2004 by * +* Jason Kivlighn ([email protected]) * +* Unai Garro ([email protected]) * +* Cyril Bosselut ([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. * +***************************************************************************/ + +#ifndef HEADERLISTVIEW_H +#define HEADERLISTVIEW_H + +#include "dblistviewbase.h" + +#include "datablocks/element.h" + +class RecipeDB; +class TDEPopupMenu; + +class HeaderListView : public DBListViewBase +{ + TQ_OBJECT + +public: + HeaderListView( TQWidget *parent, RecipeDB *db ); + +public slots: + virtual void load( int curr_limit, int curr_offset ); + +protected slots: + virtual void createHeader( const Element & ) = 0; + virtual void removeHeader( int ) = 0; + + void checkCreateHeader( const Element &el ); + +protected: + virtual void init(); +}; + +class StdHeaderListView : public HeaderListView +{ + TQ_OBJECT + +public: + StdHeaderListView( TQWidget *parent, RecipeDB *db, bool editable = false ); + +protected: + virtual void createHeader( const Element & ); + virtual void removeHeader( int ); + +private slots: + void showPopup( TDEListView *, TQListViewItem *, const TQPoint & ); + + void createNew(); + void remove(); + void rename(); + + void modHeader( TQListViewItem* i, const TQPoint &p, int c ); + void saveHeader( TQListViewItem* i, const TQString &text, int c ); + +private: + bool checkBounds( const TQString &unit ); + + TDEPopupMenu *kpop; +}; + +#endif //HEADERLISTVIEW_H diff --git a/src/widgets/inglistviewitem.cpp b/src/widgets/inglistviewitem.cpp new file mode 100644 index 0000000..0670dc4 --- /dev/null +++ b/src/widgets/inglistviewitem.cpp @@ -0,0 +1,225 @@ +/*************************************************************************** +* Copyright (C) 2005 by * +* 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 "inglistviewitem.h" + +#include <tdelocale.h> +#include <tdeconfig.h> +#include <tdeglobal.h> +#include <kdebug.h> + +#include "datablocks/unit.h" +#include "datablocks/mixednumber.h" + +IngSubListViewItem::IngSubListViewItem( TQListViewItem* qli, const Ingredient &i ) : IngListViewItem( qli, 0, i ) +{ +} + +TQString IngSubListViewItem::text( int column ) const +{ + if ( column == 0 ) { + //kdDebug()<<"displaying col 0 for "<<m_ing.name<<endl; + return TQString("%1 ").arg(i18n("OR"))+m_ing.name; + //return m_ing.name; + } + else + return IngListViewItem::text(column); + +} + +void IngSubListViewItem::setText( int column, const TQString &text ) +{ + switch ( column ) { + case 0: { + TQString compare = TQString("%1 ").arg(i18n("OR")); + if ( text.left(compare.length()) == compare ) + m_ing.name = text.right(text.length()-compare.length()); + else + m_ing.name = text; + break; + } + default: + IngListViewItem::setText(column,text); + break; + } +} + +int IngSubListViewItem::rtti() const +{ + return INGSUBLISTVIEWITEM_RTTI; +} + + +IngListViewItem::IngListViewItem( TQListView* qlv, const Ingredient &i ) : TQListViewItem( qlv ) +{ + init( i ); +} + +IngListViewItem::IngListViewItem( TQListView* qlv, TQListViewItem *after, const Ingredient &i ) : TQListViewItem( qlv, after ) +{ + init( i ); +} + +IngListViewItem::IngListViewItem( TQListViewItem* qli, TQListViewItem *after, const Ingredient &i ) : TQListViewItem( qli, after ) +{ + init( i ); +} + +int IngListViewItem::rtti() const +{ + return INGLISTVIEWITEM_RTTI; +} + +Ingredient IngListViewItem::ingredient() const +{ + return m_ing; +} + +void IngListViewItem::setAmount( double amount, double amount_offset ) +{ + amount_str = TQString::null; + + if ( amount > 0 ) { + TDEConfig * config = TDEGlobal::config(); + config->setGroup( "Formatting" ); + + if ( config->readBoolEntry( "Fraction" ) ) + amount_str = MixedNumber( amount ).toString(); + else + amount_str = beautify( TDEGlobal::locale() ->formatNumber( amount, 5 ) ); + } + if ( amount_offset > 0 ) { + if ( amount < 1e-10 ) + amount_str += "0"; + amount_str += "-" + MixedNumber(amount+amount_offset).toString(); + } + + m_ing.amount = amount; + m_ing.amount_offset = amount_offset; + + //FIXME: make sure the right unit is showing after changing this (force a repaint... repaint() doesn't do the job right because it gets caught in a loop) +} + +void IngListViewItem::setUnit( const Unit &unit ) +{ + //### This shouldn't be necessary... the db backend should ensure this doesn't happen + if ( !unit.name.isEmpty() ) + m_ing.units.name = unit.name; + if ( !unit.plural.isEmpty() ) + m_ing.units.plural = unit.plural; +} + +void IngListViewItem::setPrepMethod( const TQString &prepMethod ) +{ + m_ing.prepMethodList = ElementList::split(",",prepMethod); +} + +void IngListViewItem::setText( int column, const TQString &text ) +{ + switch ( column ) { + case 0: { + m_ing.name = text; + break; + } + case 1: { + Ingredient i; i.setAmount(text); + setAmount( i.amount, i.amount_offset ); + break; + } + case 2: + setUnit( Unit( text, m_ing.amount+m_ing.amount_offset ) ); + break; + case 3: + setPrepMethod( text ); + break; + default: + break; + } +} + +TQString IngListViewItem::text( int column ) const +{ + switch ( column ) { + case 0: + return m_ing.name; + break; + case 1: + return amount_str; + break; + case 2: + return ( m_ing.amount+m_ing.amount_offset > 1 ) ? m_ing.units.plural : m_ing.units.name; + break; + case 3: + return m_ing.prepMethodList.join(","); + break; + default: + return ( TQString::null ); + } +} + +void IngListViewItem::init( const Ingredient &i ) +{ + m_ing = i; + + setAmount( i.amount, i.amount_offset ); +} + + +IngGrpListViewItem::IngGrpListViewItem( TQListView* qlv, TQListViewItem *after, const TQString &group, int id ) : TQListViewItem( qlv, after ) +{ + init( group, id ); +} + +int IngGrpListViewItem::rtti() const +{ + return INGGRPLISTVIEWITEM_RTTI; +} + +TQString IngGrpListViewItem::group() const +{ + return m_group; +} + +int IngGrpListViewItem::id() const +{ + return m_id; +} + +TQString IngGrpListViewItem::text( int column ) const +{ + switch ( column ) { + case 0: + return m_group + ":"; + break; + default: + return ( TQString::null ); + } +} + +void IngGrpListViewItem::setText( int column, const TQString &text ) +{ + switch ( column ) { + case 0: + if ( text.right(1) == ":" ) + m_group = text.left(text.length()-1); + else + m_group = text; + break; + default: + break; + } +} + +void IngGrpListViewItem::init( const TQString &group, int id ) +{ + m_group = group; + m_id = id; +} + diff --git a/src/widgets/inglistviewitem.h b/src/widgets/inglistviewitem.h new file mode 100644 index 0000000..89de283 --- /dev/null +++ b/src/widgets/inglistviewitem.h @@ -0,0 +1,82 @@ +/*************************************************************************** +* Copyright (C) 2005 by * +* 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. * +***************************************************************************/ + +#ifndef INGLISTVIEWITEM_H +#define INGLISTVIEWITEM_H + +#include "tqlistview.h" + +#include "datablocks/ingredient.h" + +#define INGGRPLISTVIEWITEM_RTTI 1003 +#define INGLISTVIEWITEM_RTTI 1004 +#define INGSUBLISTVIEWITEM_RTTI 1009 + +class IngListViewItem : public TQListViewItem +{ +public: + IngListViewItem( TQListView* qlv, const Ingredient &i ); + IngListViewItem( TQListView* qlv, TQListViewItem *after, const Ingredient &i ); + IngListViewItem( TQListViewItem* qli, TQListViewItem *after, const Ingredient &i ); + + int rtti() const; + + Ingredient ingredient() const; + + void setAmount( double amount, double amount_offset ); + void setUnit( const Unit &unit ); + void setPrepMethod( const TQString &prepMethod ); + +protected: + Ingredient m_ing; + TQString amount_str; + +public: + virtual TQString text( int column ) const; + virtual void setText( int column, const TQString &text ); + +private: + void init( const Ingredient &i ); +}; + + +class IngSubListViewItem : public IngListViewItem +{ +public: + IngSubListViewItem( TQListViewItem* qli, const Ingredient &i ); + + virtual TQString text( int column ) const; + virtual void setText( int column, const TQString &text ); + virtual int rtti() const; +}; + + +class IngGrpListViewItem : public TQListViewItem +{ +public: + IngGrpListViewItem( TQListView* qlv, TQListViewItem *after, const TQString &group, int id ); + + int rtti() const; + + TQString group() const; + int id() const; + + virtual TQString text( int column ) const; + virtual void setText( int column, const TQString &text ); + +protected: + TQString m_group; + int m_id; + +private: + void init( const TQString &group, int id ); +}; + +#endif diff --git a/src/widgets/ingredientcombobox.cpp b/src/widgets/ingredientcombobox.cpp new file mode 100644 index 0000000..0e036b0 --- /dev/null +++ b/src/widgets/ingredientcombobox.cpp @@ -0,0 +1,188 @@ +/*************************************************************************** +* Copyright (C) 2005 by * +* 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 "ingredientcombobox.h" + +#include <tqlistbox.h> +#include <tqtimer.h> + +#include <kdebug.h> +#include <tdeapplication.h> +#include <tdeglobal.h> +#include <tdeconfig.h> + +#include "backends/recipedb.h" +#include "datablocks/elementlist.h" + +IngredientComboBox::IngredientComboBox( bool b, TQWidget *parent, RecipeDB *db, const TQString &specialItem ) : KComboBox( b, parent ), + database( db ), loading_at(0), load_timer(new TQTimer(this)), m_specialItem(specialItem) +{ + connect( load_timer, TQ_SIGNAL(timeout()), TQ_SLOT(loadMore()) ); + completionObject()->setIgnoreCase(true); +} + +void IngredientComboBox::reload() +{ + TQString remember_text; + if ( editable() ) + remember_text = lineEdit()->text(); + + ElementList ingredientList; + database->loadIngredients( &ingredientList ); + + clear(); + ingredientComboRows.clear(); + + int row = 0; + if ( !m_specialItem.isNull() ) { + insertItem(m_specialItem); + ingredientComboRows.insert( row, -1 ); + row++; + } + for ( ElementList::const_iterator it = ingredientList.begin(); it != ingredientList.end(); ++it, ++row ) { + insertItem((*it).name); + completionObject()->addItem((*it).name); + ingredientComboRows.insert( row, (*it).id ); + } + + if ( editable() ) + setEditText( remember_text ); + + database->disconnect( this ); + connect( database, TQ_SIGNAL( ingredientCreated( const Element & ) ), TQ_SLOT( createIngredient( const Element & ) ) ); + connect( database, TQ_SIGNAL( ingredientRemoved( int ) ), TQ_SLOT( removeIngredient( int ) ) ); +} + +void IngredientComboBox::loadMore() +{ + if ( loading_at >= ing_count-1 ) { + endLoad(); + return; + } + + ElementList ingredientList; + database->loadIngredients( &ingredientList, load_limit, loading_at ); + + for ( ElementList::const_iterator it = ingredientList.begin(); it != ingredientList.end(); ++it, ++loading_at ) { + insertItem((*it).name); + completionObject()->addItem((*it).name); + ingredientComboRows.insert( loading_at, (*it).id ); + } +} + +void IngredientComboBox::startLoad() +{ + //don't receive ingredient created/removed events from the database + database->disconnect( this ); + + TDEConfig * config = TDEGlobal::config(); config->setGroup( "Performance" ); + load_limit = config->readNumEntry( "Limit", -1 ); + if ( load_limit == -1 ) { + reload(); + endLoad(); + } + else { + loading_at = 0; + ing_count = database->ingredientCount(); + + load_timer->start( 0, false ); + } +} + +void IngredientComboBox::endLoad() +{ + load_timer->stop(); + + //now we're ready to receive ingredient created/removed events from the database + connect( database, TQ_SIGNAL( ingredientCreated( const Element & ) ), TQ_SLOT( createIngredient( const Element & ) ) ); + connect( database, TQ_SIGNAL( ingredientRemoved( int ) ), TQ_SLOT( removeIngredient( int ) ) ); +} + +int IngredientComboBox::id( int row ) +{ + return ingredientComboRows[ row ]; +} + +int IngredientComboBox::id( const TQString &ing ) +{ + for ( int i = 0; i < count(); i++ ) { + if ( ing == text( i ) ) + return id(i); + } + kdDebug()<<"Warning: couldn't find the ID for "<<ing<<endl; + return -1; +} + +void IngredientComboBox::createIngredient( const Element &element ) +{ + int row = findInsertionPoint( element.name ); + + TQString remember_text; + if ( editable() ) + remember_text = lineEdit()->text(); + + insertItem( element.name, row ); + completionObject()->addItem(element.name); + + if ( editable() ) + lineEdit()->setText( remember_text ); + + //now update the map by pushing everything after this item down + TQMap<int, int> new_map; + for ( TQMap<int, int>::iterator it = ingredientComboRows.begin(); it != ingredientComboRows.end(); ++it ) { + if ( it.key() >= row ) { + new_map.insert( it.key() + 1, it.data() ); + } + else + new_map.insert( it.key(), it.data() ); + } + ingredientComboRows = new_map; + ingredientComboRows.insert( row, element.id ); +} + +void IngredientComboBox::removeIngredient( int id ) +{ + int row = -1; + for ( TQMap<int, int>::iterator it = ingredientComboRows.begin(); it != ingredientComboRows.end(); ++it ) { + if ( it.data() == id ) { + row = it.key(); + completionObject()->removeItem( text(row) ); + removeItem( row ); + ingredientComboRows.remove( it ); + break; + } + } + + if ( row == -1 ) + return ; + + //now update the map by pushing everything after this item up + TQMap<int, int> new_map; + for ( TQMap<int, int>::iterator it = ingredientComboRows.begin(); it != ingredientComboRows.end(); ++it ) { + if ( it.key() > row ) { + new_map.insert( it.key() - 1, it.data() ); + } + else + new_map.insert( it.key(), it.data() ); + } + ingredientComboRows = new_map; +} + +int IngredientComboBox::findInsertionPoint( const TQString &name ) +{ + for ( int i = 0; i < count(); i++ ) { + if ( TQString::localeAwareCompare( name, text( i ) ) < 0 ) + return i; + } + + return count(); +} + +#include "ingredientcombobox.moc" diff --git a/src/widgets/ingredientcombobox.h b/src/widgets/ingredientcombobox.h new file mode 100644 index 0000000..9bd5fb7 --- /dev/null +++ b/src/widgets/ingredientcombobox.h @@ -0,0 +1,58 @@ +/*************************************************************************** +* Copyright (C) 2005 by * +* 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. * +***************************************************************************/ + +#ifndef INGREDIENTCOMBOBOX_H +#define INGREDIENTCOMBOBOX_H + +#include <kcombobox.h> + +#include <tqmap.h> + +#include "datablocks/element.h" + +class TQTimer; + +class RecipeDB; +class ElementList; + +class IngredientComboBox : public KComboBox +{ + TQ_OBJECT + +public: + IngredientComboBox( bool, TQWidget *parent, RecipeDB *db, const TQString &specialItem = TQString::null ); + + void reload(); + int id( int row ); + int id( const TQString &ing ); + + void startLoad(); + void endLoad(); + +private slots: + void createIngredient( const Element &element ); + void removeIngredient( int id ); + + int findInsertionPoint( const TQString &name ); + void loadMore(); + +private: + RecipeDB *database; + TQMap<int, int> ingredientComboRows; // Contains the category id for every given row in the category combobox + + int loading_at; + int ing_count; + int load_limit; + TQTimer *load_timer; + TQString m_specialItem; +}; + +#endif //INGREDIENTCOMBOBOX_H + diff --git a/src/widgets/ingredientinputwidget.cpp b/src/widgets/ingredientinputwidget.cpp new file mode 100644 index 0000000..3c0aa7e --- /dev/null +++ b/src/widgets/ingredientinputwidget.cpp @@ -0,0 +1,542 @@ +/*************************************************************************** +* Copyright (C) 2003-2005 by * +* Unai Garro ([email protected]) * +* Cyril Bosselut ([email protected]) * +* Jason Kivlighn ([email protected]) * +* * +* Copyright (C) 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 "ingredientinputwidget.h" + +#include <tqlabel.h> +#include <tqwidgetstack.h> +#include <tqhbox.h> +#include <tqvbox.h> +#include <tqgroupbox.h> +#include <tqbuttongroup.h> +#include <tqradiobutton.h> +#include <tqcheckbox.h> + +#include <kcombobox.h> +#include <tdelocale.h> +#include <tdemessagebox.h> +#include <kdebug.h> + +#include "backends/recipedb.h" +#include "datablocks/unit.h" +#include "widgets/fractioninput.h" +#include "widgets/ingredientcombobox.h" +#include "widgets/headercombobox.h" +#include "widgets/prepmethodcombobox.h" +#include "dialogs/createunitdialog.h" + +#include "profiling.h" + +IngredientInput::IngredientInput( RecipeDB *db, TQWidget *parent, bool allowHeader ) : TQHBox(parent), database(db) +{ + TQVBox *ingredientVBox = new TQVBox( this ); + TQHBox *typeHBox = new TQHBox( ingredientVBox ); + + if ( allowHeader ) { + typeButtonGrp = new TQButtonGroup(); + TQRadioButton *ingredientRadioButton = new TQRadioButton( i18n( "Ingredient:" ), typeHBox ); + typeButtonGrp->insert( ingredientRadioButton, 0 ); + + TQRadioButton *headerRadioButton = new TQRadioButton( i18n( "Ingredient grouping name", "Header:" ), typeHBox ); + typeButtonGrp->insert( headerRadioButton, 1 ); + + typeButtonGrp->setButton( 0 ); + connect( typeButtonGrp, TQ_SIGNAL( clicked( int ) ), TQ_SLOT( typeButtonClicked( int ) ) ); + } + else { + (void) new TQLabel( i18n( "Ingredient:" ), typeHBox ); + typeButtonGrp = 0; + } + + header_ing_stack = new TQWidgetStack(ingredientVBox); + ingredientBox = new IngredientComboBox( TRUE, header_ing_stack, database ); + ingredientBox->setAutoCompletion( TRUE ); + ingredientBox->lineEdit() ->disconnect( ingredientBox ); //so hitting enter doesn't enter the item into the box + ingredientBox->setSizePolicy( TQSizePolicy( TQSizePolicy::Ignored, TQSizePolicy::Fixed ) ); + header_ing_stack->addWidget( ingredientBox ); + headerBox = new HeaderComboBox( TRUE, header_ing_stack, database ); + headerBox->setAutoCompletion( TRUE ); + headerBox->lineEdit() ->disconnect( ingredientBox ); //so hitting enter doesn't enter the item into the box + headerBox->setSizePolicy( TQSizePolicy( TQSizePolicy::Ignored, TQSizePolicy::Fixed ) ); + header_ing_stack->addWidget( headerBox ); + + TQVBox *amountVBox = new TQVBox( this ); + amountLabel = new TQLabel( i18n( "Amount:" ), amountVBox ); + amountEdit = new FractionInput( amountVBox ); + amountEdit->setAllowRange(true); + amountEdit->setSizePolicy( TQSizePolicy( TQSizePolicy::Minimum, TQSizePolicy::Fixed ) ); + + TQVBox *unitVBox = new TQVBox( this ); + unitLabel = new TQLabel( i18n( "Unit:" ), unitVBox ); + unitBox = new KComboBox( TRUE, unitVBox ); + unitBox->setAutoCompletion( TRUE ); + unitBox->lineEdit() ->disconnect( unitBox ); //so hitting enter doesn't enter the item into the box + unitBox->setSizePolicy( TQSizePolicy( TQSizePolicy::Ignored, TQSizePolicy::Fixed ) ); + + TQVBox *prepMethodVBox = new TQVBox( this ); + prepMethodLabel = new TQLabel( i18n( "Preparation Method:" ), prepMethodVBox ); + prepMethodBox = new PrepMethodComboBox( TRUE, prepMethodVBox, database ); + prepMethodBox->setAutoCompletion( TRUE ); + prepMethodBox->lineEdit() ->disconnect( prepMethodBox ); //so hitting enter doesn't enter the item into the box + prepMethodBox->setSizePolicy( TQSizePolicy( TQSizePolicy::Ignored, TQSizePolicy::Fixed ) ); + + orButton = new TQCheckBox( i18n( "OR" ), this ); + + setStretchFactor( ingredientVBox, 5 ); + setStretchFactor( amountVBox, 1 ); + setStretchFactor( unitVBox, 2 ); + setStretchFactor( prepMethodVBox, 3 ); + + connect( ingredientBox, TQ_SIGNAL( activated( int ) ), this, TQ_SLOT( loadUnitListCombo() ) ); + connect( ingredientBox->lineEdit(), TQ_SIGNAL( lostFocus() ), this, TQ_SLOT( slotIngredientBoxLostFocus() ) ); + connect( unitBox->lineEdit(), TQ_SIGNAL( lostFocus() ), this, TQ_SLOT( slotUnitBoxLostFocus() ) ); + connect( prepMethodBox->lineEdit(), TQ_SIGNAL( lostFocus() ), this, TQ_SLOT( slotPrepMethodBoxLostFocus() ) ); + connect( orButton, TQ_SIGNAL( toggled(bool) ), this, TQ_SLOT( orToggled(bool) ) ); + + connect( unitBox->lineEdit(), TQ_SIGNAL( returnPressed() ), this, TQ_SLOT( signalIngredient() ) ); + connect( ingredientBox->lineEdit(), TQ_SIGNAL( returnPressed() ), this, TQ_SLOT( signalIngredient() ) ); + connect( headerBox->lineEdit(), TQ_SIGNAL( returnPressed() ), this, TQ_SLOT( signalIngredient() ) ); + connect( prepMethodBox->lineEdit(), TQ_SIGNAL( returnPressed() ), this, TQ_SLOT( signalIngredient() ) ); + connect( amountEdit, TQ_SIGNAL( returnPressed( const TQString & ) ), this, TQ_SLOT( signalIngredient() ) ); + + unitComboList = new UnitList; + + setFocusProxy( ingredientBox ); +} + +IngredientInput::~IngredientInput() +{ + delete unitComboList; + delete typeButtonGrp; +} + +void IngredientInput::clear() +{ + unitComboList->clear(); + + orButton->setChecked(false); + typeButtonGrp->setButton( 0 ); //put back to ingredient input + typeButtonClicked( 0 ); + + amountEdit->clear(); + ingredientBox->lineEdit()->setText(""); + prepMethodBox->lineEdit()->setText(""); + headerBox->lineEdit()->setText(""); + unitBox->lineEdit()->setText(""); +} + +void IngredientInput::orToggled(bool b) +{ + emit orToggled(b,this); +} + +void IngredientInput::reloadCombos() +{ + //these only needed to be loaded once + if ( ingredientBox->count() == 0 ) { + START_TIMER("Loading ingredient input auto-completion"); + ingredientBox->reload(); + END_TIMER(); + } + if ( headerBox->count() == 0 ) { + START_TIMER("Loading ingredient header input auto-completion"); + headerBox->reload(); + END_TIMER(); + } + if ( prepMethodBox->count() == 0 ) { + START_TIMER("Loading prep method input auto-completion"); + prepMethodBox->reload(); + END_TIMER(); + } + + loadUnitListCombo(); +} + +void IngredientInput::slotIngredientBoxLostFocus( void ) +{ + if ( ingredientBox->contains( ingredientBox->currentText() ) ) { + ingredientBox->setCurrentItem( ingredientBox->currentText() ); + loadUnitListCombo(); + } + else { + unitBox->clear(); + unitBox->completionObject() ->clear(); + unitComboList->clear(); + } +} + +void IngredientInput::slotUnitBoxLostFocus() +{ + if ( unitBox->contains( unitBox->currentText() ) ) + unitBox->setCurrentItem( unitBox->currentText() ); +} + +void IngredientInput::slotPrepMethodBoxLostFocus() +{ + if ( prepMethodBox->contains( prepMethodBox->currentText() ) ) + prepMethodBox->setCurrentItem( prepMethodBox->currentText() ); +} + +void IngredientInput::typeButtonClicked( int button_id ) +{ + if ( amountEdit->isEnabled() == !bool( button_id ) ) //it is already set (the same button was clicked more than once) + return ; + + amountEdit->setEnabled( !bool( button_id ) ); + unitBox->setEnabled( !bool( button_id ) ); + prepMethodBox->setEnabled( !bool( button_id ) ); + + if ( button_id == 1 ) { //Header + header_ing_stack->raiseWidget( headerBox ); + } + else { + header_ing_stack->raiseWidget( ingredientBox ); + } +} + +void IngredientInput::enableHeader( bool enable ) +{ + if ( !enable ) { + typeButtonGrp->setButton( 0 ); //put back to ingredient input + typeButtonClicked( 0 ); + } + typeButtonGrp->find(1)->setEnabled(enable); +} + +void IngredientInput::signalIngredient() +{ + //validate input; if successful, emit signal + if ( isHeader() ) { + if ( header().isEmpty() ) + return; + } + else { + if ( !isInputValid() ) + return; + } + + emit addIngredient(); +} + +bool IngredientInput::isInputValid() +{ + if ( ingredientBox->currentText().stripWhiteSpace().isEmpty() ) { + KMessageBox::error( this, i18n( "Please enter an ingredient" ), TQString::null ); + ingredientBox->setFocus(); + return false; + } + return checkAmountEdit() && checkBounds(); +} + +bool IngredientInput::checkBounds() +{ + if ( ingredientBox->currentText().length() > uint(database->maxIngredientNameLength()) ) { + KMessageBox::error( this, TQString( i18n( "Ingredient name cannot be longer than %1 characters." ) ).arg( database->maxIngredientNameLength() ) ); + ingredientBox->lineEdit() ->setFocus(); + ingredientBox->lineEdit() ->selectAll(); + return false; + } + + if ( unitBox->currentText().length() > uint(database->maxUnitNameLength()) ) { + KMessageBox::error( this, TQString( i18n( "Unit name cannot be longer than %1 characters." ) ).arg( database->maxUnitNameLength() ) ); + unitBox->lineEdit() ->setFocus(); + unitBox->lineEdit() ->selectAll(); + return false; + } + + TQStringList prepMethodList = TQStringList::split(",",prepMethodBox->currentText()); + for ( TQStringList::const_iterator it = prepMethodList.begin(); it != prepMethodList.end(); ++it ) { + if ( (*it).stripWhiteSpace().length() > uint(database->maxPrepMethodNameLength()) ) + { + KMessageBox::error( this, TQString( i18n( "Preparation method cannot be longer than %1 characters." ) ).arg( database->maxPrepMethodNameLength() ) ); + prepMethodBox->lineEdit() ->setFocus(); + prepMethodBox->lineEdit() ->selectAll(); + return false; + } + } + + return true; +} + +bool IngredientInput::checkAmountEdit() +{ + if ( amountEdit->isInputValid() ) + return true; + else { + KMessageBox::error( this, i18n( "Amount field contains invalid input." ), + i18n( "Invalid input" ) ); + amountEdit->setFocus(); + amountEdit->selectAll(); + return false; + } +} + +void IngredientInput::loadUnitListCombo() +{ + TQString store_unit = unitBox->currentText(); + unitBox->clear(); // Empty the combo first + unitBox->completionObject() ->clear(); + + int comboIndex = ingredientBox->currentItem(); + int comboCount = ingredientBox->count(); + + if ( comboCount > 0 ) { // If not, the list may be empty (no ingredient list defined) and crashes while reading + int selectedIngredient = ingredientBox->id( comboIndex ); + database->loadPossibleUnits( selectedIngredient, unitComboList ); + + //Populate this data into the ComboBox + for ( UnitList::const_iterator unit_it = unitComboList->begin(); unit_it != unitComboList->end(); ++unit_it ) { + unitBox->insertItem( ( *unit_it ).name ); + unitBox->completionObject() ->addItem( ( *unit_it ).name ); + if ( ( *unit_it ).name != (*unit_it ).plural ) { + unitBox->insertItem( ( *unit_it ).plural ); + unitBox->completionObject() ->addItem( ( *unit_it ).plural ); + } + + if ( !( *unit_it ).name_abbrev.isEmpty() ) { + unitBox->insertItem( ( *unit_it ).name_abbrev ); + unitBox->completionObject() ->addItem( ( *unit_it ).name_abbrev ); + } + if ( !(*unit_it ).plural_abbrev.isEmpty() && + ( *unit_it ).name_abbrev != (*unit_it ).plural_abbrev ) { + unitBox->insertItem( ( *unit_it ).plural_abbrev ); + unitBox->completionObject() ->addItem( ( *unit_it ).plural_abbrev ); + } + + } + } + unitBox->lineEdit() ->setText( store_unit ); +} + +bool IngredientInput::isHeader() const +{ + return typeButtonGrp && (typeButtonGrp->id( typeButtonGrp->selected() ) == 1); +} + +Ingredient IngredientInput::ingredient() const +{ + Ingredient ing; + + ing.prepMethodList = ElementList::split(",",prepMethodBox->currentText()); + ing.name = ingredientBox->currentText(); + amountEdit->value(ing.amount,ing.amount_offset); + ing.units = Unit(unitBox->currentText().stripWhiteSpace(),ing.amount+ing.amount_offset); + ing.ingredientID = ingredientBox->id( ingredientBox->currentItem() ); + + return ing; +} + +TQString IngredientInput::header() const +{ + return headerBox->currentText().stripWhiteSpace(); +} + +void IngredientInput::updateTabOrder() +{ + TQWidget::setTabOrder( ingredientBox, amountEdit ); + TQWidget::setTabOrder( amountEdit, unitBox ); + TQWidget::setTabOrder( unitBox, prepMethodBox ); + TQWidget::setTabOrder( prepMethodBox, orButton ); +} + + +IngredientInputWidget::IngredientInputWidget( RecipeDB *db, TQWidget *parent ) : TQVBox(parent), database(db) +{ + setSizePolicy( TQSizePolicy( TQSizePolicy::Expanding, TQSizePolicy::Fixed ) ); + + m_ingInputs.append(new IngredientInput(database,this)); + + // Connect signals & Slots + connect( m_ingInputs[0], TQ_SIGNAL(addIngredient()), this, TQ_SLOT(addIngredient()) ); + connect( m_ingInputs[0], TQ_SIGNAL(orToggled(bool,IngredientInput*)), this, TQ_SLOT(updateInputs(bool,IngredientInput*)) ); + + reloadCombos(); +} + +IngredientInputWidget::~IngredientInputWidget() +{ +} + +void IngredientInputWidget::clear() +{ + //clearing the first input deletes all substitute inputs + m_ingInputs[0]->clear(); +} + +void IngredientInputWidget::updateInputs(bool on, IngredientInput* input) +{ + TQValueList<IngredientInput*>::iterator curr = m_ingInputs.find(input); + IngredientInput *prev_input = *curr; + ++curr; + + if ( on ) { + IngredientInput *new_input = new IngredientInput(database,this,false); + new_input->reloadCombos(); + + TQWidget::setTabOrder( prev_input, new_input ); + new_input->updateTabOrder(); + + connect( new_input, TQ_SIGNAL(addIngredient()), this, TQ_SLOT(addIngredient()) ); + connect( new_input, TQ_SIGNAL(orToggled(bool,IngredientInput*)), this, TQ_SLOT(updateInputs(bool,IngredientInput*)) ); + + new_input->show(); + m_ingInputs.insert(curr,new_input); + + m_ingInputs[0]->enableHeader(false); + + } + else { + while ( curr != m_ingInputs.end() ) { + (*curr)->deleteLater(); + curr = m_ingInputs.remove(curr); + } + if ( m_ingInputs.count() == 1 ) + m_ingInputs[0]->enableHeader(true); + } +} + +void IngredientInputWidget::addIngredient() +{ + if ( m_ingInputs[0]->isHeader() ) { + TQString header = m_ingInputs[0]->header(); + if ( header.isEmpty() ) + return; + + int group_id = createNewGroupIfNecessary( header,database ); + emit headerEntered( Element(header,group_id) ); + } + else { + for ( TQValueList<IngredientInput*>::iterator it = m_ingInputs.begin(); it != m_ingInputs.end(); ++it ) { + if ( !(*it)->isInputValid() ) + return; + } + + TQValueList<IngredientData> list; + for ( TQValueList<IngredientInput*>::const_iterator it = m_ingInputs.begin(); it != m_ingInputs.end(); ++it ) { + Ingredient ing = (*it)->ingredient(); + ing.ingredientID = createNewIngredientIfNecessary(ing.name,database); + + bool plural = ing.amount+ing.amount_offset > 1; + ing.units.id = createNewUnitIfNecessary( (plural)?ing.units.plural:ing.units.name, plural, ing.ingredientID, ing.units,database ); + if ( ing.units.id == -1 ) // this will happen if the dialog to create a unit was cancelled + return ; + + TQValueList<int> prepIDs = createNewPrepIfNecessary( ing.prepMethodList,database ); + TQValueList<int>::const_iterator id_it = prepIDs.begin(); + for ( ElementList::iterator it = ing.prepMethodList.begin(); it != ing.prepMethodList.end(); ++it, ++id_it ) { + (*it).id = *id_it; + } + + list.append(ing); + } + + Ingredient ing = list.first(); + list.pop_front(); + ing.substitutes = list; + emit ingredientEntered( ing ); + } + clear(); + + m_ingInputs[0]->setFocus(); //put cursor back to the ingredient name so user can begin next ingredient +} + +int IngredientInputWidget::createNewIngredientIfNecessary( const TQString &ing, RecipeDB *database ) +{ + int id = -1; + if ( ing.isEmpty() ) + return -1; + + id = database->findExistingIngredientByName( ing ); + if ( id == -1 ) { + database->createNewIngredient( ing ); + id = database->lastInsertID(); + } + return id; +} + +int IngredientInputWidget::createNewUnitIfNecessary( const TQString &unit, bool plural, int ingredientID, Unit &new_unit, RecipeDB *database ) +{ + int id = database->findExistingUnitByName( unit ); + if ( -1 == id ) + { + CreateUnitDialog getUnit( 0, ( plural ) ? TQString::null : unit, ( !plural ) ? TQString::null : unit ); + if ( getUnit.exec() == TQDialog::Accepted ) { + new_unit = getUnit.newUnit(); + database->createNewUnit( new_unit ); + + id = database->lastInsertID(); + } + } + + if ( !database->ingredientContainsUnit( + ingredientID, + id ) ) + { + database->addUnitToIngredient( + ingredientID, + id ); + } + + new_unit = database->unitName( id ); + + //loadUnitListCombo(); + return id; +} + +TQValueList<int> IngredientInputWidget::createNewPrepIfNecessary( const ElementList &prepMethods, RecipeDB *database ) +{ + TQValueList<int> ids; + + if ( prepMethods.isEmpty() ) //no prep methods + return ids; + else + { + for ( ElementList::const_iterator it = prepMethods.begin(); it != prepMethods.end(); ++it ) { + int id = database->findExistingPrepByName( (*it).name.stripWhiteSpace() ); + if ( id == -1 ) + { + database->createNewPrepMethod( (*it).name.stripWhiteSpace() ); + id = database->lastInsertID(); + } + ids << id; + } + + return ids; + } +} + +int IngredientInputWidget::createNewGroupIfNecessary( const TQString &group, RecipeDB *database ) +{ + if ( group.stripWhiteSpace().isEmpty() ) //no group + return -1; + else + { + int id = database->findExistingIngredientGroupByName( group ); + if ( id == -1 ) //creating new + { + database->createNewIngGroup( group ); + id = database->lastInsertID(); + } + + return id; + } +} + +void IngredientInputWidget::reloadCombos() +{ + for ( TQValueList<IngredientInput*>::iterator it = m_ingInputs.begin(); it != m_ingInputs.end(); ++it ) + (*it)->reloadCombos(); +} + +#include "ingredientinputwidget.moc" diff --git a/src/widgets/ingredientinputwidget.h b/src/widgets/ingredientinputwidget.h new file mode 100644 index 0000000..ecc623a --- /dev/null +++ b/src/widgets/ingredientinputwidget.h @@ -0,0 +1,134 @@ +/*************************************************************************** +* Copyright (C) 2003-2005 by * +* Unai Garro ([email protected]) * +* Cyril Bosselut ([email protected]) * +* Jason Kivlighn ([email protected]) * +* * +* Copyright (C) 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. * +***************************************************************************/ + +#ifndef INGREDIENTINPUTWIDGET_H +#define INGREDIENTINPUTWIDGET_H + +#include <tqvbox.h> + +#include "datablocks/unit.h" + +class TQVBox; +class TQHBox; +class TQLabel; +class TQButtonGroup; +class TQWidgetStack; +class TQGroupBox; +class TQRadioButton; +class TQCheckBox; + +class KComboBox; + +class IngredientComboBox; +class HeaderComboBox; +class PrepMethodComboBox; +class RecipeDB; +class FractionInput; +class Ingredient; +class Element; +class ElementList; +class IngredientInput; + +class IngredientInputWidget : public TQVBox +{ +TQ_OBJECT + +public: + IngredientInputWidget( RecipeDB *db, TQWidget *parent ); + ~IngredientInputWidget(); + + void clear(); + + static int createNewIngredientIfNecessary( const TQString &ing, RecipeDB *db ); + static int createNewUnitIfNecessary( const TQString &unit, bool plural, int ingredient_id, Unit &new_unit, RecipeDB *db ); + static TQValueList<int> createNewPrepIfNecessary( const ElementList &prepMethods, RecipeDB *db ); + static int createNewGroupIfNecessary( const TQString &group, RecipeDB *db ); + +signals: + void ingredientEntered( const Ingredient &ing ); + + void headerEntered( const Element &header ); + +public slots: + void addIngredient(); + +private slots: + void updateInputs(bool,IngredientInput*); + +private: + /** Reloads lists of units, ingredients, and preparation methods */ + void reloadCombos(); + + void checkIfNewUnits(); + + RecipeDB *database; + + TQValueList<IngredientInput*> m_ingInputs; +}; + +class IngredientInput : public TQHBox +{ +TQ_OBJECT + +public: + IngredientInput( RecipeDB *db, TQWidget *parent, bool allowHeader = true ); + ~IngredientInput(); + + void clear(); + bool isInputValid(); + + bool isHeader() const; + Ingredient ingredient() const; + TQString header() const; + + void reloadCombos(); + void enableHeader( bool ); + void updateTabOrder(); + +signals: + void addIngredient(); + void orToggled(bool,IngredientInput*); + +private slots: + void loadUnitListCombo(); + void signalIngredient(); + void typeButtonClicked( int ); + void slotIngredientBoxLostFocus(); + void slotUnitBoxLostFocus(); + void slotPrepMethodBoxLostFocus(); + void orToggled(bool); + +private: + bool checkBounds(); + bool checkAmountEdit(); + + RecipeDB *database; + UnitList *unitComboList; + + TQCheckBox *orButton; + TQGroupBox *ingredientGBox; + TQLabel *amountLabel; + FractionInput* amountEdit; + TQLabel *unitLabel; + KComboBox* unitBox; + TQLabel *prepMethodLabel; + PrepMethodComboBox* prepMethodBox; + TQLabel *ingredientLabel; + IngredientComboBox* ingredientBox; + HeaderComboBox* headerBox; + TQWidgetStack *header_ing_stack; + TQButtonGroup *typeButtonGrp; +}; + +#endif //INGREDIENTINPUTWIDGET_H diff --git a/src/widgets/ingredientlistview.cpp b/src/widgets/ingredientlistview.cpp new file mode 100644 index 0000000..1343530 --- /dev/null +++ b/src/widgets/ingredientlistview.cpp @@ -0,0 +1,287 @@ +/*************************************************************************** +* Copyright (C) 2004 by * +* 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 "ingredientlistview.h" + +#include <tdemessagebox.h> +#include <tdeconfig.h> +#include <tdelocale.h> +#include <tdeglobal.h> +#include <kiconloader.h> +#include <tdepopupmenu.h> + +#include "backends/recipedb.h" +#include "dialogs/createelementdialog.h" +#include "dialogs/dependanciesdialog.h" + +IngredientCheckListItem::IngredientCheckListItem( IngredientCheckListView* qlv, const Element &ing ) : TQCheckListItem( qlv, TQString::null, TQCheckListItem::CheckBox ), + m_listview(qlv) +{ + // Initialize the ingredient data with the the property data + ingStored = new Element(); + ingStored->id = ing.id; + ingStored->name = ing.name; +} + +IngredientCheckListItem::IngredientCheckListItem( IngredientCheckListView* qlv, TQListViewItem *after, const Element &ing ) : TQCheckListItem( qlv, after, TQString::null, TQCheckListItem::CheckBox ), + m_listview(qlv) +{ + // Initialize the ingredient data with the the property data + ingStored = new Element(); + ingStored->id = ing.id; + ingStored->name = ing.name; +} + +IngredientCheckListItem::~IngredientCheckListItem( void ) +{ + delete ingStored; +} +int IngredientCheckListItem::id( void ) const +{ + return ingStored->id; +} +TQString IngredientCheckListItem::name( void ) const +{ + return ingStored->name; +} +Element IngredientCheckListItem::ingredient() const +{ + return *ingStored; +} + +TQString IngredientCheckListItem::text( int column ) const +{ + switch ( column ) { + case 0: + return ( ingStored->name ); + case 1: + return ( TQString::number( ingStored->id ) ); + default: + return TQString::null; + } +} + +void IngredientCheckListItem::stateChange( bool on ) +{ + m_listview->stateChange(this,on); +} + +IngredientListView::IngredientListView( TQWidget *parent, RecipeDB *db ) : DBListViewBase( parent,db, db->ingredientCount() ) +{ + setAllColumnsShowFocus( true ); + setDefaultRenameAction( TQListView::Reject ); +} + +void IngredientListView::init() +{ + connect( database, TQ_SIGNAL( ingredientCreated( const Element & ) ), TQ_SLOT( checkCreateIngredient( const Element & ) ) ); + connect( database, TQ_SIGNAL( ingredientRemoved( int ) ), TQ_SLOT( removeIngredient( int ) ) ); +} + +void IngredientListView::load( int limit, int offset ) +{ + ElementList ingredientList; + database->loadIngredients( &ingredientList, limit, offset ); + + setTotalItems(ingredientList.count()); + + for ( ElementList::const_iterator ing_it = ingredientList.begin(); ing_it != ingredientList.end(); ++ing_it ) + createIngredient( *ing_it ); +} + +void IngredientListView::checkCreateIngredient( const Element &el ) +{ + if ( handleElement(el.name) ) { //only create this ingredient if the base class okays it + createIngredient(el); + } +} + + +StdIngredientListView::StdIngredientListView( TQWidget *parent, RecipeDB *db, bool editable ) : IngredientListView( parent, db ) +{ + addColumn( i18n( "Ingredient" ) ); + + 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 ); + + 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->polish(); + + delete il; + + connect( this, TQ_SIGNAL( contextMenu( TDEListView *, TQListViewItem *, const TQPoint & ) ), TQ_SLOT( showPopup( TDEListView *, TQListViewItem *, const TQPoint & ) ) ); + connect( this, TQ_SIGNAL( doubleClicked( TQListViewItem* ) ), this, TQ_SLOT( modIngredient( TQListViewItem* ) ) ); + connect( this, TQ_SIGNAL( itemRenamed( TQListViewItem* ) ), this, TQ_SLOT( saveIngredient( TQListViewItem* ) ) ); + } +} + +void StdIngredientListView::showPopup( TDEListView * /*l*/, TQListViewItem *i, const TQPoint &p ) +{ + if ( i ) + kpop->exec( p ); +} + +void StdIngredientListView::createNew() +{ + CreateElementDialog * elementDialog = new CreateElementDialog( this, i18n( "New Ingredient" ) ); + + if ( elementDialog->exec() == TQDialog::Accepted ) { + TQString result = elementDialog->newElementName(); + + if ( checkBounds( result ) ) + database->createNewIngredient( result ); // Create the new author in the database + } +} + +void StdIngredientListView::remove + () +{ + TQListViewItem * it = currentItem(); + + if ( it ) { + int ingredientID = it->text( 1 ).toInt(); + + ElementList dependingRecipes; + database->findIngredientDependancies( ingredientID, &dependingRecipes ); + + if ( dependingRecipes.isEmpty() ) + database->removeIngredient( ingredientID ); + else { // Need Warning! + ListInfo list; + list.list = dependingRecipes; + list.name = i18n( "Recipes" ); + + DependanciesDialog warnDialog( this, list ); + warnDialog.setCustomWarning( i18n("You are about to permanantly delete recipes from your database.") ); + if ( warnDialog.exec() == TQDialog::Accepted ) + database->removeIngredient( ingredientID ); + } + } +} + +void StdIngredientListView::rename() +{ + TQListViewItem * item = currentItem(); + + if ( item ) + IngredientListView::rename( item, 0 ); +} + +void StdIngredientListView::createIngredient( const Element &ing ) +{ + createElement(new TQListViewItem( this, ing.name, TQString::number( ing.id ) )); +} + +void StdIngredientListView::removeIngredient( int id ) +{ + TQListViewItem * item = findItem( TQString::number( id ), 1 ); + removeElement(item); +} + +void StdIngredientListView::modIngredient( TQListViewItem* i ) +{ + if ( i ) + IngredientListView::rename( i, 0); +} + +void StdIngredientListView::saveIngredient( TQListViewItem* i ) +{ + if ( !checkBounds( i->text( 0 ) ) ) { + reload(ForceReload); //reset the changed text + return ; + } + + int existing_id = database->findExistingIngredientByName( i->text( 0 ) ); + int ing_id = i->text( 1 ).toInt(); + if ( existing_id != -1 && existing_id != ing_id ) //category already exists with this label... merge the two + { + switch ( KMessageBox::warningContinueCancel( this, i18n( "This ingredient already exists. Continuing will merge these two ingredients into one. Are you sure?" ) ) ) + { + case KMessageBox::Continue: { + database->mergeIngredients( existing_id, ing_id ); + break; + } + default: + reload(ForceReload); + break; //we have to reload because the ingredient was renamed, and we need to reset it + } + } + else { + database->modIngredient( ( i->text( 1 ) ).toInt(), i->text( 0 ) ); + } +} + +bool StdIngredientListView::checkBounds( const TQString &name ) +{ + if ( name.length() > uint(database->maxIngredientNameLength()) ) { + KMessageBox::error( this, TQString( i18n( "Ingredient name cannot be longer than %1 characters." ) ).arg( database->maxIngredientNameLength() ) ); + return false; + } + + return true; +} + + + +IngredientCheckListView::IngredientCheckListView( TQWidget *parent, RecipeDB *db ) : IngredientListView( parent, db ) +{ + addColumn( i18n( "Ingredient" ) ); + + TDEConfig *config = TDEGlobal::config(); + config->setGroup( "Advanced" ); + bool show_id = config->readBoolEntry( "ShowID", false ); + addColumn( i18n( "Id" ), show_id ? -1 : 0 ); +} + +void IngredientCheckListView::createIngredient( const Element &ing ) +{ + createElement(new IngredientCheckListItem( this, ing )); +} + +void IngredientCheckListView::removeIngredient( int id ) +{ + TQListViewItem * item = findItem( TQString::number( id ), 1 ); + removeElement(item); +} + +void IngredientCheckListView::load( int limit, int offset ) +{ + IngredientListView::load(limit,offset); + + for ( TQValueList<Element>::const_iterator ing_it = m_selections.begin(); ing_it != m_selections.end(); ++ing_it ) { + TQCheckListItem * item = ( TQCheckListItem* ) findItem( TQString::number( (*ing_it).id ), 1 ); + if ( item ) { + item->setOn(true); + } + } +} + +void IngredientCheckListView::stateChange(IngredientCheckListItem *it,bool on) +{ + if ( !reloading() ) { + if ( on ) + m_selections.append(it->ingredient()); + else + m_selections.remove(it->ingredient()); + } +} + +#include "ingredientlistview.moc" diff --git a/src/widgets/ingredientlistview.h b/src/widgets/ingredientlistview.h new file mode 100644 index 0000000..58990c4 --- /dev/null +++ b/src/widgets/ingredientlistview.h @@ -0,0 +1,119 @@ +/*************************************************************************** +* 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. * +***************************************************************************/ + +#ifndef INGREDIENTLISTVIEW_H +#define INGREDIENTLISTVIEW_H + +#include "dblistviewbase.h" + +#include "datablocks/element.h" + +class RecipeDB; +class TDEPopupMenu; +class IngredientCheckListView; + +/** +@author Unai Garro +*/ +class IngredientCheckListItem: public TQCheckListItem +{ +public: + IngredientCheckListItem( IngredientCheckListView* qlv, const Element &ing ); + IngredientCheckListItem( IngredientCheckListView* qlv, TQListViewItem *after, const Element &ing ); + ~IngredientCheckListItem( void ); + + int id( void ) const; + TQString name( void ) const; + Element ingredient() const; + + virtual TQString text( int column ) const; + +protected: + virtual void stateChange( bool on ); + +private: + Element *ingStored; + IngredientCheckListView *m_listview; +}; + + + +class IngredientListView : public DBListViewBase +{ + TQ_OBJECT + +public: + IngredientListView( TQWidget *parent, RecipeDB *db ); + +protected slots: + virtual void createIngredient( const Element & ) = 0; + virtual void removeIngredient( int ) = 0; + virtual void load(int limit,int offset); + +protected: + virtual void init(); + +private slots: + virtual void checkCreateIngredient( const Element & ); +}; + + + +class StdIngredientListView : public IngredientListView +{ + TQ_OBJECT + +public: + StdIngredientListView( TQWidget *parent, RecipeDB *db, bool editable = false ); + +protected: + virtual void createIngredient( const Element & ); + virtual void removeIngredient( int ); + +private slots: + void showPopup( TDEListView *, TQListViewItem *, const TQPoint & ); + + void createNew(); + void remove + (); + void rename(); + + void modIngredient( TQListViewItem* i ); + void saveIngredient( TQListViewItem* i ); + +private: + bool checkBounds( const TQString &name ); + + TDEPopupMenu *kpop; +}; + + + +class IngredientCheckListView : public IngredientListView +{ +public: + IngredientCheckListView( TQWidget *parent, RecipeDB *db ); + + virtual void stateChange(IngredientCheckListItem *,bool); + + TQValueList<Element> selections() const{ return m_selections; } + +protected: + virtual void createIngredient( const Element &ing ); + virtual void removeIngredient( int ); + + virtual void load( int limit, int offset ); + +private: + TQValueList<Element> m_selections; +}; + +#endif //INGREDIENTLISTVIEW_H diff --git a/src/widgets/kdateedit.cpp b/src/widgets/kdateedit.cpp new file mode 100644 index 0000000..8becc83 --- /dev/null +++ b/src/widgets/kdateedit.cpp @@ -0,0 +1,388 @@ +/* + This file is part of libtdepim. + + Copyright (c) 2002 Cornelius Schumacher <[email protected]> + Copyright (c) 2003-2004 Reinhold Kainhofer <[email protected]> + Copyright (c) 2004 Tobias Koenig <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, + Boston, MA 02110-1301 USA +*/ + +#include <tqapplication.h> +#include <tqlineedit.h> +#include <tqlistbox.h> +#include <tqvalidator.h> + +//#include <kcalendarsystem.h> +#include <tdeglobal.h> +#include <tdeglobalsettings.h> +#include <tdelocale.h> +#include <tdeconfig.h> + +#include "kdateedit.h" + +TQRect desktopGeometry(TQWidget* w) +{ + TQDesktopWidget *dw = TQApplication::desktop(); + if (dw->isVirtualDesktop()) { + TDEConfigGroup group(TDEGlobal::config(), "Windows"); + if (group.readBoolEntry("XineramaEnabled", true) && + group.readBoolEntry("XineramaPlacementEnabled", true)) { + if (w) + return dw->screenGeometry(dw->screenNumber(w)); + else return dw->screenGeometry(-1); + } else { + return dw->geometry(); + } + } else { + return dw->geometry(); + } +} + +class DateValidator : public TQValidator +{ + public: + DateValidator( const TQStringList &keywords, TQWidget* parent, const char* name = 0 ) + : TQValidator( parent, name ), mKeywords( keywords ) + {} + + virtual State validate( TQString &str, int& ) const + { + int length = str.length(); + + // empty string is intermediate so one can clear the edit line and start from scratch + if ( length <= 0 ) + return Intermediate; + + if ( mKeywords.contains( str.lower() ) ) + return Acceptable; + + bool ok = false; + TDEGlobal::locale()->readDate( str, &ok ); + if ( ok ) + return Acceptable; + else + return Intermediate; + } + + private: + TQStringList mKeywords; +}; + +KDateEdit::KDateEdit( TQWidget *parent, const char *name ) + : TQComboBox( true, parent, name ), + mReadOnly( false ), + mDiscardNextMousePress( false ) +{ + // need at least one entry for popup to work + setMaxCount( 1 ); + + mDate = TQDate::currentDate(); + TQString today = TDEGlobal::locale()->formatDate( mDate, true ); + + insertItem( today ); + setCurrentItem( 0 ); + changeItem( today, 0 ); + setMinimumSize( sizeHint() ); + + connect( lineEdit(), TQ_SIGNAL( returnPressed() ), + this, TQ_SLOT( lineEnterPressed() ) ); + connect( this, TQ_SIGNAL( textChanged( const TQString& ) ), + TQ_SLOT( slotTextChanged( const TQString& ) ) ); + + mPopup = new KDatePickerPopup( KDatePickerPopup::DatePicker | KDatePickerPopup::Words ); + mPopup->hide(); + mPopup->installEventFilter( this ); + + connect( mPopup, TQ_SIGNAL( dateChanged( TQDate ) ), + TQ_SLOT( dateSelected( TQDate ) ) ); + + // handle keyword entry + setupKeywords(); + lineEdit()->installEventFilter( this ); + + setValidator( new DateValidator( mKeywordMap.keys(), this ) ); + + mTextChanged = false; +} + +KDateEdit::~KDateEdit() +{ + delete mPopup; + mPopup = 0; +} + +void KDateEdit::setDate( const TQDate& date ) +{ + assignDate( date ); + updateView(); +} + +TQDate KDateEdit::date() const +{ + return mDate; +} + +void KDateEdit::setReadOnly( bool readOnly ) +{ + mReadOnly = readOnly; + lineEdit()->setReadOnly( readOnly ); +} + +bool KDateEdit::isReadOnly() const +{ + return mReadOnly; +} + +void KDateEdit::popup() +{ + if ( mReadOnly ) + return; + + TQRect desk = desktopGeometry( this ); + + TQPoint popupPoint = mapToGlobal( TQPoint( 0,0 ) ); + + int dateFrameHeight = mPopup->sizeHint().height(); + if ( popupPoint.y() + height() + dateFrameHeight > desk.bottom() ) + popupPoint.setY( popupPoint.y() - dateFrameHeight ); + else + popupPoint.setY( popupPoint.y() + height() ); + + int dateFrameWidth = mPopup->sizeHint().width(); + if ( popupPoint.x() + dateFrameWidth > desk.right() ) + popupPoint.setX( desk.right() - dateFrameWidth ); + + if ( popupPoint.x() < desk.left() ) + popupPoint.setX( desk.left() ); + + if ( popupPoint.y() < desk.top() ) + popupPoint.setY( desk.top() ); + + if ( mDate.isValid() ) + mPopup->setDate( mDate ); + else + mPopup->setDate( TQDate::currentDate() ); + + mPopup->popup( popupPoint ); + + // The combo box is now shown pressed. Make it show not pressed again + // by causing its (invisible) list box to emit a 'selected' signal. + // First, ensure that the list box contains the date currently displayed. + TQDate date = parseDate(); + assignDate( date ); + updateView(); + // Now, simulate an Enter to unpress it + TQListBox *lb = listBox(); + if (lb) { + lb->setCurrentItem(0); + TQKeyEvent* keyEvent = new TQKeyEvent(TQEvent::KeyPress, TQt::Key_Enter, 0, 0); + TQApplication::postEvent(lb, keyEvent); + } +} + +void KDateEdit::dateSelected( TQDate date ) +{ + if (assignDate( date ) ) { + updateView(); + emit dateChanged( date ); + + if ( date.isValid() ) { + mPopup->hide(); + } + } +} + +void KDateEdit::dateEntered( TQDate date ) +{ + if (assignDate( date ) ) { + updateView(); + emit dateChanged( date ); + } +} + +void KDateEdit::lineEnterPressed() +{ + bool replaced = false; + + TQDate date = parseDate( &replaced ); + + if (assignDate( date ) ) { + if ( replaced ) + updateView(); + + emit dateChanged( date ); + } +} + +TQDate KDateEdit::parseDate( bool *replaced ) const +{ + TQString text = currentText(); + TQDate result; + + if ( replaced ) + (*replaced) = false; + + if ( text.isEmpty() ) + result = TQDate(); + else if ( mKeywordMap.contains( text.lower() ) ) { + TQDate today = TQDate::currentDate(); + int i = mKeywordMap[ text.lower() ]; + if ( i >= 100 ) { + /* A day name has been entered. Convert to offset from today. + * This uses some math tricks to figure out the offset in days + * to the next date the given day of the week occurs. There + * are two cases, that the new day is >= the current day, which means + * the new day has not occurred yet or that the new day < the current day, + * which means the new day is already passed (so we need to find the + * day in the next week). + */ + i -= 100; + int currentDay = today.dayOfWeek(); + if ( i >= currentDay ) + i -= currentDay; + else + i += 7 - currentDay; + } + + result = today.addDays( i ); + if ( replaced ) + (*replaced) = true; + } else { + result = TDEGlobal::locale()->readDate( text ); + } + + return result; +} + +bool KDateEdit::eventFilter( TQObject *object, TQEvent *event ) +{ + if ( object == lineEdit() ) { + // We only process the focus out event if the text has changed + // since we got focus + if ( (event->type() == TQEvent::FocusOut) && mTextChanged ) { + lineEnterPressed(); + mTextChanged = false; + } else if ( event->type() == TQEvent::KeyPress ) { + // Up and down arrow keys step the date + TQKeyEvent* keyEvent = (TQKeyEvent*)event; + + if ( keyEvent->key() == TQt::Key_Return ) { + lineEnterPressed(); + return true; + } + + int step = 0; + if ( keyEvent->key() == TQt::Key_Up ) + step = 1; + else if ( keyEvent->key() == TQt::Key_Down ) + step = -1; + if ( step && !mReadOnly ) { + TQDate date = parseDate(); + if ( date.isValid() ) { + date = date.addDays( step ); + if ( assignDate( date ) ) { + updateView(); + emit dateChanged( date ); + return true; + } + } + } + } + } else { + // It's a date picker event + switch ( event->type() ) { + case TQEvent::MouseButtonDblClick: + case TQEvent::MouseButtonPress: { + TQMouseEvent *mouseEvent = (TQMouseEvent*)event; + if ( !mPopup->rect().contains( mouseEvent->pos() ) ) { + TQPoint globalPos = mPopup->mapToGlobal( mouseEvent->pos() ); + if ( TQApplication::widgetAt( globalPos, true ) == this ) { + // The date picker is being closed by a click on the + // KDateEdit widget. Avoid popping it up again immediately. + mDiscardNextMousePress = true; + } + } + + break; + } + default: + break; + } + } + + return false; +} + +void KDateEdit::mousePressEvent( TQMouseEvent *event ) +{ + if ( event->button() == TQt::LeftButton && mDiscardNextMousePress ) { + mDiscardNextMousePress = false; + return; + } + + TQComboBox::mousePressEvent( event ); +} + +void KDateEdit::slotTextChanged( const TQString& ) +{ + TQDate date = parseDate(); + + if ( assignDate( date ) ) + emit dateChanged( date ); + + mTextChanged = true; +} + +void KDateEdit::setupKeywords() +{ + // Create the keyword list. This will be used to match against when the user + // enters information. + mKeywordMap.insert( i18n( "tomorrow" ), 1 ); + mKeywordMap.insert( i18n( "today" ), 0 ); + mKeywordMap.insert( i18n( "yesterday" ), -1 ); + + #if 0 //depends on KDE 3.2 + TQString dayName; + for ( int i = 1; i <= 7; ++i ) { + dayName = TDEGlobal::locale()->calendar()->weekDayName( i ).lower(); + mKeywordMap.insert( dayName, i + 100 ); + } + #endif +} + +bool KDateEdit::assignDate( const TQDate& date ) +{ + mDate = date; + mTextChanged = false; + return true; +} + +void KDateEdit::updateView() +{ + TQString dateString; + if ( mDate.isValid() ) + dateString = TDEGlobal::locale()->formatDate( mDate, true ); + + // We do not want to generate a signal here, + // since we explicitly setting the date + bool blocked = signalsBlocked(); + blockSignals( true ); + changeItem( dateString, 0 ); + blockSignals( blocked ); +} + +#include "kdateedit.moc" diff --git a/src/widgets/kdateedit.h b/src/widgets/kdateedit.h new file mode 100644 index 0000000..e3957b2 --- /dev/null +++ b/src/widgets/kdateedit.h @@ -0,0 +1,139 @@ +/* + This file is part of libtdepim. + + Copyright (c) 2002 Cornelius Schumacher <[email protected]> + Copyright (c) 2004 Tobias Koenig <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, + Boston, MA 02110-1301 USA +*/ + +#ifndef KDATEEDIT_H +#define KDATEEDIT_H + +#include <tqcombobox.h> +#include <tqdatetime.h> +#include <tqmap.h> + +#include "kdatepickerpopup.h" + +class TQEvent; + +/** + A date editing widget that consists of an editable combo box. + The combo box contains the date in text form, and clicking the combo + box arrow will display a 'popup' style date picker. + + This widget also supports advanced features like allowing the user + to type in the day name to get the date. The following keywords + are supported (in the native language): tomorrow, yesturday, today, + monday, tuesday, wednesday, thursday, friday, saturday, sunday. + + @image html kdateedit.png "This is how it looks" + + @author Cornelius Schumacher <[email protected]> + @author Mike Pilone <[email protected]> + @author David Jarvie <[email protected]> + @author Tobias Koenig <[email protected]> +*/ +class KDateEdit : public TQComboBox +{ + TQ_OBJECT + + public: + KDateEdit( TQWidget *parent = 0, const char *name = 0 ); + virtual ~KDateEdit(); + + /** + @return The date entered. This date could be invalid, + you have to check validity yourself. + */ + TQDate date() const; + + /** + Sets whether the widget is read-only for the user. If read-only, + the date picker pop-up is inactive, and the displayed date cannot be edited. + + @param readOnly True to set the widget read-only, false to set it read-write. + */ + void setReadOnly( bool readOnly ); + + /** + @return True if the widget is read-only, false if read-write. + */ + bool isReadOnly() const; + + virtual void popup(); + + signals: + /** + This signal is emitted whenever the user modifies the date. + The passed date can be invalid. + */ + void dateChanged( const TQDate &date ); + + public slots: + /** + Sets the date. + + @param date The new date to display. This date must be valid or + it will not be set + */ + void setDate( const TQDate &date ); + + protected slots: + void lineEnterPressed(); + void slotTextChanged( const TQString& ); + void dateEntered( TQDate ); + void dateSelected( TQDate ); + + protected: + virtual bool eventFilter( TQObject*, TQEvent* ); + virtual void mousePressEvent( TQMouseEvent* ); + + /** + Sets the date, without altering the display. + This method is used internally to set the widget's date value. + As a virtual method, it allows derived classes to perform additional validation + on the date value before it is set. Derived classes should return true if + TQDate::isValid(@p date) returns false. + + @param date The new date to set. + @return True if the date was set, false if it was considered invalid and + remains unchanged. + */ + virtual bool assignDate( const TQDate &date ); + + /** + Fills the keyword map. Reimplement it if you want additional + keywords. + */ + void setupKeywords(); + + private: + TQDate parseDate( bool* = 0 ) const; + void updateView(); + + KDatePickerPopup *mPopup; + + TQDate mDate; + bool mReadOnly; + bool mTextChanged; + bool mDiscardNextMousePress; + + TQMap<TQString, int> mKeywordMap; +}; + +#endif diff --git a/src/widgets/kdatepickerpopup.cpp b/src/widgets/kdatepickerpopup.cpp new file mode 100644 index 0000000..da68918 --- /dev/null +++ b/src/widgets/kdatepickerpopup.cpp @@ -0,0 +1,123 @@ +/* + This file is part of libtdepim. + + Copyright (c) 2004 Bram Schoenmakers <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, + Boston, MA 02110-1301 USA +*/ + +#include <tqdatetime.h> +#include <tqpopupmenu.h> + +#include <tdelocale.h> + +#include "kdatepickerpopup.h" + +KDatePickerPopup::KDatePickerPopup( int items, const TQDate &date, TQWidget *parent, + const char *name ) + : TQPopupMenu( parent, name ) +{ + mItems = items; + + mDatePicker = new KDatePicker( this ); + mDatePicker->setCloseButton( false ); + + connect( mDatePicker, TQ_SIGNAL( dateEntered( TQDate ) ), + TQ_SLOT( slotDateChanged( TQDate ) ) ); + connect( mDatePicker, TQ_SIGNAL( dateSelected( TQDate ) ), + TQ_SLOT( slotDateChanged( TQDate ) ) ); + + mDatePicker->setDate( date ); + + buildMenu(); +} + +void KDatePickerPopup::buildMenu() +{ + if ( isVisible() ) return; + clear(); + + if ( mItems & DatePicker ) { + insertItem( mDatePicker ); + + if ( ( mItems & NoDate ) || ( mItems & Words ) ) + insertSeparator(); + } + + if ( mItems & Words ) { + insertItem( i18n("&Today"), this, TQ_SLOT( slotToday() ) ); + insertItem( i18n("&Yesterday"), this, TQ_SLOT( slotYesterday() ) ); + insertItem( i18n("Last &Week"), this, TQ_SLOT( slotLastWeek() ) ); + insertItem( i18n("Last M&onth"), this, TQ_SLOT( slotLastMonth() ) ); + + if ( mItems & NoDate ) + insertSeparator(); + } + + if ( mItems & NoDate ) + insertItem( i18n("No Date"), this, TQ_SLOT( slotNoDate() ) ); +} + +KDatePicker *KDatePickerPopup::datePicker() const +{ + return mDatePicker; +} + +void KDatePickerPopup::setDate( const TQDate &date ) +{ + mDatePicker->setDate( date ); +} + +#if 0 +void KDatePickerPopup::setItems( int items ) +{ + mItems = items; + buildMenu(); +} +#endif + +void KDatePickerPopup::slotDateChanged( TQDate date ) +{ + emit dateChanged( date ); + hide(); +} + +void KDatePickerPopup::slotToday() +{ + emit dateChanged( TQDate::currentDate() ); +} + +void KDatePickerPopup::slotYesterday() +{ + emit dateChanged( TQDate::currentDate().addDays( -1 ) ); +} + +void KDatePickerPopup::slotNoDate() +{ + emit dateChanged( TQDate() ); +} + +void KDatePickerPopup::slotLastWeek() +{ + emit dateChanged( TQDate::currentDate().addDays( -7 ) ); +} + +void KDatePickerPopup::slotLastMonth() +{ + emit dateChanged( TQDate::currentDate().addMonths( -1 ) ); +} + +#include "kdatepickerpopup.moc" diff --git a/src/widgets/kdatepickerpopup.h b/src/widgets/kdatepickerpopup.h new file mode 100644 index 0000000..67f36dd --- /dev/null +++ b/src/widgets/kdatepickerpopup.h @@ -0,0 +1,102 @@ +/* + This file is part of libtdepim. + + Copyright (c) 2004 Bram Schoenmakers <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, + Boston, MA 02110-1301 USA +*/ +#ifndef KDATEPICKERPOPUP_H +#define KDATEPICKERPOPUP_H + +#include <tqdatetime.h> +#include <tqpopupmenu.h> + +#include <kdatepicker.h> + +/** + @short This menu helps the user to select a date quickly. + + This menu helps the user to select a date tquicly. It offers various ways of selecting, e.g. with a KDatePicker or with words like "Tomorrow". + + The available items are: + + @li NoDate: A menu-item with "No Date". If choosen, the datepicker will emit a null TQDate. + @li DatePicker: Show a KDatePicker-widget. + @li Words: Show items like "Today", "Tomorrow" or "Next Week". + + When supplying multiple items, separate each item with a bitwise OR. + + @author Bram Schoenmakers <[email protected]> +*/ +class KDatePickerPopup: public TQPopupMenu +{ + TQ_OBJECT + public: + enum { NoDate = 1, DatePicker = 2, Words = 4 }; + + /** + A constructor for the KDatePickerPopup. + + @param items List of all desirable items, separated with a bitwise OR. + @param date Initial date of datepicker-widget. + @param parent The object's parent. + @param name The object's name. + */ + KDatePickerPopup( int items = 2, const TQDate &date = TQDate::currentDate(), + TQWidget *parent = 0, const char *name = 0 ); + + /** + @return A pointer to the private variable mDatePicker, an instance of + KDatePicker. + */ + KDatePicker *datePicker() const; + + void setDate( const TQDate &date ); + +#if 0 + /** Set items which should be shown and rebuilds the menu afterwards. Only if the menu is not visible. + @param items List of all desirable items, separated with a bitwise OR. + */ + void setItems( int items = 1 ); +#endif + /** @return Returns the bitwise result of the active items in the popup. */ + int items() const { return mItems; } + + signals: + + /** + This signal emits the new date (selected with datepicker or other + menu-items). + */ + void dateChanged ( TQDate ); + + protected slots: + void slotDateChanged ( TQDate ); + + void slotToday(); + void slotYesterday(); + void slotLastWeek(); + void slotLastMonth(); + void slotNoDate(); + + private: + void buildMenu(); + + KDatePicker *mDatePicker; + int mItems; +}; + +#endif diff --git a/src/widgets/krelistview.cpp b/src/widgets/krelistview.cpp new file mode 100644 index 0000000..3c06216 --- /dev/null +++ b/src/widgets/krelistview.cpp @@ -0,0 +1,116 @@ +/*************************************************************************** +* Copyright (C) 2004-2005 by * +* Jason Kivlighn ([email protected]) * +* * +* Copyright (C) 2003 by * +* 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 "krelistview.h" + +#include <tdeglobalsettings.h> +#include <tdelocale.h> +#include <kdebug.h> + +#include "widgets/dblistviewbase.h" + +KreListView::KreListView( TQWidget *parent, const TQString &title, bool filter, int filterCol, TQWidget *embeddedWidget ) : TQVBox( parent ) +{ + + filteredColumn = filterCol; + TQWidget *header = this; + if ( filter || embeddedWidget ) { + header = new TQHBox( this ); + ( ( TQHBox* ) header ) ->setSpacing( 15 ); + } + + if ( !title.isNull() ) { + listLabel = new TQLabel( header ); + listLabel->setFrameShape( TQFrame::GroupBoxPanel ); + listLabel->setFrameShadow( TQFrame::Sunken ); + listLabel->setPaletteForegroundColor( TDEGlobalSettings::highlightedTextColor() ); + listLabel->setPaletteBackgroundColor( TDEGlobalSettings::highlightColor().light( 120 ) ); // 120, to match the kremenu settings + listLabel->setText( title ); + + } + + if ( filter ) { + filterBox = new TQHBox( header ); + filterBox->setFrameShape( TQFrame::Box ); + filterBox->setMargin( 2 ); + filterLabel = new TQLabel( filterBox ); + filterLabel->setText( i18n( "Search:" ) ); + filterEdit = new KLineEdit( filterBox ); + } + + + list = new TDEListView( this ); + list->setSizePolicy( TQSizePolicy::MinimumExpanding, TQSizePolicy::MinimumExpanding ); + setSizePolicy( TQSizePolicy::MinimumExpanding, TQSizePolicy::MinimumExpanding ); + setSpacing( 10 ); + + + // If the user provides a widget, embed it into the header + if ( embeddedWidget ) + embeddedWidget->reparent( header, TQPoint( 0, 0 ) ); + //Connect Signals & Slots + if ( filter ) { + connect( filterEdit, TQ_SIGNAL( textChanged( const TQString& ) ), TQ_SIGNAL( textChanged(const TQString&) ) ); + connect( this, TQ_SIGNAL( textChanged( const TQString& ) ), TQ_SLOT( filter( const TQString& ) ) ); + } +} + +KreListView::~KreListView() +{} + +void KreListView::filter( const TQString& s ) +{ + for ( TQListViewItem * it = list->firstChild();it;it = it->nextSibling() ) { + if ( it->rtti() == NEXTLISTITEM_RTTI || it->rtti() == PREVLISTITEM_RTTI ) + continue; + + if ( s.isEmpty() ) // Don't filter if the filter text is empty + { + it->setVisible( true ); + } + else + { + + if ( it->text( filteredColumn ).contains( s, false ) ) + it->setVisible( true ); + else + it->setVisible( false ); + + } + + + } +} + +void KreListView::refilter() +{ + if ( !filterEdit->text().isEmpty() ) { + emit textChanged( filterEdit->text() ); + } +} + +void KreListView::setCustomFilter( TQObject *receiver, const char *slot ) +{ + connect( this, TQ_SIGNAL( textChanged( const TQString& ) ), receiver, slot ); +} + +void KreListView::setListView( DBListViewBase *list_view ) +{ + delete list; + + connect( list_view, TQ_SIGNAL( nextGroupLoaded() ), TQ_SLOT( refilter() ) ); + connect( list_view, TQ_SIGNAL( prevGroupLoaded() ), TQ_SLOT( refilter() ) ); + list = list_view; +} + +#include "krelistview.moc" diff --git a/src/widgets/krelistview.h b/src/widgets/krelistview.h new file mode 100644 index 0000000..0884bda --- /dev/null +++ b/src/widgets/krelistview.h @@ -0,0 +1,65 @@ +/*************************************************************************** +* Copyright (C) 2003 by * +* 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. * +***************************************************************************/ + +#ifndef KRELISTVIEW_H +#define KRELISTVIEW_H + +#include <tqlabel.h> +#include <tqvbox.h> +#include <tdelistview.h> +#include <klineedit.h> + +class DBListViewBase; + +/** +@author Unai Garro +*/ + +class KreListView: public TQVBox +{ + TQ_OBJECT +public: + + KreListView( TQWidget *parent, const TQString &title = TQString::null, bool filter = false, int filterCol = 0, TQWidget *embeddedWidget = 0 ); + ~KreListView(); + TDEListView *listView() + { + return list; + } + + void setListView( TDEListView *list_view ) + { + delete list; + list = list_view; + } + void setListView( DBListViewBase *list_view ); + + void setCustomFilter( TQObject *receiver, const char *slot ); + TQString filterText() const { return filterEdit->text(); } + +public slots: + void refilter(); + +signals: + void textChanged( const TQString & ); + +private: + TQHBox *filterBox; + TQLabel *listLabel; + int filteredColumn; + TQLabel *filterLabel; + KLineEdit *filterEdit; + TDEListView *list; + +private slots: + void filter( const TQString& s ); +}; + +#endif diff --git a/src/widgets/kremenu.cpp b/src/widgets/kremenu.cpp new file mode 100644 index 0000000..365fba9 --- /dev/null +++ b/src/widgets/kremenu.cpp @@ -0,0 +1,631 @@ +/*************************************************************************** +* Copyright (C) 2003 by * +* Unai Garro ([email protected]) * +* Cyril Bosselut ([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 publishfed by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +***************************************************************************/ +#include "kremenu.h" + +#include <tqbitmap.h> +#include <tqcursor.h> +#include <tqfont.h> +#include <tqimage.h> +#include <tqobjectlist.h> +#include <tqpainter.h> +#include <tqpixmap.h> +#include <tqsignalmapper.h> + +#include <tdeapplication.h> +#include <kcursor.h> +#include <kdebug.h> +#include <tdeglobalsettings.h> +#include <kiconloader.h> +#include <kimageeffect.h> +#include <tdelocale.h> +#include <kpixmap.h> +#include <kpixmapeffect.h> + +KreMenu::KreMenu( TQWidget *parent, const char *name ) : + TQWidget( parent, name, TQt::WNoAutoErase ) +{ + Menu newMenu; + + mainMenuId = menus.append( newMenu ); + + currentMenuId = mainMenuId; + m_currentMenu = &( *currentMenuId ); + + setMouseTracking( true ); + setFocusPolicy( TQWidget::StrongFocus ); + setSizePolicy( TQSizePolicy::Fixed, TQSizePolicy::Preferred ); +} + + +KreMenu::~KreMenu() +{} + +void KreMenu::childEvent ( TQChildEvent *e ) +{ + if ( e->type() == TQChildEvent::ChildInserted ) { + + TQObject * child = e->child(); + if ( child->inherits( "KreMenuButton" ) ) { + KreMenuButton * button = ( KreMenuButton* ) child; + + Menu *buttonMenu = &( *( button->menuId ) ); + + + + if ( !( buttonMenu->activeButton ) ) // Highlight the button if it's the first in the menu + { + button->setActive( true ); + buttonMenu->activeButton = button; + } + + buttonMenu->addButton( button ); + + if ( buttonMenu != m_currentMenu ) + button->hide(); + else + button->show(); + + connect ( button, TQ_SIGNAL( clicked( KreMenuButton* ) ), this, TQ_SLOT( collectClicks( KreMenuButton* ) ) ); + } + } + else if ( e->type() == TQChildEvent::ChildRemoved ) { + TQObject * child = e->child(); + + KreMenuButton *button = ( KreMenuButton* ) child; + if ( m_currentMenu->positionList.find( button ) != m_currentMenu->positionList.end() ) // Ensure that what was removed was a button + { + // Remove the button from the list first + int pos = m_currentMenu->positionList[ button ]; // FIXME: this works only if the button is removed from the main menu + m_currentMenu->widgetList.remove( pos ); // FIXME: this works only if the button is removed from the main menu + m_currentMenu->positionList.remove( button ); // FIXME: this works only if the button is removed from the main menu + + // Now recalculate the position of the next button + ( m_currentMenu->widgetNumber ) --; // FIXME: this works only if the button is removed from the main menu + + KreMenuButton *lastButton = m_currentMenu->widgetList[ ( m_currentMenu->widgetNumber ) - 1 ]; + if ( lastButton ) + m_currentMenu->childPos = lastButton->y() + lastButton->height(); + m_currentMenu->activeButton = 0; + + setMinimumWidth( minimumSizeHint().width() + 10 ); //update the minimum width + } + + } + TQWidget::childEvent( e ); +} + +void KreMenu::collectClicks( KreMenuButton *w ) +{ + setFocus(); + + highlightButton( w ); + + // Emit signal indicating button activation with button ID + KrePanel panel = w->getPanel(); + emit clicked( panel ); +} + +MenuId KreMenu::createSubMenu( const TQString &title, const TQString &icon ) +{ + + // Create the new menu + Menu newMenu; + MenuId id = menus.append( newMenu ); + + // Add a button to the main menu for this submenu + TDEIconLoader il; + KreMenuButton *newMenuButton = new KreMenuButton( this ); + newMenuButton->subMenuId = id; + newMenuButton->setTitle( title ); + newMenuButton->setIconSet( il.loadIconSet( icon, TDEIcon::Panel ) ); + + // Add a button to the submenu to go back to the top menu + KreMenuButton *newSubMenuButton = new KreMenuButton( this ); + newSubMenuButton->menuId = id; + newSubMenuButton->subMenuId = mainMenuId; + newSubMenuButton->setTitle( i18n( "Up" ) ); + newSubMenuButton->setIconSet( il.loadIconSet( "1uparrow", TDEIcon::Panel ) ); + + connect( newMenuButton, TQ_SIGNAL( clicked( MenuId ) ), this, TQ_SLOT( showMenu( MenuId ) ) ); + connect( newSubMenuButton, TQ_SIGNAL( clicked( MenuId ) ), this, TQ_SLOT( showMenu( MenuId ) ) ); + + + return id; +} + +void KreMenu::highlightButton( KreMenuButton *button ) +{ + MenuId buttonMenuId = button->menuId; + Menu *buttonMenu = &( *buttonMenuId ); + + //Deactivate the old button + if ( buttonMenu->activeButton ) { + buttonMenu->activeButton->setActive( false ); + buttonMenu->activeButton->update(); + } + + //Activate the new button + + button->setActive( true ); + button->update(); + buttonMenu->activeButton = button; +} + +void KreMenu::keyPressEvent( TQKeyEvent *e ) +{ + switch ( e->key() ) { + case TQt::Key_Up: { + int current_index = m_currentMenu->positionList[ m_currentMenu->activeButton ]; + if ( current_index > 0 ) { + highlightButton( m_currentMenu->widgetList[ current_index - 1 ] ); + + //simulate a mouse click + TQMouseEvent me( TQEvent::MouseButtonPress, TQPoint(), 0, 0 ); + TDEApplication::sendEvent( m_currentMenu->activeButton, &me ); + + e->accept(); + } + break; + } + case TQt::Key_Down: { + int current_index = m_currentMenu->positionList[ m_currentMenu->activeButton ]; + if ( current_index < int( m_currentMenu->positionList.count() ) - 1 ) { + highlightButton( m_currentMenu->widgetList[ current_index + 1 ] ); + + //simulate a mouse click + TQMouseEvent me( TQEvent::MouseButtonPress, TQPoint(), 0, 0 ); + TDEApplication::sendEvent( m_currentMenu->activeButton, &me ); + + e->accept(); + } + break; + } + case TQt::Key_Enter: + case TQt::Key_Return: + case TQt::Key_Space: { + //simulate a mouse click + TQMouseEvent me( TQEvent::MouseButtonPress, TQPoint(), 0, 0 ); + TDEApplication::sendEvent( m_currentMenu->activeButton, &me ); + + e->accept(); + break; + } + default: + e->ignore(); + } +} + +TQSize KreMenu::sizeHint() const +{ + return minimumSizeHint(); +} + +//the minimum size hint will be the minimum size hint of the largest child +TQSize KreMenu::minimumSizeHint() const +{ + int width = 30; + + TQObjectList *childElements = queryList( 0, 0, false, false ); //only first-generation children (not recursive) + TQObjectListIterator it( *childElements ); + + TQObject *obj; + while ( ( obj = it.current() ) != 0 ) { + ++it; + + if ( obj->isWidgetType() ) { + int obj_width_hint = ( ( TQWidget* ) obj ) ->minimumSizeHint().width(); + + if ( obj_width_hint > width ) + width = obj_width_hint; + } + } + + return TQSize( width, 150 ); +} + +void KreMenu::paintEvent( TQPaintEvent * ) +{ + // Make sure the size is bigger than the minimum necessary + //if (minimumWidth() <45) setMinimumWidth(45); // FIXME: can somehow setMinimumWidth be restricted? This may not be the best place to do this + + // Get gradient colors + TQColor c = colorGroup().button(); + TQColor c1 = c.dark( 130 ); + TQColor c2 = c.light( 120 ); + + // Draw the gradient + KPixmap kpm; + kpm.resize( size() ); + KPixmapEffect::unbalancedGradient ( kpm, c2, c1, KPixmapEffect::HorizontalGradient, -150, -150 ); + + // Draw the handle + TQPainter painter( &kpm ); + painter.setPen( c1 ); + painter.drawLine( width() - 5, 20, width() - 5, height() - 20 ); + painter.end(); + + //Set the border transparent using a mask + TQBitmap mask( kpm.size() ); + mask.fill( TQt::color0 ); + painter.begin( &mask ); + painter.setPen( TQt::color1 ); + painter.setBrush( TQt::color1 ); + painter.drawRoundRect( 0, 0, width(), height(), ( int ) ( 2.0 / width() * height() ), 2 ); + painter.end(); + kpm.setMask( mask ); + + //Draw the border line + painter.begin( &kpm ); + painter.setPen( c1 ); + painter.drawRoundRect( 0, 0, width(), height(), ( int ) ( 2.0 / width() * height() ), 2 ); + + //Draw the top line bordering with the first button + if ( m_currentMenu->activeButton ) // draw only if there's a button + { + int w = m_currentMenu->activeButton->width(); + painter.setPen( c1 ); + painter.drawLine( w / 5, 8, w - 1, 8 ); + painter.setPen( c2 ); + painter.drawLine( w / 5, 9, w - 1, 9 ); + } + + painter.end(); + + // Copy the pixmap to the widget + bitBlt( this, 0, 0, &kpm ); +} + +void KreMenu::resizeEvent( TQResizeEvent* e ) +{ + emit resized( ( e->size() ).width(), ( e->size() ).height() ); +} + +void KreMenu::showMenu( MenuId id ) +{ + + // Hide the buttons in the current menu + // and show the ones in the new menu + + TQObjectList * childElements = queryList(); + TQObjectListIterator it( *childElements ); + + TQObject *obj; + while ( ( obj = it.current() ) != 0 ) { + ++it; + if ( obj->inherits( "KreMenuButton" ) ) { + KreMenuButton * button = ( KreMenuButton* ) obj; + if ( button->menuId == currentMenuId ) + button->hide(); + else if ( button->menuId == id ) + button->show(); + } + } + + + // Set the new menu as current + currentMenuId = id; + m_currentMenu = &( *( currentMenuId ) ); +} + + + + + +KreMenuButton::KreMenuButton( KreMenu *parent, KrePanel _panel, MenuId id, const char *name ) : + TQWidget( parent, name, TQt::WNoAutoErase ), + panel( _panel ) +{ + icon = 0; + highlighted = false; + text = TQString::null; + + if ( id == 0 ) + menuId = parent->mainMenu(); + else + menuId = id; + + subMenuId = 0; // By default it's not a submenu button + + resize( parent->size().width(), 55 ); + connect ( parent, TQ_SIGNAL( resized( int, int ) ), this, TQ_SLOT( rescale() ) ); + connect( this, TQ_SIGNAL( clicked() ), this, TQ_SLOT( forwardClicks() ) ); + setCursor( TQCursor( KCursor::handCursor() ) ); +} + + +KreMenuButton::~KreMenuButton() +{ + delete icon; +} + +void KreMenuButton::setTitle( const TQString &s ) +{ + text = s; + +#if 0 //this causes problems for the button to go back to editing a recipe + //adjust text to two lines if needed + if ( fontMetrics().width( text ) > 110 ) { + text.replace( ' ', "\n" ); + } +#endif + + setMinimumWidth( minimumSizeHint().width() ); + if ( parentWidget() ->minimumWidth() < minimumSizeHint().width() ) + parentWidget() ->setMinimumWidth( minimumSizeHint().width() + 10 ); + + update(); +} + +void KreMenuButton::mousePressEvent ( TQMouseEvent * ) +{ + emit clicked(); +} + +void KreMenuButton::rescale() +{ + resize( parentWidget() ->width() - 10, height() ); +} +TQSize KreMenuButton::sizeHint() const +{ + if ( parentWidget() ) + return ( TQSize( parentWidget() ->size().width() - 10, 40 ) ); + else + return TQSize( 100, 30 ); +} + +TQSize KreMenuButton::minimumSizeHint() const +{ + int text_width = TQMAX( fontMetrics().width( text.section( '\n', 0, 0 ) ), fontMetrics().width( text.section( '\n', 1, 1 ) ) ); + + if ( icon ) + return TQSize( 40 + icon->width() + text_width, 30 ); + else + return TQSize( 40 + text_width, 30 ); +} + +void KreMenuButton::paintEvent( TQPaintEvent * ) +{ + if ( !isShown() ) + return ; + // First draw the gradient + int darken = 130, lighten = 120; + TQColor c1, c2, c1h, c2h; //non-highlighted and highlighted versions + + // Set the gradient colors + + c1 = colorGroup().button().dark( darken ); + c2 = colorGroup().button().light( lighten ); + + if ( highlighted ) { + darken -= 10; + lighten += 10; + c1h = TDEGlobalSettings::highlightColor().dark( darken ); + c2h = TDEGlobalSettings::highlightColor().light( lighten ); + } + + // draw the gradient now + + TQPainter painter; + KPixmap kpm; + kpm.resize( ( ( TQWidget * ) parent() ) ->size() ); // use parent's same size to obtain the same gradient + + if ( !highlighted ) { + + // first the gradient + KPixmapEffect::unbalancedGradient ( kpm, c2, c1, KPixmapEffect::HorizontalGradient, -150, -150 ); + + } + else { + + // top gradient (highlighted) + kpm.resize( width(), height() ); + KPixmapEffect::unbalancedGradient ( kpm, c2h, c1h, KPixmapEffect::HorizontalGradient, -150, -150 ); + // low gradient besides the line (not hightlighted) + KPixmap kpmb; + kpmb.resize( width(), 2 ); + KPixmapEffect::unbalancedGradient ( kpmb, c2, c1, KPixmapEffect::HorizontalGradient, -150, -150 ); + // mix the two + bitBlt( &kpm, 0, height() - 2, &kpmb ); + + } + + // Draw the line + painter.begin( &kpm ); + painter.setPen( colorGroup().button().dark( darken ) ); + painter.drawLine( width() / 5, height() - 2, width() - 1, height() - 2 ); + painter.setPen( colorGroup().button().light( lighten ) ); + painter.drawLine( width() / 5, height() - 1, width() - 1, height() - 1 ); + painter.end(); + + + // Now Add the icon + + painter.begin( &kpm ); + int xPos, yPos; + if ( icon ) { + // Set the icon's desired horizontal position + + xPos = 10; + yPos = 0; + + + // Make sure it fits in the area + // If not, resize and reposition horizontally to be centered + + TQPixmap scaledIcon = *icon; + + if ( ( icon->height() > height() ) || ( icon->width() > width() / 3 ) ) // Nice effect, make sure you take less than half in width and fit in height (try making the menu very short in width) + { + TQImage image; + image = ( *icon ); + scaledIcon.convertFromImage( image.smoothScale( width() / 3, height(), TQImage::ScaleMin ) ); + } + + // Calculate the icon's vertical position + + yPos = ( height() - scaledIcon.height() ) / 2 - 1; + + + // Now draw it + + painter.drawPixmap( xPos, yPos, scaledIcon ); + + xPos += scaledIcon.width(); // increase it to place the text area correctly + } + + painter.end(); + + // If it's highlighted, draw a rounded area around the text + + // Calculate the rounded area + + int areax = xPos + 10; + int areah = fontMetrics().height() * ( text.contains( '\n' ) + 1 ) + fontMetrics().lineSpacing() * text.contains( '\n' ) + 6; // Make sure the area is sensible for text and adjust for multiple lines + + int areaw = width() - areax - 10; + + if ( areah > ( height() - 4 ) ) { + areah = height() - 4; // Limit to button height + } + + int areay = ( height() - areah - 2 ) / 2 + 1; // Center the area vertically + + + // Calculate roundness + + int roundy = 99, roundx = ( int ) ( ( float ) roundy * areah / areaw ); //Make corners round + + + if ( highlighted && areaw > 0 ) // If there is no space for the text area do not draw it + { + + // Draw the gradient + KPixmap area; + area.resize( areaw, areah ); + + + KPixmapEffect::gradient( area, c2h.light( 150 ), c1h.light( 150 ), KPixmapEffect::VerticalGradient ); + + painter.begin( &area ); + painter.setPen( c1h ); + painter.setBrush( TQt::NoBrush ); + painter.drawRoundRect( 0, 0, areaw, areah, roundx, roundy ); + painter.end(); + + // Make it round + TQBitmap mask( TQSize( areaw, areah ) ); + mask.fill( TQt::color0 ); + painter.begin( &mask ); + painter.setPen( TQt::color1 ); + painter.setBrush( TQt::color1 ); + painter.drawRoundRect( 0, 0, areaw, areah, roundx, roundy ); + painter.end(); + area.setMask( mask ); + + // Copy it to the button + bitBlt( &kpm, areax, areay, &area ); + } + + // Finally, draw the text besides the icon + TQRect r = rect(); + r.setLeft( areax + 5 ); + r.setWidth( areaw - 10 ); + + painter.begin( &kpm ); + if ( highlighted ) + painter.setPen( TDEGlobalSettings::highlightedTextColor() ); + else + painter.setPen( TDEGlobalSettings::textColor() ); + painter.setClipRect( r ); + painter.drawText( r, TQt::AlignVCenter, text ); + painter.end(); + + // Copy the offscreen button to the widget + bitBlt( this, 0, 0, &kpm, 0, 0, width(), height() ); // Copy the image with correct button size (button is already smaller than parent in width to leave space for the handle, so no need to use -10) + +} + +void KreMenuButton::setIconSet( const TQIconSet &is ) +{ + delete icon; + + icon = new TQPixmap( is.pixmap( TQIconSet::Small, TQIconSet::Normal, TQIconSet::On ) ); + + setMinimumWidth( minimumSizeHint().width() ); + if ( parentWidget() ->minimumWidth() < minimumSizeHint().width() ) + parentWidget() ->setMinimumWidth( minimumSizeHint().width() + 10 ); +} + +Menu::Menu( void ) +{ + childPos = 10; // Initial button is on top (10px), then keep scrolling down + widgetNumber = 0; // Initially we have no buttons + activeButton = 0; // Button that is highlighted +} + + +Menu::Menu( const Menu &m ) +{ + activeButton = m.activeButton; + childPos = m.childPos; + widgetNumber = m.widgetNumber; + + copyMap( positionList, m.positionList ); + copyMap( widgetList, m.widgetList ); +} + +Menu::~Menu( void ) +{} + +Menu& Menu::operator=( const Menu &m ) +{ + + activeButton = m.activeButton; + childPos = m.childPos; + widgetNumber = m.widgetNumber; + + copyMap( positionList, m.positionList ); + copyMap( widgetList, m.widgetList ); + + return *this; +} + + +void Menu::addButton( KreMenuButton* button ) +{ + button->move( 0, childPos ); + button->rescale(); + childPos += button->height(); + positionList[ button ] = widgetNumber; // Store index for this widget, and increment number + widgetList[ widgetNumber ] = button; // Store the button in the list (the inverse mapping of the previous one) + widgetNumber++; +} + +void Menu::copyMap( TQMap <int, KreMenuButton*> &destMap, const TQMap <int, KreMenuButton*> &origMap ) +{ + TQMap<int, KreMenuButton*>::ConstIterator it; + destMap.clear(); + for ( it = origMap.begin(); it != origMap.end(); ++it ) { + destMap[ it.key() ] = it.data(); + } +} + +void Menu::copyMap( TQMap <KreMenuButton*, int> &destMap, const TQMap <KreMenuButton*, int> &origMap ) +{ + TQMap<KreMenuButton*, int>::ConstIterator it; + destMap.clear(); + for ( it = origMap.begin(); it != origMap.end(); ++it ) { + destMap[ it.key() ] = it.data(); + } +} + +#include "kremenu.moc" diff --git a/src/widgets/kremenu.h b/src/widgets/kremenu.h new file mode 100644 index 0000000..4b02632 --- /dev/null +++ b/src/widgets/kremenu.h @@ -0,0 +1,170 @@ +/*************************************************************************** +* Copyright (C) 2003 by * +* Unai Garro ([email protected]) * +* Cyril Bosselut ([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. * +***************************************************************************/ + +#ifndef KREMENU_H +#define KREMENU_H + +#include <tqbuttongroup.h> +#include <tqevent.h> +#include <tqiconset.h> +#include <tqmap.h> +#include <tqpushbutton.h> +#include <tqstring.h> + +#include "krecipesview.h" //for KrePanel enum + + +/** +* @author Unai Garro +* @author Bosselut Cyril +*/ + +class Menu; +class KreMenu; +class KreMenuButton; +typedef TQValueList <Menu>::Iterator MenuId; + + +class Menu +{ +public: + // Methods + + Menu( void ); + Menu( const Menu &m ); + ~Menu( void ); + void addButton( KreMenuButton *button ); + Menu& operator=( const Menu &m ); + + // Variables + + TQMap <KreMenuButton*, int> positionList; // Stores the indexes for the widgets + TQMap <int, KreMenuButton*> widgetList; // Stores the widgets for each position (just the inverse mapping) + KreMenuButton* activeButton; // Indicates which button is highlighted + int childPos; + int widgetNumber; +private: + // Methods + void copyMap( TQMap <int, KreMenuButton*> &destMap, const TQMap <int, KreMenuButton*> &origMap ); + void copyMap( TQMap <KreMenuButton*, int> &destMap, const TQMap <KreMenuButton*, int> &origMap ); +}; + + +class KreMenu : public TQWidget +{ + TQ_OBJECT +public: + KreMenu( TQWidget *parent = 0, const char *name = 0 ); + ~KreMenu(); + + MenuId createSubMenu( const TQString &title, const TQString &icon ); + MenuId mainMenu( void ) + { + return mainMenuId; + } + MenuId currentMenu( void ) + { + return currentMenuId; + } + TQSize sizeHint() const; + TQSize minimumSizeHint() const; + void resizeEvent( TQResizeEvent* e ); + void highlightButton( KreMenuButton *button ); + + +protected: + + virtual void paintEvent ( TQPaintEvent *e ); + virtual void childEvent ( TQChildEvent *e ); + virtual void keyPressEvent( TQKeyEvent *e ); + +private: + //Variables + TQValueList <Menu> menus; + MenuId mainMenuId; + MenuId currentMenuId; + Menu *m_currentMenu; + +signals: + void resized( int, int ); + void clicked( KrePanel ); + +private slots: + void collectClicks( KreMenuButton *w ); + void showMenu( MenuId id ); + +}; + +class KreMenuButton: public TQWidget +{ + TQ_OBJECT +public: + KreMenuButton( KreMenu *parent, KrePanel panel = KrePanel( -1 ), MenuId id = 0, const char *name = 0 ); + + ~KreMenuButton(); + + TQSize sizeHint() const; + TQSize minimumSizeHint() const; + + TQString title( void ) + { + return text; + } + void setActive( bool active = true ) + { + highlighted = active; + } + void setIconSet( const TQIconSet &is ); + MenuId menuId; + MenuId subMenuId; + + KrePanel getPanel() const + { + return panel; + } + +signals: + void resized( int, int ); + void clicked( void ); + void clicked( KreMenuButton* ); // sent together with clicked() + void clicked( MenuId ); // sent together with clicked() + +public slots: + void setTitle( const TQString &s ); + void rescale( void ); + +private: + // Button parts + TQPixmap* icon; + TQString text; + bool highlighted; + + KrePanel panel; + +private slots: + + void forwardClicks( void ) + { + emit clicked( this ); + if ( subMenuId != 0 ) + emit clicked( subMenuId ); + } + +protected: + + virtual void paintEvent( TQPaintEvent *e ); + virtual void mousePressEvent ( TQMouseEvent * e ); + +}; + + + +#endif diff --git a/src/widgets/kreruler.cpp b/src/widgets/kreruler.cpp new file mode 100644 index 0000000..448a375 --- /dev/null +++ b/src/widgets/kreruler.cpp @@ -0,0 +1,1049 @@ +/* This file is part of the KDE project + Copyright (C) 1998, 1999 Reginald Stadlbauer <[email protected]> + Copyright (C) 2005 Jason Kivlighn <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +// Description: Ruler (header) + +/******************************************************************/ + +#include "kreruler.h" + +#include <tdelocale.h> +#include <tdeglobalsettings.h> +#include <kdebug.h> +#include <kiconloader.h> +#include <tqcursor.h> +#include <tqpainter.h> +#include <tqpopupmenu.h> +#include <tqtooltip.h> + +#include "krepagelayout.h" + +class KoRulerPrivate { +public: + KoRulerPrivate() { + } + ~KoRulerPrivate() {} + + TQWidget *canvas; + int flags; + int oldMx, oldMy; + bool whileMovingBorderLeft, whileMovingBorderRight; + bool whileMovingBorderTop, whileMovingBorderBottom; + TQPixmap pmFirst, pmLeft; + KoPageLayout layout; + KoTabulatorList tabList; + // Do we have to remove a certain tab in the DC Event? + KoTabulator removeTab; + // The tab we're moving / clicking on - basically only valid between press and release time + KoTabulator currTab; + // The action we're currently doing - basically only valid between press and release time + KoRuler::Action action; + TQPopupMenu *rb_menu; + int mRemoveTab, mPageLayout; // menu item ids + int frameEnd; + double i_right; + bool m_bReadWrite; + bool doubleClickedIndent; + bool rtl; + bool mousePressed; +}; + +// Equality test for tab positions in particular +static inline bool equals( double a, double b ) { + return kAbs( a - b ) < 1E-4; +} + + +/******************************************************************/ +/* Class: KoRuler */ +/******************************************************************/ + +const int KoRuler::F_TABS = 1; +const int KoRuler::F_INDENTS = 2; +const int KoRuler::F_HELPLINES = 4; +const int KoRuler::F_NORESIZE = 8; + +/*================================================================*/ +KoRuler::KoRuler( TQWidget *_parent, TQWidget *_canvas, Orientation _orientation, + const KoPageLayout& _layout, int _flags, KoUnit::Unit _unit ) + : TQFrame( _parent ), buffer( width(), height() ), m_zoom(1.0), m_1_zoom(1.0), + m_unit( _unit ) +{ + setWFlags( WResizeNoErase | WRepaintNoErase ); + setFrameStyle( MenuBarPanel ); + + d=new KoRulerPrivate(); + + d->canvas = _canvas; + orientation = _orientation; + d->layout = _layout; + d->flags = _flags; + + d->m_bReadWrite=true; + d->doubleClickedIndent=false; + diffx = 0; + diffy = 0; + i_left=0.0; + i_first=0.0; + d->i_right=0.0; + + setMouseTracking( true ); + d->mousePressed = false; + d->action = A_NONE; + + d->oldMx = 0; + d->oldMy = 0; + d->rtl = false; + + showMPos = false; + mposX = 0; + mposY = 0; + gridSize=0.0; + hasToDelete = false; + d->whileMovingBorderLeft = d->whileMovingBorderRight = d->whileMovingBorderTop = d->whileMovingBorderBottom = false; + + d->pmFirst = UserIcon( "koRulerFirst" ); + d->pmLeft = UserIcon( "koRulerLeft" ); + d->currTab.type = T_INVALID; + + d->removeTab.type = T_INVALID; + if ( orientation == TQt::Horizontal ) { + frameStart = tqRound( zoomIt(d->layout.ptLeft) ); + d->frameEnd = tqRound( zoomIt(d->layout.ptWidth - d->layout.ptRight) ); + } else { + frameStart = tqRound( zoomIt(d->layout.ptTop) ); + d->frameEnd = tqRound( zoomIt(d->layout.ptHeight - d->layout.ptBottom) ); + } + m_bFrameStartSet = false; + + setupMenu(); + + // For compatibility, emitting doubleClicked shall emit openPageLayoutDia + connect( this, TQ_SIGNAL( doubleClicked() ), this, TQ_SIGNAL( openPageLayoutDia() ) ); +} + +/*================================================================*/ +KoRuler::~KoRuler() +{ + delete d->rb_menu; + delete d; +} + +void KoRuler::setPageLayoutMenuItemEnabled(bool b) +{ + d->rb_menu->setItemEnabled(d->mPageLayout, b); +} + +/*================================================================*/ +void KoRuler::setMousePos( int mx, int my ) +{ + if ( !showMPos || ( mx == mposX && my == mposY ) ) return; + + TQPainter p( this ); + p.setRasterOp( TQt::NotROP ); + + if ( orientation == TQt::Horizontal ) { + if ( hasToDelete ) + p.drawLine( mposX, 1, mposX, height() - 1 ); + p.drawLine( mx, 1, mx, height() - 1 ); + hasToDelete = true; + } + else { + if ( hasToDelete ) + p.drawLine( 1, mposY, width() - 1, mposY ); + p.drawLine( 1, my, width() - 1, my ); + hasToDelete = true; + } + p.end(); + + mposX = mx; + mposY = my; +} + +// distance between the main lines (those with a number) +double KoRuler::lineDistance() const +{ + switch( m_unit ) { + case KoUnit::U_INCH: + return INCH_TO_POINT( m_zoom ); // every inch + case KoUnit::U_PT: + return 100.0 * m_zoom; // every 100 pt + case KoUnit::U_MM: + case KoUnit::U_CM: + case KoUnit::U_DM: + return CM_TO_POINT ( m_zoom ); // every cm + case KoUnit::U_PI: + return PI_TO_POINT ( 10.0 * m_zoom ); // every 10 pica + case KoUnit::U_DD: + return DD_TO_POINT( m_zoom ); // every diderot + case KoUnit::U_CC: + return CC_TO_POINT( 10.0 * m_zoom ); // every 10 cicero + } + // should never end up here + return 100.0 * m_zoom; +} + +/*================================================================*/ +void KoRuler::drawHorizontal( TQPainter *_painter ) +{ + TQFont font = TDEGlobalSettings::toolBarFont(); + TQFontMetrics fm( font ); + resize( width(), TQMAX( fm.height() + 4, 20 ) ); + + // Use a double-buffer pixmap + TQPainter p( &buffer ); + p.fillRect( 0, 0, width(), height(), TQBrush( colorGroup().brush( TQColorGroup::Background ) ) ); + + int totalw = tqRound( zoomIt(d->layout.ptWidth) ); + TQString str; + + p.setBrush( colorGroup().brush( TQColorGroup::Base ) ); + + // Draw white rect + TQRect r; + if ( !d->whileMovingBorderLeft ) + r.setLeft( -diffx + frameStart ); + else + r.setLeft( d->oldMx ); + r.setTop( 0 ); + if ( !d->whileMovingBorderRight ) + r.setWidth(d->frameEnd-frameStart); + else + r.setRight( d->oldMx ); + r.setBottom( height() ); + + p.drawRect( r ); + p.setFont( font ); + + // Draw the numbers + double dist = lineDistance(); + int maxwidth = 0; + + for ( double i = 0.0;i <= (double)totalw;i += dist ) { + str = TQString::number( KoUnit::toUserValue( i / m_zoom, m_unit ) ); + int textwidth = fm.width( str ); + maxwidth = TQMAX( maxwidth, textwidth ); + } + + // Make sure that the ruler stays readable at lower zoom levels + while( dist <= maxwidth ) { + dist += lineDistance(); + } + + for ( double i = 0.0;i <= (double)totalw;i += dist ) { + str = TQString::number( KoUnit::toUserValue( i / m_zoom, m_unit ) ); + int textwidth = fm.width( str ); + maxwidth = TQMAX( maxwidth, textwidth ); + p.drawText( tqRound(i) - diffx - tqRound(textwidth * 0.5), + tqRound(( height() - fm.height() ) * 0.5), + textwidth, height(), AlignLeft | AlignTop, str ); + } + + // Draw the medium-sized lines + // Only if we have enough space (i.e. not at 33%) + if ( dist > maxwidth + 2 ) + { + for ( double i = dist * 0.5;i <= (double)totalw;i += dist ) { + int ii=tqRound(i); + p.drawLine( ii - diffx, 7, ii - diffx, height() - 7 ); + } + } + + // Draw the small lines + // Only if we have enough space (i.e. not at 33%) + if ( dist * 0.5 > maxwidth + 2 ) + { + for ( double i = dist * 0.25;i <= (double)totalw;i += dist * 0.5 ) { + int ii=tqRound(i); + p.drawLine( ii - diffx, 9, ii - diffx, height() - 9 ); + } + } + + // Draw ending bar (at page width) + //int constant=zoomIt(1); + //p.drawLine( totalw - diffx + constant, 1, totalw - diffx + constant, height() - 1 ); + //p.setPen( colorGroup().color( TQColorGroup::Base ) ); + //p.drawLine( totalw - diffx, 1, totalw - diffx, height() - 1 ); + + // Draw starting bar (at 0) + //p.setPen( colorGroup().color( TQColorGroup::Text ) ); + //p.drawLine( -diffx, 1, -diffx, height() - 1 ); + //p.setPen( colorGroup().color( TQColorGroup::Base ) ); + //p.drawLine( -diffx - constant, 1, -diffx - constant, height() - 1 ); + + // Show the mouse position + if ( d->action == A_NONE && showMPos ) { + p.setPen( colorGroup().color( TQColorGroup::Text ) ); + p.drawLine( mposX, 1, mposX, height() - 1 ); + } + hasToDelete = false; + + p.end(); + _painter->drawPixmap( 0, 0, buffer ); +} + + +/*================================================================*/ +void KoRuler::drawVertical( TQPainter *_painter ) +{ + TQFont font = TDEGlobalSettings::toolBarFont(); + TQFontMetrics fm( font ); + resize( TQMAX( fm.height() + 4, 20 ), height() ); + + TQPainter p( &buffer ); + p.fillRect( 0, 0, width(), height(), TQBrush( colorGroup().brush( TQColorGroup::Background ) ) ); + + int totalh = tqRound( zoomIt(d->layout.ptHeight) ); + // Clip rect - this gives basically always a rect like (2,2,width-2,height-2) + TQRect paintRect = _painter->clipRegion( TQPainter::CoordPainter ).boundingRect(); + // Ruler rect + TQRect rulerRect( 0, -diffy, width(), totalh ); + + if ( paintRect.intersects( rulerRect ) ) { + TQString str; + + p.setBrush( colorGroup().brush( TQColorGroup::Base ) ); + + // Draw white rect + TQRect r; + if ( !d->whileMovingBorderTop ) + r.setTop( -diffy + frameStart ); + else + r.setTop( d->oldMy ); + r.setLeft( 0 ); + if ( !d->whileMovingBorderBottom ) + r.setHeight(d->frameEnd-frameStart); + else + r.setBottom( d->oldMy ); + r.setRight( width() ); + + p.drawRect( r ); + p.setFont( font ); + + // Draw the numbers + double dist = lineDistance(); + int maxheight = 0; + + for ( double i = 0.0;i <= (double)totalh;i += dist ) { + str = TQString::number( KoUnit::toUserValue( i / m_zoom, m_unit ) ); + int textwidth = fm.width( str ); + maxheight = TQMAX( maxheight, textwidth ); + } + + // Make sure that the ruler stays readable at lower zoom levels + while( dist <= maxheight ) { + dist += lineDistance(); + } + + for ( double i = 0.0;i <= (double)totalh;i += dist ) { + str = TQString::number( KoUnit::toUserValue( i / m_zoom, m_unit ) ); + int textheight = fm.height(); + int textwidth = fm.width( str ); + maxheight = TQMAX( maxheight, textwidth ); + p.save(); + p.translate( tqRound(( width() - textheight ) * 0.5), + tqRound(i) - diffy + tqRound(textwidth * 0.5) ); + p.rotate( -90 ); + p.drawText( 0, 0, textwidth + 1, textheight, AlignLeft | AlignTop, str ); + p.restore(); + } + + // Draw the medium-sized lines + if ( dist > maxheight + 2 ) + { + for ( double i = dist * 0.5;i <= (double)totalh;i += dist ) { + int ii=tqRound(i); + p.drawLine( 7, ii - diffy, width() - 7, ii - diffy ); + } + } + + // Draw the small lines + if ( dist * 0.5 > maxheight + 2 ) + { + for ( double i = dist * 0.25;i <=(double)totalh;i += dist *0.5 ) { + int ii=tqRound(i); + p.drawLine( 9, ii - diffy, width() - 9, ii - diffy ); + } + } + + // Draw ending bar (at page height) + //p.drawLine( 1, totalh - diffy + 1, width() - 1, totalh - diffy + 1 ); + //p.setPen( colorGroup().color( TQColorGroup::Base ) ); + //p.drawLine( 1, totalh - diffy, width() - 1, totalh - diffy ); + + // Draw starting bar (at 0) + //p.setPen( colorGroup().color( TQColorGroup::Text ) ); + //p.drawLine( 1, -diffy, width() - 1, -diffy ); + //p.setPen( colorGroup().color( TQColorGroup::Base ) ); + //p.drawLine( 1, -diffy - 1, width() - 1, -diffy - 1 ); + } + + // Show the mouse position + if ( d->action == A_NONE && showMPos ) { + p.setPen( colorGroup().color( TQColorGroup::Text ) ); + p.drawLine( 1, mposY, width() - 1, mposY ); + } + hasToDelete = false; + + p.end(); + _painter->drawPixmap( 0, 0, buffer ); +} + +void KoRuler::mousePressEvent( TQMouseEvent *e ) +{ + if( !d->m_bReadWrite) + return; + + d->oldMx = e->x(); + d->oldMy = e->y(); + d->mousePressed = true; + d->removeTab.type = T_INVALID; + + switch ( e->button() ) { + case RightButton: + if(d->currTab.type == T_INVALID || !(d->flags & F_TABS)) + d->rb_menu->setItemEnabled(d->mRemoveTab, false); + else + d->rb_menu->setItemEnabled(d->mRemoveTab, true); + d->rb_menu->popup( TQCursor::pos() ); + d->action = A_NONE; + d->mousePressed = false; + return; + case MidButton: + // MMB shall do like double-click (it opens a dialog). + handleDoubleClick(); + return; + case LeftButton: + if ( d->action == A_BR_RIGHT || d->action == A_BR_LEFT ) { + if ( d->action == A_BR_RIGHT ) + d->whileMovingBorderRight = true; + else + d->whileMovingBorderLeft = true; + + if ( d->canvas ) + drawLine(d->oldMx, -1); + update(); + } else if ( d->action == A_BR_TOP || d->action == A_BR_BOTTOM ) { + if ( d->action == A_BR_TOP ) + d->whileMovingBorderTop = true; + else + d->whileMovingBorderBottom = true; + + if ( d->canvas ) { + TQPainter p( d->canvas ); + p.setRasterOp( TQt::NotROP ); + p.drawLine( 0, d->oldMy, d->canvas->width(), d->oldMy ); + p.end(); + } + update(); + } else if ( d->action == A_FIRST_INDENT || d->action == A_LEFT_INDENT || d->action == A_RIGHT_INDENT ) { + if ( d->canvas ) + drawLine(d->oldMx, -1); + } else if ( d->action == A_TAB ) { + if ( d->canvas && d->currTab.type != T_INVALID ) { + drawLine( tqRound( applyRtlAndZoom(d->currTab.ptPos) ) + frameStart - diffx, -1 ); + } + } + else if ( d->flags & F_HELPLINES ) + { + setCursor( orientation == TQt::Horizontal ? + TQt::sizeVerCursor : TQt::sizeHorCursor ); + d->action = A_HELPLINES; + } + default: + break; + } +} + +void KoRuler::mouseReleaseEvent( TQMouseEvent *e ) +{ + d->mousePressed = false; + + // Hacky, but necessary to prevent multiple tabs with the same coordinates (Werner) + bool fakeMovement=false; + if(d->removeTab.type != T_INVALID) { + mouseMoveEvent(e); + fakeMovement=true; + } + + if ( d->action == A_BR_RIGHT || d->action == A_BR_LEFT ) { + d->whileMovingBorderRight = false; + d->whileMovingBorderLeft = false; + + if ( d->canvas ) + drawLine(d->oldMx, -1); + update(); + emit newPageLayout( d->layout ); + } else if ( d->action == A_BR_TOP || d->action == A_BR_BOTTOM ) { + d->whileMovingBorderTop = false; + d->whileMovingBorderBottom = false; + + if ( d->canvas ) { + TQPainter p( d->canvas ); + p.setRasterOp( TQt::NotROP ); + p.drawLine( 0, d->oldMy, d->canvas->width(), d->oldMy ); + p.end(); + } + update(); + emit newPageLayout( d->layout ); + } else if ( d->action == A_FIRST_INDENT ) { + if ( d->canvas ) + drawLine(d->oldMx, -1); + update(); + emit newFirstIndent( i_first ); + } else if ( d->action == A_LEFT_INDENT ) { + if ( d->canvas ) + drawLine(d->oldMx, -1); + update(); + emit newLeftIndent( i_left ); + } else if ( d->action == A_RIGHT_INDENT ) { + if ( d->canvas ) + drawLine(d->oldMx, -1); + update(); + emit newRightIndent( d->i_right ); + } else if ( d->action == A_TAB ) { + if ( d->canvas && !fakeMovement ) { + drawLine( tqRound( applyRtlAndZoom( d->currTab.ptPos ) ) + frameStart - diffx, -1); + } + if ( willRemoveTab( e->y() ) ) + { + d->tabList.remove(d->currTab); + } + qHeapSort( d->tabList ); + + // Delete the new tabulator if it is placed on top of another. + KoTabulatorList::ConstIterator tmpTab=d->tabList.begin(); + int count=0; + while(tmpTab!=d->tabList.end()) { + if( equals( (*tmpTab).ptPos, d->currTab.ptPos ) ) { + count++; + if(count > 1) { + d->tabList.remove(d->currTab); + break; + } + } + tmpTab++; + } + //searchTab( e->x() ); // DF: why set currTab here? + emit tabListChanged( d->tabList ); + update(); + } + else if( d->action == A_HELPLINES ) + { + emit addHelpline( e->pos(), orientation == TQt::Horizontal); + setCursor( ArrowCursor ); + } + d->currTab.type = T_INVALID; // added (DF) +} + +void KoRuler::mouseMoveEvent( TQMouseEvent *e ) +{ + hasToDelete = false; + + int pw = d->frameEnd - frameStart; + int ph = tqRound(zoomIt(d->layout.ptHeight)); + int left = frameStart - diffx; + int top = tqRound(zoomIt(d->layout.ptTop)); + top -= diffy; + int right = d->frameEnd - diffx; + int bottom = tqRound(zoomIt(d->layout.ptBottom)); + bottom = ph - bottom - diffy; + // Cumulate first-line-indent + int ip_first = tqRound( zoomIt( i_first + ( d->rtl ? d->i_right : i_left) ) ); + int ip_left = tqRound(zoomIt(i_left)); + int ip_right = tqRound(zoomIt(d->i_right)); + + int mx = e->x(); + mx = mx+diffx < 0 ? 0 : mx; + int my = e->y(); + my = my+diffy < 0 ? 0 : my; + + TQToolTip::remove( this); + switch ( orientation ) { + case TQt::Horizontal: { + if ( !d->mousePressed ) { + setCursor( ArrowCursor ); + d->action = A_NONE; + /////// ###### + // At the moment, moving the left and right border indicators + // is disabled when setFrameStartEnd has been called (i.e. in KWord) + // Changing the layout margins directly from it would be utterly wrong + // (just try the 2-columns modes...). What needs to be done is: + // emitting a signal frameResized in mouseReleaseEvent, when a left/right + // border has been moved, and in kword we need to update the margins from + // there, if the left border of the 1st column or the right border of the + // last column was moved... and find what to do with the other borders. + // And for normal frames, resize the frame without touching the page layout. + // All that is too much for now -> disabling. + if ( !m_bFrameStartSet ) + { + if ( mx > left - 5 && mx < left + 5 ) { + setCursor( TQt::sizeHorCursor ); + d->action = A_BR_LEFT; + } else if ( mx > right - 5 && mx < right + 5 ) { + setCursor( TQt::sizeHorCursor ); + d->action = A_BR_RIGHT; + } + } + if ( d->flags & F_TABS ) + searchTab(mx); + } else { + // Calculate the new value. + int newPos=mx; + if( newPos!=right && gridSize!=0.0 && (e->state() & ShiftButton)==0) { // apply grid. + double grid=zoomIt(gridSize * 16); + newPos=tqRound( ((newPos * 16 / grid) * grid) / 16 ); + } + if(newPos-left < 0) newPos=left; + else if (right-newPos < 0) newPos=right; + double newValue = unZoomIt(static_cast<double>(newPos) - frameStart + diffx); + + switch ( d->action ) { + case A_BR_LEFT: { + if ( d->canvas && mx < right-10 && mx+diffx-2 > 0) { + drawLine( d->oldMx, mx ); + d->layout.ptLeft = unZoomIt(static_cast<double>(mx + diffx)); + if( ip_left > right-left-15 ) { + ip_left=right-left-15; + ip_left=ip_left<0 ? 0 : ip_left; + i_left=unZoomIt( ip_left ); + emit newLeftIndent( i_left ); + } + if ( ip_right > right-left-15 ) { + ip_right=right-left-15; + ip_right=ip_right<0? 0 : ip_right; + d->i_right=unZoomIt( ip_right ); + emit newRightIndent( d->i_right ); + } + d->oldMx = mx; + d->oldMy = my; + update(); + } + else + return; + } break; + case A_BR_RIGHT: { + if ( d->canvas && mx > left+10 && mx+diffx <= pw-2) { + drawLine( d->oldMx, mx ); + d->layout.ptRight = unZoomIt(static_cast<double>(pw - ( mx + diffx ))); + if( ip_left > right-left-15 ) { + ip_left=right-left-15; + ip_left=ip_left<0 ? 0 : ip_left; + i_left=unZoomIt( ip_left ); + emit newLeftIndent( i_left ); + } + if ( ip_right > right-left-15 ) { + ip_right=right-left-15; + ip_right=ip_right<0? 0 : ip_right; + d->i_right=unZoomIt( ip_right ); + emit newRightIndent( d->i_right ); + } + d->oldMx = mx; + d->oldMy = my; + update(); + } + else + return; + } break; + case A_FIRST_INDENT: { + if ( d->canvas ) { + if (d->rtl) + newValue = unZoomIt(pw) - newValue - d->i_right; + else + newValue -= i_left; + if(newValue == i_first) break; + drawLine( d->oldMx, newPos); + d->oldMx=newPos; + i_first = newValue; + update(); + } + } break; + case A_LEFT_INDENT: { + if ( d->canvas ) { + //if (d->rtl) newValue = unZoomIt(pw) - newValue; + if(newValue == i_left) break; + + drawLine( d->oldMx, newPos); + i_left = newValue; + d->oldMx = newPos; + update(); + } + } break; + case A_RIGHT_INDENT: { + if ( d->canvas ) { + double rightValue = unZoomIt(right - newPos); + //if (d->rtl) rightValue = unZoomIt(pw) - rightValue; + if(rightValue == d->i_right) break; + + drawLine( d->oldMx, newPos); + d->i_right=rightValue; + d->oldMx = newPos; + update(); + } + } break; + case A_TAB: { + if ( d->canvas) { + if (d->rtl) newValue = unZoomIt(pw) - newValue; + if(newValue == d->currTab.ptPos) break; // no change + TQPainter p( d->canvas ); + p.setRasterOp( TQt::NotROP ); + // prevent 1st drawLine when we just created a new tab + // (it's a NOT line) + double pt; + int pt_fr; + if( d->currTab != d->removeTab ) + { + pt = applyRtlAndZoom(d->currTab.ptPos); + pt_fr = tqRound(pt) + frameStart - diffx; + p.drawLine( pt_fr, 0, pt_fr, d->canvas->height() ); + } + + KoTabulatorList::Iterator it = d->tabList.find( d->currTab ); + Q_ASSERT( it != d->tabList.end() ); + if ( it != d->tabList.end() ) + (*it).ptPos = newValue; + d->currTab.ptPos = newValue; + + pt = applyRtlAndZoom( newValue ); + pt_fr = tqRound(pt) + frameStart - diffx; + p.drawLine( pt_fr, 0, pt_fr, d->canvas->height() ); + + p.end(); + d->oldMx = mx; + d->oldMy = my; + d->removeTab.type = T_INVALID; + update(); + } + } break; + default: break; + } + } + if( d->action == A_HELPLINES ) + { + emit moveHelpLines( e->pos(), orientation == TQt::Horizontal); + } + + return; + } break; + case TQt::Vertical: { + if ( !d->mousePressed ) { + setCursor( ArrowCursor ); + d->action = A_NONE; + if ( d->flags & F_NORESIZE ) + break; + if ( my > top - 5 && my < top + 5 ) { + TQToolTip::add( this, i18n("Top margin") ); + setCursor( TQt::sizeVerCursor ); + d->action = A_BR_TOP; + } else if ( my > bottom - 5 && my < bottom + 5 ) { + TQToolTip::add( this, i18n("Bottom margin") ); + setCursor( TQt::sizeVerCursor ); + d->action = A_BR_BOTTOM; + } + } else { + switch ( d->action ) { + case A_BR_TOP: { + if ( d->canvas && my < bottom-20 && my+diffy-2 > 0) { + TQPainter p( d->canvas ); + p.setRasterOp( TQt::NotROP ); + p.drawLine( 0, d->oldMy, d->canvas->width(), d->oldMy ); + p.drawLine( 0, my, d->canvas->width(), my ); + p.end(); + d->layout.ptTop = unZoomIt(static_cast<double>(my + diffy)); + d->oldMx = mx; + d->oldMy = my; + update(); + } + else + return; + } break; + case A_BR_BOTTOM: { + if ( d->canvas && my > top+20 && my+diffy < ph-2) { + TQPainter p( d->canvas ); + p.setRasterOp( TQt::NotROP ); + p.drawLine( 0, d->oldMy, d->canvas->width(), d->oldMy ); + p.drawLine( 0, my, d->canvas->width(), my ); + p.end(); + d->layout.ptBottom = unZoomIt(static_cast<double>(ph - ( my + diffy ))); + d->oldMx = mx; + d->oldMy = my; + update(); + } + else + return; + } break; + default: break; + } + } + } break; + } + if( d->action == A_HELPLINES ) + { + emit moveHelpLines( e->pos(), orientation == TQt::Horizontal); + } + + d->oldMx = mx; + d->oldMy = my; +} + +void KoRuler::resizeEvent( TQResizeEvent *e ) +{ + TQFrame::resizeEvent( e ); + buffer.resize( size() ); +} + +void KoRuler::mouseDoubleClickEvent( TQMouseEvent* ) +{ + handleDoubleClick(); +} + +void KoRuler::handleDoubleClick() +{ + if ( !d->m_bReadWrite ) + return; + + d->doubleClickedIndent = false; + + // When Binary Compatibility is broken this will hopefully emit a + // doubleClicked(int) to differentiate between double-clicking an + // indent and double-clicking the ruler + if ( d->flags & F_INDENTS ) { + if ( d->action == A_LEFT_INDENT || d->action == A_RIGHT_INDENT || d->action == A_FIRST_INDENT ) { + d->doubleClickedIndent = true; + emit doubleClicked(); // usually paragraph dialog + return; + } + } + + // Double-clicked nothing + d->action = A_NONE; + emit doubleClicked(); // usually page layout dialog +} + +void KoRuler::setTabList( const KoTabulatorList & _tabList ) +{ + d->tabList = _tabList; + qHeapSort(d->tabList); // "Trust no one." as opposed to "In David we trust." + + // Note that d->currTab and d->removeTab could now point to + // tabs which don't exist in d->tabList + + update(); +} + +double KoRuler::makeIntern( double _v ) +{ + return KoUnit::fromUserValue( _v, m_unit ); +} + +void KoRuler::setupMenu() +{ + d->rb_menu = new TQPopupMenu(); + TQ_CHECK_PTR( d->rb_menu ); + for ( uint i = 0 ; i <= KoUnit::U_LASTUNIT ; ++i ) + { + KoUnit::Unit unit = static_cast<KoUnit::Unit>( i ); + d->rb_menu->insertItem( KoUnit::unitDescription( unit ), i /*as id*/ ); + if ( m_unit == unit ) + d->rb_menu->setItemChecked( i, true ); + } + connect( d->rb_menu, TQ_SIGNAL( activated( int ) ), TQ_SLOT( slotMenuActivated( int ) ) ); + + d->rb_menu->insertSeparator(); + d->mPageLayout=d->rb_menu->insertItem(i18n("Page Layout..."), this, TQ_SLOT(pageLayoutDia())); +#if 0 + d->rb_menu->insertSeparator(); + d->mRemoveTab=d->rb_menu->insertItem(i18n("Remove Tabulator"), this, TQ_SLOT(rbRemoveTab())); + d->rb_menu->setItemEnabled( d->mRemoveTab, false ); +#endif +} + +void KoRuler::uncheckMenu() +{ + for ( uint i = 0 ; i <= KoUnit::U_LASTUNIT ; ++i ) + d->rb_menu->setItemChecked( i, false ); +} + +void KoRuler::setUnit( KoUnit::Unit unit ) +{ + m_unit = unit; + uncheckMenu(); + d->rb_menu->setItemChecked( m_unit, true ); + update(); +} + +void KoRuler::setZoom( const double& zoom ) +{ + if(zoom==m_zoom) + return; + if(zoom < 1E-4) // Don't do 0 or negative values + return; + m_zoom=zoom; + m_1_zoom=1/m_zoom; + update(); +} + +bool KoRuler::willRemoveTab( int y ) const +{ + return (y < -50 || y > height() + 25) && d->currTab.type != T_INVALID; +} + +void KoRuler::rbRemoveTab() { + + d->tabList.remove( d->currTab ); + d->currTab.type = T_INVALID; + emit tabListChanged( d->tabList ); + update(); +} + +void KoRuler::setReadWrite(bool _readWrite) +{ + d->m_bReadWrite=_readWrite; +} + +void KoRuler::searchTab(int mx) { + + int pos; + d->currTab.type = T_INVALID; + KoTabulatorList::ConstIterator it = d->tabList.begin(); + for ( ; it != d->tabList.end() ; ++it ) { + pos = tqRound(applyRtlAndZoom((*it).ptPos)) - diffx + frameStart; + if ( mx > pos - 5 && mx < pos + 5 ) { + setCursor( TQt::sizeHorCursor ); + d->action = A_TAB; + d->currTab = *it; + break; + } + } +} + +void KoRuler::drawLine(int oldX, int newX) { + + TQPainter p( d->canvas ); + p.setRasterOp( TQt::NotROP ); + p.drawLine( oldX, 0, oldX, d->canvas->height() ); + if(newX!=-1) + p.drawLine( newX, 0, newX, d->canvas->height() ); + p.end(); +} + +void KoRuler::showMousePos( bool _showMPos ) +{ + showMPos = _showMPos; + hasToDelete = false; + mposX = -1; + mposY = -1; + update(); +} + +void KoRuler::setOffset( int _diffx, int _diffy ) +{ + //kdDebug() << "KoRuler::setOffset " << _diffx << "," << _diffy << endl; + diffx = _diffx; + diffy = _diffy; + update(); +} + +void KoRuler::setFrameStartEnd( int _frameStart, int _frameEnd ) +{ + if ( _frameStart != frameStart || _frameEnd != d->frameEnd || !m_bFrameStartSet ) + { + frameStart = _frameStart; + d->frameEnd = _frameEnd; + // Remember that setFrameStartEnd was called. This activates a slightly + // different mode (when moving start and end positions). + m_bFrameStartSet = true; + update(); + } +} + +void KoRuler::setRightIndent( double _right ) +{ + d->i_right = makeIntern( _right ); + update(); +} + +void KoRuler::setDirection( bool rtl ) +{ + d->rtl = rtl; + update(); +} + +void KoRuler::changeFlags(int _flags) +{ + d->flags = _flags; +} + +int KoRuler::flags() const +{ + return d->flags; +} + +bool KoRuler::doubleClickedIndent() const +{ + return d->doubleClickedIndent; +} + +double KoRuler::applyRtlAndZoom( double value ) const +{ + int frameWidth = d->frameEnd - frameStart; + return d->rtl ? ( frameWidth - zoomIt( value ) ) : zoomIt( value ); +} + +double KoRuler::unZoomItRtl( int pixValue ) const +{ + int frameWidth = d->frameEnd - frameStart; + return d->rtl ? ( unZoomIt( (double)(frameWidth - pixValue) ) ) : unZoomIt( (double)pixValue ); +} + +void KoRuler::slotMenuActivated( int i ) +{ + if ( i >= 0 && i <= KoUnit::U_LASTUNIT ) + { + KoUnit::Unit unit = static_cast<KoUnit::Unit>(i); + setUnit( unit ); + emit unitChanged( unit ); + } +} + +TQSize KoRuler::minimumSizeHint() const +{ + TQSize size; + TQFont font = TDEGlobalSettings::toolBarFont(); + TQFontMetrics fm( font ); + + size.setWidth( TQMAX( fm.height() + 4, 20 ) ); + size.setHeight( TQMAX( fm.height() + 4, 20 ) ); + + return size; +} + +TQSize KoRuler::sizeHint() const +{ + return minimumSizeHint(); +} + +void KoRuler::setPageLayout( const KoPageLayout& _layout ) +{ + d->layout = _layout; + update(); +} + +#include "kreruler.moc" diff --git a/src/widgets/kreruler.h b/src/widgets/kreruler.h new file mode 100644 index 0000000..ccf76f2 --- /dev/null +++ b/src/widgets/kreruler.h @@ -0,0 +1,366 @@ +/* This file is part of the KDE project + Copyright (C) 1998, 1999 Reginald Stadlbauer <[email protected]> + Copyright (C) 2005 Jason Kivlighn <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +// Description: Ruler (header) + +/******************************************************************/ + +#ifndef KRERULER_H +#define KRERULER_H + +#include <tqframe.h> +#include <tqpixmap.h> + +#include <tdemacros.h> + +#if 0 +#include <koGlobal.h> +#include <koTabChooser.h> +#endif + +#include "kreunit.h" + +class KoPageLayout; +class TQPainter; + +enum KoTabulators { T_LEFT = 0, T_CENTER = 1, T_RIGHT = 2, T_DEC_PNT = 3, T_INVALID = -1 }; +enum KoTabulatorFilling { TF_BLANK = 0, TF_DOTS = 1, TF_LINE = 2, TF_DASH = 3, TF_DASH_DOT = 4, TF_DASH_DOT_DOT = 5}; + +/** + * Struct: KoTabulator + * Defines the position of a tabulation (in pt), and its type + */ +struct KoTabulator { + /** + * Position of the tab in pt + */ + double ptPos; + /** + * Type of tab (left/center/right/decimalpoint) + */ + KoTabulators type; + /** + * Type of tab filling. + */ + KoTabulatorFilling filling; + /** + * Width of the tab filling line. + */ + double ptWidth; + /** + * Alignment character. + */ + TQChar alignChar; + + bool operator==( const KoTabulator & t ) const { + return TQABS( ptPos - t.ptPos ) < 1E-4 && type == t.type && + filling == t.filling && TQABS( ptWidth - t.ptWidth ) < 1E-4; + } + bool operator!=( const KoTabulator & t ) const { + return !operator==(t); + } + // Operators used for sorting + bool operator < ( const KoTabulator & t ) const { + return ptPos < t.ptPos; + } + bool operator <= ( const KoTabulator & t ) const { + return ptPos <= t.ptPos; + } + bool operator > ( const KoTabulator & t ) const { + return ptPos > t.ptPos; + } +}; + +typedef TQValueList<KoTabulator> KoTabulatorList; + +class KoRulerPrivate; + +/** + * KoRuler is the horizontal or vertical ruler, to be used around + * the drawing area of most KOffice programs. + * + * It shows the graduated ruler with numbering, in any of the base units (like mm/pt/inch), + * and supports zooming, tabulators, paragraph indents, showing the mouse position, etc. + * + * It also offers a popupmenu upon right-clicking, for changing the unit, + * the page layout, or removing a tab. + */ +class KoRuler : public TQFrame +{ + TQ_OBJECT + friend class KoRulerPrivate; // for the Action enum +public: + static const int F_TABS; + static const int F_INDENTS; + static const int F_HELPLINES; + static const int F_NORESIZE; + + /** + * Create a ruler + * TODO document params + */ + KoRuler( TQWidget *_parent, TQWidget *_canvas, Orientation _orientation, + const KoPageLayout& _layout, int _flags, KoUnit::Unit _unit ); + ~KoRuler(); + + /** + * Set the unit to be used. + */ + void setUnit( KoUnit::Unit unit ); + + /** + * Set the zoom of the ruler (default value of 1.0 is 100%) + */ + void setZoom( const double& zoom=1.0 ); + /** + * @return the current zoom level + */ + const double& zoom() const { return m_zoom; } + + /** + * Set the page layout, see @ref KoPageLayout. + * This defines the size of the page and the margins, + * from which the size of the ruler is deducted. + */ + void setPageLayout( const KoPageLayout& _layout ); + + /** + * Call showMousePos(true) if the ruler should indicate the position + * of the mouse. This is usually only the case for drawing applications, + * so it is not enabled by default. + */ + void showMousePos( bool _showMPos ); + /** + * Set the position of the mouse, to update the indication in the ruler. + * This is only effective if showMousePos(true) was called previously. + * The position to give is not zoomed, it's in real pixel coordinates! + */ + void setMousePos( int mx, int my ); + + /** + * Set a global offset to the X and Y coordinates. + * Usually the main drawing area is a TQScrollView, and this is called + * with contentsX() and contentsY(), each time those values change. + */ + void setOffset( int _diffx, int _diffy ); + + /** + * Set the [paragraph] left indent to the specified position (in the current unit) + */ + void setLeftIndent( double _left ) + { i_left = makeIntern( _left ); update(); } + + /** + * Set the [paragraph] first-line left indent to the specified position (in the current unit) + * This indent is cumulated with the left or right margin, depending on the [paragraph] direction. + */ + void setFirstIndent( double _first ) + { i_first = makeIntern( _first ); update(); } + + /** + * Set the [paragraph] right indent to the specified position (in the current unit) + */ + void setRightIndent( double _right ); + + /** + * Set the [paragraph] direction. By default (rtl=false), the left indent is on the + * left, and the right indent is on the right ;) + * If rtl=true, it's the opposite. + */ + void setDirection( bool rtl ); + + /** + * Set the list of tabulators to show in the ruler. + */ + void setTabList( const KoTabulatorList & tabList ); + + /** + * Set the start and the end of the current 'frame', i.e. the part + * of the page in which we are currently working. See KWord frames + * for an example where this is used. The tab positions and paragraph + * indents then become relative to the beginning of the frame, and the + * ruler goes from frameStart to frameEnd instead of using the page margins. + * @p _frameStart et @p _frameEnd are in pixel coordinates. + */ + void setFrameStartEnd( int _frameStart, int _frameEnd ); + + /** + * KoRuler is in "read write" mode by default. + * Use setReadWrite(false) to use it in read-only mode. + */ + void setReadWrite( bool _readWrite ); + + /** + * Change the flag (i.e. activate or deactivate certain features of KoRuler) + */ + void changeFlags(int _flags); + + /** + * Set the size of the grid used for tabs positioning, size in pt. + * default value is 0. 0 means no grid. + */ + void setGridSize(double newGridSize) { gridSize=newGridSize; } + + /** + * @return the current flags + */ + int flags() const; + + /** + * @return whether the current doubleClicked() signal was triggered + * by the user double-clicking on an indent (BCI). It returns false + * if the user just double-clicked on an "empty" part of the ruler. + * + * This method is strictly provided for use in a slot connected to the + * doubleClicked() signal; calling it at any other time results in + * undefined behavior. + */ + bool doubleClickedIndent() const; + + /** + * Enable or disable the "Page Layout" menu item. + */ + void setPageLayoutMenuItemEnabled(bool b); + + /** + * Reimplemented from TQWidget + */ + virtual TQSize minimumSizeHint() const; + + /** + * Reimplemented from TQWidget + */ + virtual TQSize sizeHint() const; + +signals: + void newPageLayout( const KoPageLayout & ); + void newLeftIndent( double ); + void newFirstIndent( double ); + void newRightIndent( double ); + /** Old signal, kept for compatibility. Use doubleClicked instead. */ + void openPageLayoutDia(); + /** This signal is emitted when double-clicking the ruler (or an indent) */ + void doubleClicked(); + /** This signal is emitted when double-clicking a tab */ + void doubleClicked( double ptPos ); + + void tabListChanged( const KoTabulatorList & ); + void unitChanged( KoUnit::Unit ); + + void addHelpline(const TQPoint &, bool ); + void moveHelpLines( const TQPoint &, bool ); + +protected: + enum Action {A_NONE, A_BR_LEFT, A_BR_RIGHT, A_BR_TOP, A_BR_BOTTOM, + A_LEFT_INDENT, A_FIRST_INDENT, A_TAB, A_RIGHT_INDENT, + A_HELPLINES }; + + void drawContents( TQPainter *_painter ) + { orientation == TQt::Horizontal ? drawHorizontal( _painter ) : drawVertical( _painter ); } + + void drawHorizontal( TQPainter *_painter ); + void drawVertical( TQPainter *_painter ); + + void mousePressEvent( TQMouseEvent *e ); + void mouseReleaseEvent( TQMouseEvent *e ); + void mouseMoveEvent( TQMouseEvent *e ); + void mouseDoubleClickEvent( TQMouseEvent* ); + void resizeEvent( TQResizeEvent *e ); + void handleDoubleClick(); + + double makeIntern( double _v ); + double zoomIt(const double &value) const; + int zoomIt(const int &value) const; + unsigned int zoomIt(const unsigned int &value) const; + double unZoomIt(const double &value) const; + int unZoomIt(const int &value) const; + unsigned int unZoomIt(const unsigned int &value) const; + void setupMenu(); + void uncheckMenu(); + void searchTab(int mx); + void drawLine(int oldX, int newX); + +private: + double applyRtlAndZoom( double value ) const; + double unZoomItRtl( int pixValue ) const; + double lineDistance() const; + bool willRemoveTab( int y ) const; + + KoRulerPrivate *d; + + TQt::Orientation orientation; + int diffx, diffy; + double i_left, i_first; + TQPixmap buffer; + double m_zoom, m_1_zoom; + KoUnit::Unit m_unit; + bool hasToDelete; + bool showMPos; + bool m_bFrameStartSet; + bool m_bReadWrite; + int mposX, mposY; + int frameStart; + + double gridSize; + +protected slots: + void slotMenuActivated( int i ); + void pageLayoutDia() { emit doubleClicked()/*openPageLayoutDia()*/; } + void rbRemoveTab(); + +}; + +inline double KoRuler::zoomIt(const double &value) const { + if (m_zoom==1.0) + return value; + return m_zoom*value; +} + +inline int KoRuler::zoomIt(const int &value) const { + if (m_zoom==1.0) + return value; + return tqRound(m_zoom*value); +} + +inline unsigned int KoRuler::zoomIt(const unsigned int &value) const { + if (m_zoom==1.0) + return value; + return static_cast<unsigned int>(tqRound(m_zoom*value)); +} + +inline double KoRuler::unZoomIt(const double &value) const { + if(m_zoom==1.0) + return value; + return value*m_1_zoom; +} + +inline int KoRuler::unZoomIt(const int &value) const { + if(m_zoom==1.0) + return value; + return tqRound(value*m_1_zoom); +} + +inline unsigned int KoRuler::unZoomIt(const unsigned int &value) const { + if(m_zoom==1.0) + return value; + return static_cast<unsigned int>(tqRound(value*m_1_zoom)); +} + +#endif diff --git a/src/widgets/kretextedit.cpp b/src/widgets/kretextedit.cpp new file mode 100644 index 0000000..c6d150d --- /dev/null +++ b/src/widgets/kretextedit.cpp @@ -0,0 +1,183 @@ +/*************************************************************************** +* Copyright (C) 2004 by * +* 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 "kretextedit.h" + +#include <tqtextstream.h> + +#include <tdeaccel.h> +#include <kdebug.h> + +KreTextEdit::KreTextEdit( TQWidget *parent ) : KTextEdit( parent ), TDECompletionBase() +{ + TDECompletion * comp = completionObject(); //creates the completion object + comp->setIgnoreCase( true ); + + completing = false; + + connect( this, TQ_SIGNAL( clicked( int, int ) ), TQ_SLOT( haltCompletion() ) ); +} + +void KreTextEdit::haltCompletion() +{ + completing = false; +} + +void KreTextEdit::keyPressEvent( TQKeyEvent *e ) +{ + // Filter key-events if completion mode is not set to CompletionNone + KKey key( e ); + + KeyBindingMap keys = getKeyBindings(); + TDEShortcut cut; + bool noModifier = ( e->state() == NoButton || e->state() == ShiftButton ); + + if ( noModifier ) { + TQString keycode = e->text(); + if ( !keycode.isEmpty() && keycode.unicode() ->isPrint() ) { + TQTextEdit::keyPressEvent ( e ); + tryCompletion(); + e->accept(); + return ; + } + } + + // Handles completion + if ( keys[ TextCompletion ].isNull() ) + cut = TDEStdAccel::shortcut( TDEStdAccel::TextCompletion ); + else + cut = keys[ TextCompletion ]; + + //using just the standard Ctrl+E isn't user-friendly enough for Grandma... + if ( completing && ( cut.contains( key ) || e->key() == TQt::Key_Enter || e->key() == TQt::Key_Return ) ) { + int paraFrom, indexFrom, paraTo, indexTo; + getSelection ( ¶From, &indexFrom, ¶To, &indexTo ); + + removeSelection(); + setCursorPosition( paraTo, indexTo ); + + completing = false; + return ; + } + + // handle rotation + + // Handles previous match + if ( keys[ PrevCompletionMatch ].isNull() ) + cut = TDEStdAccel::shortcut( TDEStdAccel::PrevCompletion ); + else + cut = keys[ PrevCompletionMatch ]; + + if ( cut.contains( key ) ) { + rotateText( TDECompletionBase::PrevCompletionMatch ); + return ; + } + + // Handles next match + if ( keys[ NextCompletionMatch ].isNull() ) + cut = TDEStdAccel::shortcut( TDEStdAccel::NextCompletion ); + else + cut = keys[ NextCompletionMatch ]; + + if ( cut.contains( key ) ) { + rotateText( TDECompletionBase::NextCompletionMatch ); + return ; + } + + //any other key events will end any text completion execpt for modifiers + switch ( e->key() ) { + case TQt::Key_Shift: + case TQt::Key_Control: + case TQt::Key_Alt: + case TQt::Key_Meta: + break; + default: + completing = false; + break; + } + + // Let KTextEdit handle any other keys events. + KTextEdit::keyPressEvent ( e ); +} + +void KreTextEdit::setCompletedText( const TQString &txt ) +{ + int para, index; + getCursorPosition( ¶, &index ); + + TQString para_text = text( para ); + int word_length = index - completion_begin; + + insert( txt.right( txt.length() - word_length ) ); + setSelection( para, index, para, completion_begin + txt.length() ); + setCursorPosition( para, index ); + + completing = true; +} + +void KreTextEdit::setCompletedItems( const TQStringList &/*items*/ ) +{} + +void KreTextEdit::tryCompletion() +{ + int para, index; + getCursorPosition( ¶, &index ); + + TQString para_text = text( para ); + if ( para_text.at( index ).isSpace() || completing ) { + if ( !completing ) + completion_begin = para_text.findRev( ' ', index - 1 ) + 1; + + TQString completing_word = para_text.mid( completion_begin, index - completion_begin ); + + TQString match = compObj() ->makeCompletion( completing_word ); + + if ( !match.isNull() && match != completing_word ) + setCompletedText( match ); + else + completing = false; + } +} + +void KreTextEdit::rotateText( TDECompletionBase::KeyBindingType type ) +{ + TDECompletion * comp = compObj(); + if ( comp && completing && + ( type == TDECompletionBase::PrevCompletionMatch || + type == TDECompletionBase::NextCompletionMatch ) ) { + TQString input = ( type == TDECompletionBase::PrevCompletionMatch ) ? comp->previousMatch() : comp->nextMatch(); + + // Skip rotation if previous/next match is null or the same text + int para, index; + getCursorPosition( ¶, &index ); + TQString para_text = text( para ); + TQString complete_word = para_text.mid( completion_begin, index - completion_begin ); + if ( input.isNull() || input == complete_word ) + return ; + setCompletedText( input ); + } +} + +void KreTextEdit::addCompletionItem( const TQString &name ) +{ + compObj() ->addItem( name ); +} + +void KreTextEdit::removeCompletionItem( const TQString &name ) +{ + compObj() ->removeItem( name ); +} + +void KreTextEdit::clearCompletionItems() +{ + compObj() ->clear(); +} + +#include "kretextedit.moc" diff --git a/src/widgets/kretextedit.h b/src/widgets/kretextedit.h new file mode 100644 index 0000000..ec9e5fa --- /dev/null +++ b/src/widgets/kretextedit.h @@ -0,0 +1,52 @@ +/*************************************************************************** +* Copyright (C) 2004 by * +* 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. * +***************************************************************************/ + +#ifndef KRETEXTEDIT_H +#define KRETEXTEDIT_H + +#include <ktextedit.h> +#include <kcompletion.h> + +#include "datablocks/elementlist.h" + +/* @author Jason Kivlighn + * @brief An extended KTextEdit that allows for text completion + */ +class KreTextEdit : public KTextEdit, TDECompletionBase +{ + TQ_OBJECT + +public: + KreTextEdit( TQWidget *parent ); + + virtual void setCompletedText( const TQString &text ); + virtual void setCompletedItems( const TQStringList &items ); + +public slots: + void addCompletionItem( const TQString & ); + void removeCompletionItem( const TQString & ); + void clearCompletionItems(); + +protected: + void keyPressEvent( TQKeyEvent * ); + +private slots: + void haltCompletion(); + +private: + void tryCompletion(); + void rotateText( TDECompletionBase::KeyBindingType type ); + + bool completing; + int completion_begin; + +}; + +#endif //KRETEXTEDIT_H diff --git a/src/widgets/kwidgetlistbox.cpp b/src/widgets/kwidgetlistbox.cpp new file mode 100644 index 0000000..a05ddcb --- /dev/null +++ b/src/widgets/kwidgetlistbox.cpp @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2005 Petri Damst�n <[email protected]> + * + * Note: This file is now part of Krecipes, which is a slightly modified version of the + * original used in SuperKaramba + * + * This file is part of SuperKaramba. + * + * SuperKaramba 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. + * + * SuperKaramba is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with SuperKaramba; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + ****************************************************************************/ +#include "kwidgetlistbox.h" +#include <kdebug.h> +#include <tdeglobalsettings.h> + +KWidgetListbox::KWidgetListbox(TQWidget *parent, const char *name) + : TQTable(parent, name) +{ + setNumRows(0); + setNumCols(1); + setColumnStretchable(0, true); + setLeftMargin(0); + setTopMargin(0); + horizontalHeader()->hide(); + verticalHeader()->hide(); + setSelectionMode(TQTable::NoSelection); + setFocusStyle(TQTable::FollowStyle); + connect(this, TQ_SIGNAL(currentChanged(int, int)), + this, TQ_SLOT(selectionChanged(int, int))); + setHScrollBarMode(TQScrollView::AlwaysOff); + setVScrollBarMode(TQScrollView::Auto); +} + +KWidgetListbox::~KWidgetListbox() +{ + clear(); +} + +void KWidgetListbox::clear() +{ + for(int i = 0; i < numRows(); ++i) + clearCellWidget(i, 0); + setNumRows(0); +} + +int KWidgetListbox::insertItem(TQWidget* item, int index) +{ + int row = index; + + if(index == -1) + { + row = numRows(); + } + //else + // return -1; + + insertRows(row); + setRowHeight(row, item->height()); + setCellWidget(row, 0, item); + + for ( int i = row; i < numRows(); ++i ) { + setItemColors(i, even(i)); + } + + ensureCellVisible(row,0); + + return row; +} + +void KWidgetListbox::setSelected(TQWidget* item) +{ + setSelected(index(item)); +} + +void KWidgetListbox::selectionChanged(int row, int col) +{ + ensureCellVisible(row, col); + updateColors(); + emit selected(row); +} + +void KWidgetListbox::removeItem(TQWidget* item) +{ + removeItem(index(item)); +} + +void KWidgetListbox::removeItem(int index) +{ + removeRow(index); + updateColors(); +} + +void KWidgetListbox::setSelected(int index) +{ + setCurrentCell(index, 0); +} + +int KWidgetListbox::selected() const +{ + return currentRow(); +} + +TQWidget* KWidgetListbox::selectedItem() const +{ + return item(selected()); +} + +TQWidget* KWidgetListbox::item(int index) const +{ + return cellWidget(index, 0); +} + +int KWidgetListbox::index(TQWidget* itm) const +{ + for(int i = 0; i < numRows(); ++i) + if(item(i) == itm) + return i; + return -1; +} + +bool KWidgetListbox::even(int index) +{ + int v = 0; + for(int i = 0; i < numRows(); ++i) + { + if(index == i) + break; + //if(!isRowHidden(i)) + ++v; + } + return (v%2 == 0); +} + +void KWidgetListbox::updateColors() +{ + int v = 0; + for(int i = 0; i < numRows(); ++i) + { + //if(!isRowHidden(i)) + { + setItemColors(i, (v%2 == 0)); + ++v; + } + } +} + +void KWidgetListbox::setItemColors(int index, bool even) +{ + TQWidget* itm = item(index); +if ( !itm){ kdDebug()<<"no widget at index "<<index<<endl; return; } +/* + if(index == selected()) + { + itm->setPaletteBackgroundColor(TDEGlobalSettings::highlightColor()); + itm->setPaletteForegroundColor(TDEGlobalSettings::highlightedTextColor()); + }*/ + if(even) + { + itm->setPaletteBackgroundColor(TDEGlobalSettings::baseColor()); + itm->setPaletteForegroundColor(TDEGlobalSettings::textColor()); + } + else + { + itm->setPaletteBackgroundColor( + TDEGlobalSettings::alternateBackgroundColor()); + itm->setPaletteForegroundColor(TDEGlobalSettings::textColor()); + } +} + +void KWidgetListbox::showItems(show_callback func, void* data) +{ + for(int i = 0; i < numRows(); ++i) + { + if(func == 0) + showRow(i); + else + { + if(func(i, item(i), data)) + showRow(i); + else + hideRow(i); + } + } + updateColors(); +} + +void KWidgetListbox::showEvent(TQShowEvent*) +{ + //kdDebug() << k_funcinfo << endl; + repaintContents(false); +} + +void KWidgetListbox::paintCell(TQPainter*, int, int, const TQRect&, + bool, const TQColorGroup&) +{ + //kdDebug() << k_funcinfo << endl; +} + +#include "kwidgetlistbox.moc" diff --git a/src/widgets/kwidgetlistbox.h b/src/widgets/kwidgetlistbox.h new file mode 100644 index 0000000..0ad463c --- /dev/null +++ b/src/widgets/kwidgetlistbox.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2005 Petri Damst�n <[email protected]> + * + * Note: This file is now part of Krecipes, which is a slightly modified version of the + * original used in SuperKaramba + * + * This file is part of SuperKaramba. + * + * SuperKaramba 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. + * + * SuperKaramba is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with SuperKaramba; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + ****************************************************************************/ +#ifndef KWIDGETLISTBOX_H +#define KWIDGETLISTBOX_H + +#include <tqtable.h> + +/** +@author See README for the list of authors +*/ + +typedef bool (*show_callback) (int index, TQWidget* widget, void* data); + +class KWidgetListbox : public TQTable +{ + TQ_OBJECT + + public: + KWidgetListbox(TQWidget *parent = 0, const char *name = 0); + ~KWidgetListbox(); + + int insertItem(TQWidget* item, int index = -1); + void setSelected(TQWidget* item); + void setSelected(int index); + void removeItem(TQWidget* item); + void removeItem(int index); + void clear(); + int selected() const; + TQWidget* selectedItem() const; + TQWidget* item(int index) const; + int index(TQWidget* itm) const; + uint count() const { return numRows(); }; + + void showItems(show_callback func = 0, void* data = 0); + + void paintCell(TQPainter* p, int row, int col, const TQRect& cr, + bool selected, const TQColorGroup& cg); + protected: + void setItemColors(int index, bool even); + void updateColors(); + bool even(int index); + virtual void showEvent(TQShowEvent* e); + + protected slots: + void selectionChanged(int row, int col); + + signals: + void selected(int index); +}; + +#endif diff --git a/src/widgets/paneldeco.cpp b/src/widgets/paneldeco.cpp new file mode 100644 index 0000000..8045a5b --- /dev/null +++ b/src/widgets/paneldeco.cpp @@ -0,0 +1,173 @@ +/*************************************************************************** +* Copyright (C) 2003 by 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 "paneldeco.h" + +#include <tqpainter.h> +#include <tqpoint.h> +#include <tqrect.h> + +#include <kiconloader.h> +#include <kpixmap.h> +#include <kpixmapeffect.h> + + +// Panel decoration + +PanelDeco::PanelDeco( TQWidget *parent, const char *name, const TQString &title, const TQString &iconName ) : TQVBox( parent, name ) +{ + + // Top decoration + tDeco = new TopDeco( this, "TopDecoration", title, iconName ); + + hbox = new TQHBox( this ); + + //Left decoration + lDeco = new LeftDeco( hbox, "LeftDecoration" ); + + //The widget stack (panels) + stack = new TQWidgetStack( hbox ); + stack->setSizePolicy( TQSizePolicy( TQSizePolicy::MinimumExpanding, TQSizePolicy::MinimumExpanding ) ); + +} + + +PanelDeco::~PanelDeco() +{} + +void PanelDeco::childEvent( TQChildEvent *e ) +{ + if ( e->type() == TQEvent::ChildInserted ) { + TQObject * obj = e->child(); + if ( obj->inherits( "TQWidget" ) ) { + TQWidget * w = ( TQWidget* ) obj; + if ( w != hbox && w != tDeco ) + w->reparent( stack, TQPoint( 0, 0 ) ); + } + } +} + + +int PanelDeco::id( TQWidget* w ) +{ + return ( stack->id( w ) ); +} + +void PanelDeco::raise( TQWidget *w ) +{ + TQWidget * old_w = visiblePanel(); + + stack->raiseWidget( w ); + + if ( old_w != w ) + emit panelRaised( w, old_w ); +} + +TQWidget* PanelDeco::visiblePanel( void ) +{ + return ( stack->visibleWidget() ); +} + +void PanelDeco::setHeader( const TQString &title, const TQString &icon ) +{ + tDeco->setHeader( title, icon ); +} + +// Left part of the decoration + +LeftDeco::LeftDeco( TQWidget *parent, const char *name ) : + TQWidget( parent, name, TQt::WNoAutoErase ) +{} + +LeftDeco::~LeftDeco() +{} + +// Top part of the decoration + +TopDeco::TopDeco( TQWidget *parent, const char *name, const TQString &title, const TQString &iconName ) : + TQWidget( parent, name, TQt::WNoAutoErase ) +{ + setMinimumHeight( 30 ); + icon = 0; + panelTitle = TQString::null; + if ( !iconName.isNull() ) { + TDEIconLoader il; + icon = new TQPixmap( il.loadIcon( iconName, TDEIcon::NoGroup, 22 ) ); + } + + if ( !title.isNull() ) { + panelTitle = title; + } +} + +TopDeco::~TopDeco() +{ + delete icon; +} + + +void TopDeco::paintEvent( TQPaintEvent * ) +{ + // Get gradient colors + TQColor c1 = colorGroup().button().light( 120 ); + TQColor c2 = paletteBackgroundColor(); + + // Draw the gradient + KPixmap kpm; + kpm.resize( size() ); + KPixmapEffect::unbalancedGradient ( kpm, c1, c2, KPixmapEffect::VerticalGradient, 150, 150 ); + + // Add a line on top + TQPainter painter( &kpm ); + painter.setPen( colorGroup().button().dark( 130 ) ); + painter.drawLine( 0, 0, width(), 0 ); + + // Now Add the icon + int xpos = 0, ypos = 0; + if ( icon ) { + xpos = 20; + ypos = ( height() - icon->height() ) / 2 - 1; + painter.drawPixmap( xpos, ypos, *icon ); + xpos += icon->width(); // Move it so that later we can easily place the text + } + + // Finally, draw the text besides the icon + if ( !panelTitle.isNull() ) { + xpos += 15; + TQRect r = rect(); + r.setLeft( xpos ); + painter.setPen( TQColor( 0x00, 0x00, 0x00 ) ); + TQFont ft = font(); + ft.setBold( true ); + painter.setFont( ft ); + painter.drawText( r, TQt::AlignVCenter, panelTitle ); + } + painter.end(); + // Copy the pixmap to the widget + bitBlt( this, 0, 0, &kpm ); +} + +void TopDeco::setHeader( const TQString &title, const TQString &iconName ) +{ + if ( !title.isNull() ) + panelTitle = title; + if ( !iconName.isNull() ) { + TDEIconLoader il; + icon = new TQPixmap( il.loadIcon( iconName, TDEIcon::NoGroup, 22 ) ); + } + if ( !title.isNull() || !iconName.isNull() ) + update(); +} + +TQSize TopDeco::sizeHint( void ) +{ + return ( TQSize( parentWidget() ->width(), 30 ) ); +} + +#include "paneldeco.moc" diff --git a/src/widgets/paneldeco.h b/src/widgets/paneldeco.h new file mode 100644 index 0000000..126f7fb --- /dev/null +++ b/src/widgets/paneldeco.h @@ -0,0 +1,85 @@ +/*************************************************************************** +* Copyright (C) 2003 by 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. * +***************************************************************************/ +#ifndef PANELDECO_H +#define PANELDECO_H + + +#include <tqevent.h> +#include <tqiconset.h> +#include <tqstring.h> +#include <tqhbox.h> +#include <tqpixmap.h> +#include <tqvbox.h> +#include <tqwidget.h> +#include <tqwidgetstack.h> + + +/** +* @author Unai Garro +*/ + +class PanelDeco; +class LeftDeco; +class TopDeco; + +class PanelDeco : public TQVBox +{ + TQ_OBJECT +public: + // Methods + PanelDeco( TQWidget *parent = 0, const char *name = 0, const TQString &title = TQString::null, const TQString &iconName = TQString::null ); + ~PanelDeco(); + int id( TQWidget* w ); // obtain the id of the given panel + TQWidget* visiblePanel( void ); // obtain the current active panel no. + +signals: + void panelRaised( TQWidget *w, TQWidget *old_w ); + +private: + TQHBox *hbox; + LeftDeco *lDeco; + TopDeco *tDeco; + TQWidgetStack *stack; + +public slots: + void raise( TQWidget *w ); + void setHeader( const TQString &title = TQString::null, const TQString &icon = TQString::null ); +protected: + virtual void childEvent( TQChildEvent *e ); + + +}; + +class LeftDeco: public TQWidget +{ + TQ_OBJECT +public: + LeftDeco( TQWidget *parent = 0, const char *name = 0 ); + + ~LeftDeco(); +}; + +class TopDeco: public TQWidget +{ + TQ_OBJECT +public: + TopDeco( TQWidget *parent = 0, const char *name = 0, const TQString &title = TQString::null, const TQString &iconName = TQString::null ); + ~TopDeco(); + virtual TQSize sizeHint( void ); +public slots: + void setHeader( const TQString &title = TQString::null, const TQString &iconName = TQString::null ); +protected: + virtual void paintEvent( TQPaintEvent *e ); +private: + TQPixmap *icon; + TQString panelTitle; +}; + +#endif diff --git a/src/widgets/prepmethodcombobox.cpp b/src/widgets/prepmethodcombobox.cpp new file mode 100644 index 0000000..b0b7660 --- /dev/null +++ b/src/widgets/prepmethodcombobox.cpp @@ -0,0 +1,186 @@ +/*************************************************************************** +* Copyright (C) 2005 by * +* 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 "prepmethodcombobox.h" + +#include <tqlistbox.h> + +#include <kdebug.h> + +#include "backends/recipedb.h" +#include "datablocks/elementlist.h" + +/** Completion object which allows completing completing items + * the last item in a comma-separated list + */ +class PrepMethodCompletion : public TDECompletion +{ +public: + PrepMethodCompletion() : TDECompletion() + {} + + virtual TQString makeCompletion( const TQString &string ) { + kdDebug()<<"original makeCompletion( "<<string<<" )"<<endl; + + int comma_index = string.findRev(","); + TQString completion_txt = string; + if ( comma_index != -1 ) + completion_txt = completion_txt.right( completion_txt.length() - comma_index - 1 ).stripWhiteSpace(); + if ( completion_txt.isEmpty() ) + return string; + + kdDebug()<<"altered makeCompletion( "<<completion_txt<<" )"<<endl; + + completion_txt = TDECompletion::makeCompletion(completion_txt); + kdDebug()<<"got: "<<completion_txt<<endl; + + if ( completion_txt.isEmpty() ) + completion_txt = string; + else if ( comma_index != -1 ) + completion_txt = string.left( comma_index ) + "," + completion_txt; + + kdDebug()<<"returning: "<<completion_txt<<endl; + return completion_txt; + } +}; + +PrepMethodComboBox::PrepMethodComboBox( bool b, TQWidget *parent, RecipeDB *db, const TQString &specialItem ) : + KComboBox( b, parent ), + database( db ), m_specialItem(specialItem) +{ + setAutoDeleteCompletionObject(true); + setCompletionObject(new PrepMethodCompletion()); +} + +void PrepMethodComboBox::reload() +{ + TQString remember_text; + if ( editable() ) + remember_text = lineEdit()->text(); + + ElementList prepMethodList; + database->loadPrepMethods( &prepMethodList ); + + clear(); + prepMethodComboRows.clear(); + + int row = 0; + if ( !m_specialItem.isNull() ) { + insertItem(m_specialItem); + prepMethodComboRows.insert( row, -1 ); + row++; + } + for ( ElementList::const_iterator it = prepMethodList.begin(); it != prepMethodList.end(); ++it, ++row ) { + insertItem((*it).name); + completionObject()->addItem((*it).name); + prepMethodComboRows.insert( row,(*it).id ); + } + + if ( editable() ) + lineEdit()->setText( remember_text ); + + database->disconnect( this ); + connect( database, TQ_SIGNAL( prepMethodCreated( const Element & ) ), TQ_SLOT( createPrepMethod( const Element & ) ) ); + connect( database, TQ_SIGNAL( prepMethodRemoved( int ) ), TQ_SLOT( removePrepMethod( int ) ) ); +} + +int PrepMethodComboBox::id( int row ) +{ + return prepMethodComboRows[ row ]; +} + +int PrepMethodComboBox::id( const TQString &ing ) +{ + for ( int i = 0; i < count(); i++ ) { + if ( ing == text( i ) ) + return id(i); + } + kdDebug()<<"Warning: couldn't find the ID for "<<ing<<endl; + return -1; +} + +void PrepMethodComboBox::createPrepMethod( const Element &element ) +{ + int row = findInsertionPoint( element.name ); + + TQString remember_text; + if ( editable() ) + remember_text = lineEdit()->text(); + + insertItem( element.name, row ); + completionObject()->addItem(element.name); + + if ( editable() ) + lineEdit()->setText( remember_text ); + + //now update the map by pushing everything after this item down + TQMap<int, int> new_map; + for ( TQMap<int, int>::iterator it = prepMethodComboRows.begin(); it != prepMethodComboRows.end(); ++it ) { + if ( it.key() >= row ) { + new_map.insert( it.key() + 1, it.data() ); + } + else + new_map.insert( it.key(), it.data() ); + } + prepMethodComboRows = new_map; + prepMethodComboRows.insert( row, element.id ); +} + +void PrepMethodComboBox::removePrepMethod( int id ) +{ + int row = -1; + for ( TQMap<int, int>::iterator it = prepMethodComboRows.begin(); it != prepMethodComboRows.end(); ++it ) { + if ( it.data() == id ) { + row = it.key(); + completionObject()->removeItem( text(row) ); + removeItem( row ); + prepMethodComboRows.remove( it ); + break; + } + } + + if ( row == -1 ) + return ; + + //now update the map by pushing everything after this item up + TQMap<int, int> new_map; + for ( TQMap<int, int>::iterator it = prepMethodComboRows.begin(); it != prepMethodComboRows.end(); ++it ) { + if ( it.key() > row ) { + new_map.insert( it.key() - 1, it.data() ); + } + else + new_map.insert( it.key(), it.data() ); + } + prepMethodComboRows = new_map; +} + +int PrepMethodComboBox::findInsertionPoint( const TQString &name ) +{ + for ( int i = 0; i < count(); i++ ) { + if ( TQString::localeAwareCompare( name, text( i ) ) < 0 ) + return i; + } + + return count(); +} + +void PrepMethodComboBox::setSelected( int prepID ) +{ + //do a reverse lookup on the row->id map + TQMap<int, int>::const_iterator it; + for ( it = prepMethodComboRows.begin(); it != prepMethodComboRows.end(); ++it ) { + if ( it.data() == prepID ) { + setCurrentItem(it.key()); + break; + } + } +} + +#include "prepmethodcombobox.moc" diff --git a/src/widgets/prepmethodcombobox.h b/src/widgets/prepmethodcombobox.h new file mode 100644 index 0000000..ad902d7 --- /dev/null +++ b/src/widgets/prepmethodcombobox.h @@ -0,0 +1,48 @@ +/*************************************************************************** +* Copyright (C) 2005 by * +* 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. * +***************************************************************************/ + +#ifndef PREPMETHODCOMBOBOX_H +#define PREPMETHODCOMBOBOX_H + +#include <kcombobox.h> + +#include <tqmap.h> + +#include "datablocks/element.h" + +class RecipeDB; +class ElementList; + +class PrepMethodComboBox : public KComboBox +{ + TQ_OBJECT + +public: + PrepMethodComboBox( bool, TQWidget *parent, RecipeDB *db, const TQString &specialItem = TQString::null ); + + void reload(); + int id( int row ); + int id( const TQString &ing ); + void setSelected( int prepID ); + +private slots: + void createPrepMethod( const Element &element ); + void removePrepMethod( int id ); + + int findInsertionPoint( const TQString &name ); + +private: + RecipeDB *database; + TQMap<int, int> prepMethodComboRows; // Contains the prep method id for every given row in the combobox + TQString m_specialItem; +}; + +#endif //PREPMETHODCOMBOBOX_H + diff --git a/src/widgets/prepmethodlistview.cpp b/src/widgets/prepmethodlistview.cpp new file mode 100644 index 0000000..1f1939b --- /dev/null +++ b/src/widgets/prepmethodlistview.cpp @@ -0,0 +1,189 @@ +/*************************************************************************** +* Copyright (C) 2004 by * +* 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 "prepmethodlistview.h" + +#include <tdemessagebox.h> +#include <tdeconfig.h> +#include <tdelocale.h> +#include <tdeglobal.h> +#include <kiconloader.h> +#include <tdepopupmenu.h> + +#include "backends/recipedb.h" +#include "dialogs/createelementdialog.h" +#include "dialogs/dependanciesdialog.h" + +PrepMethodListView::PrepMethodListView( TQWidget *parent, RecipeDB *db ) : DBListViewBase( parent,db,db->prepMethodCount()) +{ + setAllColumnsShowFocus( true ); + setDefaultRenameAction( TQListView::Reject ); +} + +void PrepMethodListView::init() +{ + connect( database, TQ_SIGNAL( prepMethodCreated( const Element & ) ), TQ_SLOT( checkCreatePrepMethod( const Element & ) ) ); + connect( database, TQ_SIGNAL( prepMethodRemoved( int ) ), TQ_SLOT( removePrepMethod( int ) ) ); +} + +void PrepMethodListView::load( int limit, int offset ) +{ + ElementList prepMethodList; + database->loadPrepMethods( &prepMethodList, limit, offset ); + + setTotalItems(prepMethodList.count()); + + for ( ElementList::const_iterator ing_it = prepMethodList.begin(); ing_it != prepMethodList.end(); ++ing_it ) + createPrepMethod( *ing_it ); +} + +void PrepMethodListView::checkCreatePrepMethod( const Element &el ) +{ + if ( handleElement(el.name) ) { //only create this prep method if the base class okays it + createPrepMethod(el); + } +} + + +StdPrepMethodListView::StdPrepMethodListView( TQWidget *parent, RecipeDB *db, bool editable ) : PrepMethodListView( parent, db ) +{ + addColumn( i18n( "Preparation Method" ) ); + + 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 ); + + 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->polish(); + + delete il; + + connect( this, TQ_SIGNAL( contextMenu( TDEListView *, TQListViewItem *, const TQPoint & ) ), TQ_SLOT( showPopup( TDEListView *, TQListViewItem *, const TQPoint & ) ) ); + connect( this, TQ_SIGNAL( doubleClicked( TQListViewItem* ) ), this, TQ_SLOT( modPrepMethod( TQListViewItem* ) ) ); + connect( this, TQ_SIGNAL( itemRenamed( TQListViewItem* ) ), this, TQ_SLOT( savePrepMethod( TQListViewItem* ) ) ); + } +} + +void StdPrepMethodListView::showPopup( TDEListView * /*l*/, TQListViewItem *i, const TQPoint &p ) +{ + if ( i ) + kpop->exec( p ); +} + +void StdPrepMethodListView::createNew() +{ + CreateElementDialog * elementDialog = new CreateElementDialog( this, i18n( "New Preparation Method" ) ); + + if ( elementDialog->exec() == TQDialog::Accepted ) { + TQString result = elementDialog->newElementName(); + + //check bounds first + if ( checkBounds( result ) ) + database->createNewPrepMethod( result ); // Create the new prepMethod in the database + } +} + +void StdPrepMethodListView::remove + () +{ + TQListViewItem * item = currentItem(); + + if ( item ) { + ElementList dependingRecipes; + int prepMethodID = item->text( 1 ).toInt(); + database->findPrepMethodDependancies( prepMethodID, &dependingRecipes ); + if ( dependingRecipes.isEmpty() ) + database->removePrepMethod( prepMethodID ); + else // Need Warning! + { + ListInfo info; + info.list = dependingRecipes; + info.name = i18n("Recipes"); + DependanciesDialog warnDialog( this, info ); + warnDialog.setCustomWarning( i18n("You are about to permanantly delete recipes from your database.") ); + if ( warnDialog.exec() == TQDialog::Accepted ) + database->removePrepMethod( prepMethodID ); + } + } +} + +void StdPrepMethodListView::rename() +{ + TQListViewItem * item = currentItem(); + + if ( item ) + PrepMethodListView::rename( item, 0 ); +} + +void StdPrepMethodListView::createPrepMethod( const Element &ing ) +{ + createElement(new TQListViewItem( this, ing.name, TQString::number( ing.id ) )); +} + +void StdPrepMethodListView::removePrepMethod( int id ) +{ + TQListViewItem * item = findItem( TQString::number( id ), 1 ); + removeElement(item); +} + +void StdPrepMethodListView::modPrepMethod( TQListViewItem* i ) +{ + if ( i ) + PrepMethodListView::rename( i, 0 ); +} + +void StdPrepMethodListView::savePrepMethod( TQListViewItem* i ) +{ + if ( !checkBounds( i->text( 0 ) ) ) { + reload(ForceReload); //reset the changed text + return ; + } + + int existing_id = database->findExistingPrepByName( i->text( 0 ) ); + int prep_id = i->text( 1 ).toInt(); + if ( existing_id != -1 && existing_id != prep_id ) //category already exists with this label... merge the two + { + switch ( KMessageBox::warningContinueCancel( this, i18n( "This preparation method already exists. Continuing will merge these two into one. Are you sure?" ) ) ) + { + case KMessageBox::Continue: { + database->mergePrepMethods( existing_id, prep_id ); + break; + } + default: + reload(ForceReload); + break; + } + } + else { + database->modPrepMethod( ( i->text( 1 ) ).toInt(), i->text( 0 ) ); + } +} + +bool StdPrepMethodListView::checkBounds( const TQString &name ) +{ + if ( name.length() > uint(database->maxPrepMethodNameLength()) ) { + KMessageBox::error( this, TQString( i18n( "Preparation method cannot be longer than %1 characters." ) ).arg( database->maxPrepMethodNameLength() ) ); + return false; + } + + return true; +} + +#include "prepmethodlistview.moc" diff --git a/src/widgets/prepmethodlistview.h b/src/widgets/prepmethodlistview.h new file mode 100644 index 0000000..a31fccd --- /dev/null +++ b/src/widgets/prepmethodlistview.h @@ -0,0 +1,70 @@ +/*************************************************************************** +* Copyright (C) 2004 by * +* 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. * +***************************************************************************/ + +#ifndef PREPMETHODLISTVIEW_H +#define PREPMETHODLISTVIEW_H + +#include "dblistviewbase.h" + +#include "datablocks/element.h" + +class RecipeDB; +class TDEPopupMenu; + +class PrepMethodListView : public DBListViewBase +{ + TQ_OBJECT + +public: + PrepMethodListView( TQWidget *parent, RecipeDB *db ); + +public slots: + virtual void load( int curr_limit, int curr_offset ); + +protected slots: + virtual void createPrepMethod( const Element & ) = 0; + virtual void removePrepMethod( int ) = 0; + + void checkCreatePrepMethod( const Element &el ); + +protected: + virtual void init(); +}; + + +class StdPrepMethodListView : public PrepMethodListView +{ + TQ_OBJECT + +public: + StdPrepMethodListView( TQWidget *parent, RecipeDB *db, bool editable = false ); + +protected: + virtual void createPrepMethod( const Element & ); + virtual void removePrepMethod( int ); + +private slots: + void showPopup( TDEListView *, TQListViewItem *, const TQPoint & ); + + void createNew(); + void remove + (); + void rename(); + + void modPrepMethod( TQListViewItem* i ); + void savePrepMethod( TQListViewItem* i ); + +private: + bool checkBounds( const TQString &name ); + + TDEPopupMenu *kpop; +}; + +#endif //PREPMETHODLISTVIEW_H diff --git a/src/widgets/propertylistview.cpp b/src/widgets/propertylistview.cpp new file mode 100644 index 0000000..29a3321 --- /dev/null +++ b/src/widgets/propertylistview.cpp @@ -0,0 +1,289 @@ +/*************************************************************************** +* Copyright (C) 2004 by * +* 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 "propertylistview.h" + +#include <tdelocale.h> +#include <tdemessagebox.h> +#include <tdeconfig.h> +#include <tdeglobal.h> +#include <kiconloader.h> +#include <tdepopupmenu.h> +#include <kdebug.h> + +#include "backends/recipedb.h" +#include "dialogs/createpropertydialog.h" + +PropertyCheckListItem::PropertyCheckListItem( TQListView* klv, const IngredientProperty &property ) : TQCheckListItem( klv, TQString::null, TQCheckListItem::CheckBox ), + m_property( property ) +{ + //setOn( false ); // Set unchecked by default +} + +PropertyCheckListItem::PropertyCheckListItem( TQListViewItem* it, const IngredientProperty &property ) : TQCheckListItem( it, TQString::null, TQCheckListItem::CheckBox ), + m_property( property ) +{ + //setOn( false ); // Set unchecked by default +} + +TQString PropertyCheckListItem::text( int column ) const +{ + switch ( column ) { + case 0: + return m_property.name; + break; + case 1: + return m_property.units; + break; + case 2: + return TQString::number( m_property.id ); + break; + + } + + return TQString::null; +} + + +HidePropertyCheckListItem::HidePropertyCheckListItem( TQListView* klv, const IngredientProperty &property, bool enable ) : PropertyCheckListItem( klv, property ) +{ + m_holdSettings = true; + setOn( enable ); // Set checked by default + m_holdSettings = false; +} + +HidePropertyCheckListItem::HidePropertyCheckListItem( TQListViewItem* it, const IngredientProperty &property, bool enable ) : PropertyCheckListItem( it, property ) +{ + m_holdSettings = true; + setOn( enable ); // Set checked by default + m_holdSettings = false; +} + +void HidePropertyCheckListItem::stateChange( bool on ) +{ + if ( !m_holdSettings ) { + TDEConfig *config = TDEGlobal::config(); + config->setGroup("Formatting"); + + config->sync(); + TQStringList hiddenList = config->readListEntry("HiddenProperties"); + if ( on ) + hiddenList.remove(m_property.name); + else if ( !hiddenList.contains(m_property.name) ) + hiddenList.append(m_property.name); + + config->writeEntry("HiddenProperties",hiddenList); + } +} + +PropertyListView::PropertyListView( TQWidget *parent, RecipeDB *db ) : TDEListView( parent ), + database( db ) +{ + setAllColumnsShowFocus( true ); + setDefaultRenameAction( TQListView::Reject ); + + connect( db, TQ_SIGNAL( propertyCreated( const IngredientProperty & ) ), TQ_SLOT( createProperty( const IngredientProperty & ) ) ); + connect( db, TQ_SIGNAL( propertyRemoved( int ) ), TQ_SLOT( removeProperty( int ) ) ); +} + +void PropertyListView::reload() +{ + clear(); // Clear the view + + m_loading = true; + + IngredientPropertyList propertyList; + database->loadProperties( &propertyList ); + + //Populate this data into the TDEListView + IngredientPropertyList::const_iterator prop_it; + for ( prop_it = propertyList.begin(); prop_it != propertyList.end(); ++prop_it ) + createProperty( *prop_it ); + + m_loading = false; +} + + + +StdPropertyListView::StdPropertyListView( TQWidget *parent, RecipeDB *db, bool editable ) : PropertyListView( parent, db ) +{ + addColumn( i18n( "Property" ) ); + addColumn( i18n( "Units" ) ); + + TDEConfig * config = TDEGlobal::config(); + config->setGroup( "Advanced" ); + bool show_id = config->readBoolEntry( "ShowID", false ); + addColumn( i18n( "Id" ), show_id ? -1 : 2 ); + + setSorting( 0 ); + + if ( editable ) { + setRenameable( 0, 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->polish(); + + delete il; + + connect( this, TQ_SIGNAL( contextMenu( TDEListView *, TQListViewItem *, const TQPoint & ) ), TQ_SLOT( showPopup( TDEListView *, TQListViewItem *, const TQPoint & ) ) ); + connect( this, TQ_SIGNAL( doubleClicked( TQListViewItem* ) ), this, TQ_SLOT( modProperty( TQListViewItem* ) ) ); + connect( this, TQ_SIGNAL( itemRenamed( TQListViewItem* ) ), this, TQ_SLOT( saveProperty( TQListViewItem* ) ) ); + } +} + +void StdPropertyListView::showPopup( TDEListView * /*l*/, TQListViewItem *i, const TQPoint &p ) +{ + if ( i ) + kpop->exec( p ); +} + +void StdPropertyListView::createNew() +{ + UnitList list; + database->loadUnits( &list ); + CreatePropertyDialog* propertyDialog = new CreatePropertyDialog( this, &list ); + + if ( propertyDialog->exec() == TQDialog::Accepted ) { + TQString name = propertyDialog->newPropertyName(); + TQString units = propertyDialog->newUnitsName(); + if ( !( ( name.isEmpty() ) || ( units.isEmpty() ) ) ) // Make sure none of the fields are empty + { + //check bounds first + if ( checkBounds( name ) ) + database->addProperty( name, units ); + } + } + delete propertyDialog; +} + +void StdPropertyListView::remove + () +{ + TQListViewItem * item = currentItem(); + + if ( item ) { + switch ( KMessageBox::warningContinueCancel( this, i18n( "Are you sure you want to delete this property?" ) ) ) { + case KMessageBox::Continue: + database->removeProperty( item->text( 2 ).toInt() ); + break; + default: + break; + } + } +} + +void StdPropertyListView::rename() +{ + TQListViewItem * item = currentItem(); + + if ( item ) + PropertyListView::rename( item, 0 ); +} + +void StdPropertyListView::removeProperty( int id ) +{ + TQListViewItem * item = findItem( TQString::number( id ), 2 ); + + Q_ASSERT( item ); + + delete item; +} + +void StdPropertyListView::createProperty( const IngredientProperty &property ) +{ + ( void ) new TQListViewItem( this, property.name, property.units, TQString::number( property.id ) ); +} + +void StdPropertyListView::modProperty( TQListViewItem* i ) +{ + if ( i ) + PropertyListView::rename( i, 0 ); +} + +void StdPropertyListView::saveProperty( TQListViewItem* i ) +{ + if ( !checkBounds( i->text( 0 ) ) ) { + reload(); //reset the changed text + return ; + } +kdDebug() << "saveProp: " << i->text( 0 ) << endl; + int existing_id = database->findExistingPropertyByName( i->text( 0 ) ); + int prop_id = i->text( 2 ).toInt(); + if ( existing_id != -1 && existing_id != prop_id ) //category already exists with this label... merge the two + { + switch ( KMessageBox::warningContinueCancel( this, i18n( "This property already exists. Continuing will merge these two properties into one. Are you sure?" ) ) ) + { + case KMessageBox::Continue: { + database->mergeProperties( existing_id, prop_id ); + break; + } + default: + reload(); + break; + } + } + else + database->modProperty( prop_id, i->text( 0 ) ); +} + +bool StdPropertyListView::checkBounds( const TQString &name ) +{ + if ( name.length() > database->maxPropertyNameLength() ) { + KMessageBox::error( this, TQString( i18n( "Property name cannot be longer than %1 characters." ) ).arg( database->maxPropertyNameLength() ) ); + return false; + } + + return true; +} + + + +PropertyConstraintListView::PropertyConstraintListView( TQWidget *parent, RecipeDB *db ) : PropertyListView( parent, db ) +{ + addColumn( i18n( "Enabled" ) ); + addColumn( i18n( "Property" ) ); + addColumn( i18n( "Min. Value" ) ); + addColumn( i18n( "Max. Value" ) ); + addColumn( "Id", 0 ); //hidden, only for internal purposes + + setRenameable( 0, true ); +} + +void PropertyConstraintListView::removeProperty( int id ) +{ + TQListViewItem * item = findItem( TQString::number( id ), 4 ); + + Q_ASSERT( item ); + + delete item; +} + +void PropertyConstraintListView::createProperty( const IngredientProperty &property ) +{ + ( void ) new ConstraintsListItem( this, property ); +} + + +CheckPropertyListView::CheckPropertyListView( TQWidget *parent, RecipeDB *db, bool editable ) : StdPropertyListView( parent, db, editable ) +{ +} + +void CheckPropertyListView::createProperty( const IngredientProperty &property ) +{ + ( void ) new HidePropertyCheckListItem( this, property, (m_loading)?false:true ); +} + +#include "propertylistview.moc" diff --git a/src/widgets/propertylistview.h b/src/widgets/propertylistview.h new file mode 100644 index 0000000..62c249f --- /dev/null +++ b/src/widgets/propertylistview.h @@ -0,0 +1,203 @@ +/*************************************************************************** +* Copyright (C) 2004 by * +* 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. * +***************************************************************************/ + +#ifndef PROPERTYLISTVIEW_H +#define PROPERTYLISTVIEW_H + +#include <tdelistview.h> + +#include "datablocks/element.h" +#include "datablocks/ingredientproperty.h" +#include "datablocks/constraintlist.h" + +class RecipeDB; +class TDEPopupMenu; + +class PropertyCheckListItem : public TQCheckListItem +{ +public: + PropertyCheckListItem( TQListView* klv, const IngredientProperty &property ); + PropertyCheckListItem( TQListViewItem* it, const IngredientProperty &property ); + + ~PropertyCheckListItem( void ) + {} + virtual TQString text( int column ) const; + + IngredientProperty property() const + { + return m_property; + } + +protected: + IngredientProperty m_property; + +}; + +class HidePropertyCheckListItem : public PropertyCheckListItem +{ +public: + HidePropertyCheckListItem( TQListView* klv, const IngredientProperty &property, bool enable = false ); + HidePropertyCheckListItem( TQListViewItem* it, const IngredientProperty &property, bool enable = false ); + +protected: + virtual void stateChange( bool on ); + +private: + bool m_holdSettings; +}; + + +class ConstraintsListItem: public TQCheckListItem +{ +public: + ConstraintsListItem( TQListView* klv, const IngredientProperty &pty ) : TQCheckListItem( klv, TQString::null, TQCheckListItem::CheckBox ) + { + // Initialize the constraint data with the the property data + ctStored = new Constraint(); + ctStored->id = pty.id; + ctStored->name = pty.name; + ctStored->perUnit = pty.perUnit; + ctStored->units = pty.units; + ctStored->max = 0; + ctStored->min = 0; + } + + ~ConstraintsListItem( void ) + { + delete ctStored; + } + +private: + Constraint *ctStored; + +public: + void setConstraint( const Constraint &constraint ) + { + delete ctStored; + ctStored = new Constraint( constraint ); + + setOn( ctStored->enabled ); + } + double maxVal() + { + return ctStored->max; + } + double minVal() + { + return ctStored->min; + } + int propertyId() + { + return ctStored->id; + } + void setMax( double maxValue ) + { + ctStored->max = maxValue; + setText( 3, TQString::number( maxValue ) ); + } + void setMin( double minValue ) + { + ctStored->min = minValue; + setText( 2, TQString::number( minValue ) ); + } + virtual TQString text( int column ) const + { + switch ( column ) { + case 1: + return ( ctStored->name ); + case 2: + return ( TQString::number( ctStored->min ) ); + case 3: + return ( TQString::number( ctStored->max ) ); + case 4: + return ( TQString::number( ctStored->id ) ); + default: + return ( TQString::null ); + } + } +}; + + +class PropertyListView : public TDEListView +{ + TQ_OBJECT + +public: + PropertyListView( TQWidget *parent, RecipeDB * ); + +public slots: + void reload( void ); + +protected: + RecipeDB *database; + bool m_loading; + +protected slots: + virtual void removeProperty( int id ) = 0; + virtual void createProperty( const IngredientProperty &property ) = 0; +}; + + + +class StdPropertyListView : public PropertyListView +{ + TQ_OBJECT + +public: + StdPropertyListView( TQWidget *parent, RecipeDB *, bool editable = false ); + +protected: + virtual void removeProperty( int id ); + virtual void createProperty( const IngredientProperty &property ); + +private slots: + void showPopup( TDEListView *, TQListViewItem *, const TQPoint & ); + + void createNew(); + void remove + (); + void rename(); + + void modProperty( TQListViewItem* i ); + void saveProperty( TQListViewItem* i ); + +private: + bool checkBounds( const TQString &name ); + + TDEPopupMenu *kpop; +}; + + + +class PropertyConstraintListView : public PropertyListView +{ +public: + PropertyConstraintListView( TQWidget *parent, RecipeDB * ); + +protected: + virtual void removeProperty( int id ); + virtual void createProperty( const IngredientProperty &property ); +}; + +class CheckPropertyListView : public StdPropertyListView +{ + TQ_OBJECT + +public: + CheckPropertyListView( TQWidget *parent, RecipeDB *, bool editable = false ); + +protected: + virtual void createProperty( const IngredientProperty &property ); + +private: + bool checkBounds( const TQString &name ); +}; + +#endif //PROPERTYLISTVIEW_H diff --git a/src/widgets/ratingdisplaywidget.ui b/src/widgets/ratingdisplaywidget.ui new file mode 100644 index 0000000..bd1380e --- /dev/null +++ b/src/widgets/ratingdisplaywidget.ui @@ -0,0 +1,232 @@ +<!DOCTYPE UI><UI version="3.1" stdsetdef="1"> +<class>RatingDisplayWidget</class> +<widget class="TQWidget"> + <property name="name"> + <cstring>RatingDisplayWidget</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>533</width> + <height>211</height> + </rect> + </property> + <property name="paletteBackgroundColor"> + <color> + <red>250</red> + <green>248</green> + <blue>241</blue> + </color> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="TQLayoutWidget"> + <property name="name"> + <cstring>layout4</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <spacer> + <property name="name"> + <cstring>spacer2_2</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Fixed</enum> + </property> + <property name="sizeHint"> + <size> + <width>21</width> + <height>20</height> + </size> + </property> + </spacer> + <widget class="TQLabel"> + <property name="name"> + <cstring>icon</cstring> + </property> + <property name="minimumSize"> + <size> + <width>76</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string></string> + </property> + <property name="alignment"> + <set>AlignCenter</set> + </property> + </widget> + <spacer> + <property name="name"> + <cstring>spacer2</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>21</width> + <height>80</height> + </size> + </property> + </spacer> + </vbox> + </widget> + <widget class="TQLayoutWidget"> + <property name="name"> + <cstring>layout5</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="TQLabel"> + <property name="name"> + <cstring>raterName</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>7</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <bold>1</bold> + </font> + </property> + <property name="text"> + <string>Rater</string> + </property> + </widget> + <widget class="TDEListView"> + <column> + <property name="text"> + <string>Criteria</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <column> + <property name="text"> + <string>Stars</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <property name="name"> + <cstring>criteriaListView</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>1</verstretch> + </sizepolicy> + </property> + <property name="selectionMode" stdset="0"> + <enum>NoSelection</enum> + </property> + </widget> + <widget class="TQLabel"> + <property name="name"> + <cstring>comment</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>7</hsizetype> + <vsizetype>7</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Comments</string> + </property> + <property name="alignment"> + <set>WordBreak|AlignTop</set> + </property> + </widget> + <widget class="TQLayoutWidget"> + <property name="name"> + <cstring>layout4</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <spacer> + <property name="name"> + <cstring>spacer</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>150</width> + <height>20</height> + </size> + </property> + </spacer> + <widget class="KPushButton"> + <property name="name"> + <cstring>buttonRemove</cstring> + </property> + <property name="text"> + <string>Remove</string> + </property> + </widget> + <widget class="KPushButton"> + <property name="name"> + <cstring>buttonEdit</cstring> + </property> + <property name="text"> + <string>Edit...</string> + </property> + </widget> + </hbox> + </widget> + </vbox> + </widget> + </hbox> +</widget> +<includes> + <include location="local" impldecl="in declaration">datablocks/rating.h</include> +</includes> +<variables> + <variable access="public">RatingList::iterator rating_it;</variable> +</variables> +<layoutdefaults spacing="6" margin="6"/> +<layoutfunctions spacing="KDialog::spacingHint" margin="KDialog::marginHint"/> +<includes> + <include location="global" impldecl="in implementation">kpushbutton.h</include> + <include location="global" impldecl="in implementation">tdelistview.h</include> +</includes> +</UI> diff --git a/src/widgets/ratingwidget.cpp b/src/widgets/ratingwidget.cpp new file mode 100644 index 0000000..10f35af --- /dev/null +++ b/src/widgets/ratingwidget.cpp @@ -0,0 +1,164 @@ +/*************************************************************************** + copyright : (C) 2003-2005 by Robby Stephenson + email : [email protected] + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of version 2 of the GNU General Public License as * + * published by the Free Software Foundation; * + * * + ***************************************************************************/ + +#include "ratingwidget.h" + +#include <tdeglobal.h> // needed for KMAX +#include <kiconloader.h> +#include <kdebug.h> + +#include <tqintdict.h> +#include <tqlayout.h> + +namespace { + static const int RATING_WIDGET_MAX_STAR_SIZE = 24; +} + +const TQPixmap& RatingWidget::pixmap(const TQString& value_) { + static TQIntDict<TQPixmap> pixmaps; + if(pixmaps.isEmpty()) { + pixmaps.insert(-1, new TQPixmap()); + } + bool ok; + int n = value_.toInt(&ok); + if(!ok || n < 1 || n > 10) { + return *pixmaps[-1]; + } + if(pixmaps[n]) { + return *pixmaps[n]; + } + + TQString picName = TQString::fromLatin1("stars%1").arg(n); + TQPixmap* pix = new TQPixmap(UserIcon(picName)); + pixmaps.insert(n, pix); + return *pix; +} + +RatingWidget::RatingWidget(int stars, TQWidget* parent_, const char* name_/*=0*/) + : TQHBox(parent_, name_), m_currIndex(-1), m_min(0), m_max(stars*2) { + m_pixOn = UserIcon(TQString::fromLatin1("star_on")); + m_pixOff = UserIcon(TQString::fromLatin1("star_off")); + m_pixHalf = UserIcon(TQString::fromLatin1("star_half")); + setSpacing(0); + + // find maximum width and height + int w = KMAX(RATING_WIDGET_MAX_STAR_SIZE, KMAX(m_pixOn.width(), m_pixOff.width())); + int h = KMAX(RATING_WIDGET_MAX_STAR_SIZE, KMAX(m_pixOn.height(), m_pixOff.height())); + for(int i = 0; i < stars; ++i) { + TQLabel* l = new TQLabel(this); + l->setFixedSize(w, h); + m_widgets.append(l); + } + init(); + + TQBoxLayout* l = dynamic_cast<TQBoxLayout*>(layout()); + if(l) { + l->addStretch(1); + } +} + +void RatingWidget::init() { + m_total = KMIN(m_max/2, static_cast<int>(m_widgets.count())); + uint i = 0; + for( ; static_cast<int>(i) < m_total; ++i) { + m_widgets.at(i)->setPixmap(m_pixOff); + } + for( ; i < m_widgets.count(); ++i) { + m_widgets.at(i)->setPixmap(TQPixmap()); + } + update(); +} + +void RatingWidget::update() { + int i = 0; + for( ; i <= (m_currIndex-1)/2; ++i) { + m_widgets.at(i)->setPixmap(m_pixOn); + } + for( ; i < m_total; ++i) { + m_widgets.at(i)->setPixmap(m_pixOff); + } + + if ( m_currIndex % 2 == 0 ) { + m_widgets.at(m_currIndex/2)->setPixmap(m_pixHalf); + } + + TQHBox::update(); +} + +void RatingWidget::mousePressEvent(TQMouseEvent* event_) { + // only react to left button + if(event_->button() != TQt::LeftButton) { + return; + } + + int idx; + TQWidget* child = childAt(event_->pos()); + bool left = false; + if(child) { + TQRect child_geom_left_half = child->geometry(); + child_geom_left_half.setWidth(child_geom_left_half.width()/2); + if ( child_geom_left_half.contains(event_->pos()) ) + left = true; + + idx = m_widgets.findRef(static_cast<TQLabel*>(child)); + // if the widget is clicked beyond the maximum value, clear it + // remember total and min are values, but index is zero-based! + if(idx > m_total-1) { + idx = -1; + } else if(idx < m_min-1) { + idx = m_min-1; // limit to minimum, remember index is zero-based + } + } else { + idx = -1; + } + + int oldCurrent = m_currIndex; + + m_currIndex = idx*2+1; + + if ( left ) + m_currIndex--; + + if ( oldCurrent != m_currIndex ) { + update(); + emit modified(); + } +} + +void RatingWidget::clear() { + m_currIndex = -1; + update(); +} + +TQString RatingWidget::text() const { + // index is index of the list, which is zero-based. Add 1! + return m_currIndex == -1 ? TQString::null : TQString::number(double(m_currIndex+1)/2); +} + +void RatingWidget::setText(const TQString& text_) { + bool ok; + // text is value, subtract one to get index + m_currIndex =text_.toInt(&ok)-1; + if(ok) { + if(m_currIndex > m_total-1) { + m_currIndex = -1; + } else if(m_currIndex < m_min-1) { + m_currIndex = m_min-1; // limit to minimum, remember index is zero-based + } + } else { + m_currIndex = -1; + } + update(); +} + +#include "ratingwidget.moc" diff --git a/src/widgets/ratingwidget.h b/src/widgets/ratingwidget.h new file mode 100644 index 0000000..a603def --- /dev/null +++ b/src/widgets/ratingwidget.h @@ -0,0 +1,63 @@ +/*************************************************************************** + copyright : (C) 2003-2005 by Robby Stephenson + email : [email protected] + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of version 2 of the GNU General Public License as * + * published by the Free Software Foundation; * + * * + ***************************************************************************/ + +#ifndef RATINGWIDGET_H +#define RATINGWIDGET_H + +#include <tqhbox.h> +#include <tqptrlist.h> +#include <tqlabel.h> +#include <tqpixmap.h> +#include <tqstringlist.h> + +/** + * @author Robby Stephenson + */ +class RatingWidget : public TQHBox { +TQ_OBJECT + +typedef TQPtrList<TQLabel> LabelList; + +public: + RatingWidget(int stars, TQWidget* parent, const char* name = 0); + + void clear(); + TQString text() const; + void setText(const TQString& text); + + static const TQPixmap& pixmap(const TQString& value); + +public slots: + void update(); + +signals: + void modified(); + +protected: + virtual void mousePressEvent(TQMouseEvent* e); + +private: + void init(); + + LabelList m_widgets; + + int m_currIndex; + int m_total; + int m_min; + int m_max; + + TQPixmap m_pixOn; + TQPixmap m_pixOff; + TQPixmap m_pixHalf; +}; +#endif diff --git a/src/widgets/recipelistview.cpp b/src/widgets/recipelistview.cpp new file mode 100644 index 0000000..536d06c --- /dev/null +++ b/src/widgets/recipelistview.cpp @@ -0,0 +1,447 @@ +/*************************************************************************** +* 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 "recipelistview.h" + +#include <tqintdict.h> +#include <tqdatastream.h> +#include <tqtooltip.h> + +#include <tdeapplication.h> +#include <kdebug.h> +#include <tdeconfig.h> +#include <tdeglobal.h> +#include <tdelocale.h> +#include <kiconloader.h> +#include <kprogress.h> + +#include "backends/recipedb.h" + +class UncategorizedItem : public TQListViewItem +{ +public: + UncategorizedItem( TQListView *lv ) : TQListViewItem( lv, i18n("Uncategorized") ){} + int rtti() const { return 1006; } +}; + +RecipeItemDrag::RecipeItemDrag( RecipeListItem *recipeItem, TQWidget *dragSource, const char *name ) + : TQStoredDrag( RECIPEITEMMIMETYPE, dragSource, name ) +{ + if ( recipeItem ) { + TQByteArray data; + TQDataStream out( data, IO_WriteOnly ); + out << recipeItem->recipeID(); + out << recipeItem->title(); + setEncodedData( data ); + } +} + +bool RecipeItemDrag::canDecode( TQMimeSource* e ) +{ + return e->provides( RECIPEITEMMIMETYPE ); +} + +bool RecipeItemDrag::decode( const TQMimeSource* e, RecipeListItem& item ) +{ + if ( !e ) + return false; + + TQByteArray data = e->encodedData( RECIPEITEMMIMETYPE ); + if ( data.isEmpty() ) + return false; + + TQString title; + int recipeID; + TQDataStream in( data, IO_ReadOnly ); + in >> recipeID; + in >> title; + + item.setTitle( title ); + item.setRecipeID( recipeID ); + + return true; +} + +class RecipeListToolTip : public TQToolTip +{ +public: + RecipeListToolTip( RecipeListView *view ) : TQToolTip(view->viewport()), m_view(view) + {} + + void maybeTip( const TQPoint &point ) + { + TQListViewItem *item = m_view->itemAt( point ); + if ( item ) { + TQString text = m_view->tooltip(item,0); + if ( !text.isEmpty() ) + tip( m_view->itemRect( item ), text ); + } + } + +private: + RecipeListView *m_view; + +}; + + +RecipeListView::RecipeListView( TQWidget *parent, RecipeDB *db ) : StdCategoryListView( parent, db ), + flat_list( false ), + m_uncat_item(0), + m_progress_dlg(0) +{ + setColumnText( 0, i18n( "Recipe" ) ); + + TDEConfig *config = TDEGlobal::config(); config->setGroup( "Performance" ); + curr_limit = config->readNumEntry("CategoryLimit",-1); + + TDEIconLoader il; + setPixmap( il.loadIcon( "categories", TDEIcon::NoGroup, 16 ) ); + + setSelectionMode( TQListView::Extended ); + + (void)new RecipeListToolTip(this); +} + +void RecipeListView::init() +{ + connect( database, TQ_SIGNAL( recipeCreated( const Element &, const ElementList & ) ), TQ_SLOT( createRecipe( const Element &, const ElementList & ) ) ); + connect( database, TQ_SIGNAL( recipeRemoved( int ) ), TQ_SLOT( removeRecipe( int ) ) ); + connect( database, TQ_SIGNAL( recipeRemoved( int, int ) ), TQ_SLOT( removeRecipe( int, int ) ) ); + connect( database, TQ_SIGNAL( recipeModified( const Element &, const ElementList & ) ), TQ_SLOT( modifyRecipe( const Element &, const ElementList & ) ) ); + + StdCategoryListView::init(); +} + +TQDragObject *RecipeListView::dragObject() +{ + RecipeListItem * item = dynamic_cast<RecipeListItem*>( currentItem() ); + if ( item != 0 ) { + RecipeItemDrag * obj = new RecipeItemDrag( item, this, "Recipe drag item" ); + /*const TQPixmap *pm = item->pixmap(0); + if( pm ) + obj->setPixmap( *pm );*/ + return obj; + } + return 0; +} + +bool RecipeListView::acceptDrag( TQDropEvent *event ) const +{ + return RecipeItemDrag::canDecode( event ); +} + +TQString RecipeListView::tooltip(TQListViewItem *item, int /*column*/) const +{ + if ( item->rtti() == RECIPELISTITEM_RTTI ) { + RecipeListItem *recipe_it = (RecipeListItem*)item; + + Recipe r; + database->loadRecipe(&r,RecipeDB::Meta|RecipeDB::Noatime,recipe_it->recipeID() ); + + TDELocale *locale = TDEGlobal::locale(); + + return TQString("<center><b>%7</b></center><center>__________</center>%1 %2<br />%3 %4<br />%5 %6") + .arg(i18n("Created:")).arg(locale->formatDateTime(r.ctime)) + .arg(i18n("Modified:")).arg(locale->formatDateTime(r.mtime)) + .arg(i18n("Last Accessed:")).arg(locale->formatDateTime(r.atime)) + .arg(recipe_it->title()); + }/* Maybe this would be handy + else if ( item->rtti() == CATEGORYLISTITEM_RTTI ) { + CategoryListItem *cat_it = (CategoryListItem*)item; + + return TQString("<b>%1</b><hr />%2: %3") + .arg(cat_it->categoryName()) + .arg(i18n("Recipes")) + .arg(TQString::number(WHATEVER THE CHILD COUNT IS)); + }*/ + + return TQString::null; +} + +void RecipeListView::load(int limit, int offset) +{ + m_uncat_item = 0; + + if ( flat_list ) { + ElementList recipeList; + database->loadRecipeList( &recipeList ); + + ElementList::const_iterator recipe_it; + for ( recipe_it = recipeList.begin();recipe_it != recipeList.end();++recipe_it ) { + Recipe recipe; + recipe.recipeID = ( *recipe_it ).id; + recipe.title = ( *recipe_it ).name; + createRecipe( recipe, -1 ); + } + } + else { + StdCategoryListView::load(limit,offset); + + if ( offset == 0 ) { + ElementList recipeList; + database->loadUncategorizedRecipes( &recipeList ); + + ElementList::const_iterator recipe_it; + for ( recipe_it = recipeList.begin();recipe_it != recipeList.end();++recipe_it ) { + Recipe recipe; + recipe.recipeID = ( *recipe_it ).id; + recipe.title = ( *recipe_it ).name; + createRecipe( recipe, -1 ); + } + } + } +} + +void RecipeListView::populate( TQListViewItem *item ) +{ + CategoryItemInfo *cat_item = dynamic_cast<CategoryItemInfo*>(item); + if ( !cat_item || cat_item->isPopulated() ) return; + + delete item->firstChild(); //delete the "pseudo item" + + if ( m_progress_dlg ){ + m_progress_dlg->progressBar()->advance(1); + kapp->processEvents(); + } + + StdCategoryListView::populate(item); + + if ( !flat_list ) { + int id = cat_item->categoryId(); + + // Now show the recipes + ElementList recipeList; + database->loadRecipeList( &recipeList, id ); + + ElementList::const_iterator recipe_it; + for ( recipe_it = recipeList.begin(); recipe_it != recipeList.end(); ++recipe_it ) { + Recipe recipe; + recipe.recipeID = ( *recipe_it ).id; + recipe.title = ( *recipe_it ).name; + createRecipe( recipe, id ); + } + } +} + +void RecipeListView::populateAll( TQListViewItem *parent ) +{ + bool first = false; + if ( !parent ) { + first = true; + m_progress_dlg = new KProgressDialog(this,"populate_all_prog_dlg",TQString::null,i18n("Loading recipes"),true); + m_progress_dlg->setAllowCancel(false); + m_progress_dlg->progressBar()->setTotalSteps(0); + m_progress_dlg->progressBar()->setPercentageVisible(false); + + m_progress_dlg->grabKeyboard(); //don't let the user keep hitting keys + + parent = firstChild(); + } + else { + populate( parent ); + parent = parent->firstChild(); + } + + for ( TQListViewItem *item = parent; item; item = item->nextSibling() ) { + if ( m_progress_dlg && m_progress_dlg->wasCancelled() ) + break; + + populateAll( item ); + } + + if ( first ) { + delete m_progress_dlg; + m_progress_dlg = 0; + } +} + +void RecipeListView::createRecipe( const Recipe &recipe, int parent_id ) +{ + if ( parent_id == -1 ) { + if ( !m_uncat_item && curr_offset == 0 ) { + m_uncat_item = new UncategorizedItem(this); + if ( childCount() == 1 ) //only call createElement if this is the only item in the list + createElement(m_uncat_item); //otherwise, this item won't stay at the top + } + + if ( m_uncat_item ) + new RecipeListItem( m_uncat_item, recipe ); + } + else { + CategoryListItem *parent = (CategoryListItem*)items_map[ parent_id ]; + if ( parent && parent->isPopulated() ) + createElement(new RecipeListItem( parent, recipe )); + } +} + +void RecipeListView::createRecipe( const Element &recipe_el, const ElementList &categories ) +{ + Recipe recipe; + recipe.recipeID = recipe_el.id; + recipe.title = recipe_el.name; + + if ( categories.count() == 0 ) { + createRecipe( recipe, -1 ); + } + else { + for ( ElementList::const_iterator cat_it = categories.begin(); cat_it != categories.end(); ++cat_it ) { + int cur_cat_id = ( *cat_it ).id; + + TQListViewItemIterator iterator( this ); + while ( iterator.current() ) { + if ( iterator.current() ->rtti() == 1001 ) { + CategoryListItem * cat_item = ( CategoryListItem* ) iterator.current(); + if ( cat_item->categoryId() == cur_cat_id ) { + createRecipe( recipe, cur_cat_id ); + } + } + ++iterator; + } + } + } +} + +void RecipeListView::createElement( TQListViewItem *item ) +{ + CategoryItemInfo *cat_item = dynamic_cast<CategoryItemInfo*>(item); + if ( cat_item && !cat_item->isPopulated() ) { + new PseudoListItem( item ); + } + + //if ( cat_item && !cat_item->isPopulated() && item->rtti() == RECIPELISTITEM_RTTI ) + // return; + + #if 0 + ElementList list; + database->loadRecipeList( &list, cat_item->categoryId() ); + if ( list.count() > 0 ) + #endif + + CategoryListView::createElement(item); +} + +void RecipeListView::modifyRecipe( const Element &recipe, const ElementList &categories ) +{ + removeRecipe( recipe.id ); + createRecipe( recipe, categories ); +} + +void RecipeListView::removeRecipe( int id ) +{ + TQListViewItemIterator iterator( this ); + while ( iterator.current() ) { + if ( iterator.current() ->rtti() == 1000 ) { + RecipeListItem * recipe_it = ( RecipeListItem* ) iterator.current(); + if ( recipe_it->recipeID() == id ) { + removeElement(recipe_it); + + //delete the "Uncategorized" item if we removed the last recipe that was under it + if ( m_uncat_item && m_uncat_item->childCount() == 0 ) { + delete m_uncat_item; + m_uncat_item = 0; + } + } + } + ++iterator; + } +} + +void RecipeListView::removeRecipe( int recipe_id, int cat_id ) +{ + TQListViewItem * item = items_map[ cat_id ]; + + //find out if this is the only category the recipe belongs to + int finds = 0; + TQListViewItemIterator iterator( this ); + while ( iterator.current() ) { + if ( iterator.current() ->rtti() == 1000 ) { + RecipeListItem * recipe_it = ( RecipeListItem* ) iterator.current(); + + if ( recipe_it->recipeID() == recipe_id ) { + if ( finds > 1 ) + break; + finds++; + } + } + ++iterator; + } + + //do this to only iterate over children of 'item' + TQListViewItem *pEndItem = NULL; + TQListViewItem *pStartItem = item; + do { + if ( pStartItem->nextSibling() ) + pEndItem = pStartItem->nextSibling(); + else + pStartItem = pStartItem->parent(); + } + while ( pStartItem && !pEndItem ); + + iterator = TQListViewItemIterator( item ); + while ( iterator.current() != pEndItem ) { + if ( iterator.current() ->rtti() == 1000 ) { + RecipeListItem * recipe_it = ( RecipeListItem* ) iterator.current(); + + if ( recipe_it->recipeID() == recipe_id ) { + + if ( finds == 1 ) { + //the item is now uncategorized + if ( !m_uncat_item && curr_offset == 0 ) + m_uncat_item = new UncategorizedItem(this); + if ( m_uncat_item ) { + Recipe r; + r.title = recipe_it->title(); r.recipeID = recipe_id; + new RecipeListItem(m_uncat_item,r); + } + } + removeElement(recipe_it); + break; + } + } + ++iterator; + } +} + +void RecipeListView::removeCategory( int id ) +{ + TQListViewItem * item = items_map[ id ]; + if ( !item ) + return ; //this may have been deleted already by its parent being deleted + + moveChildrenToRoot( item ); + + StdCategoryListView::removeCategory( id ); +} + +void RecipeListView::moveChildrenToRoot( TQListViewItem *item ) +{ + TQListViewItem * next_sibling; + for ( TQListViewItem * it = item->firstChild(); it; it = next_sibling ) { + next_sibling = it->nextSibling(); + if ( it->rtti() == 1000 ) { + RecipeListItem *recipe_it = (RecipeListItem*) it; + Recipe r; + r.title = recipe_it->title(); r.recipeID = recipe_it->recipeID(); + + //the item is now uncategorized + removeElement(it,false); + it->parent() ->takeItem( it ); + if ( !m_uncat_item && curr_offset == 0 ) + m_uncat_item = new UncategorizedItem(this); + if ( m_uncat_item ) + new RecipeListItem(m_uncat_item,r); + } + moveChildrenToRoot( it ); + delete it; + } +} + +#include "recipelistview.moc" diff --git a/src/widgets/recipelistview.h b/src/widgets/recipelistview.h new file mode 100644 index 0000000..642f768 --- /dev/null +++ b/src/widgets/recipelistview.h @@ -0,0 +1,166 @@ +/*************************************************************************** +* Copyright (C) 2004 by * +* Jason Kivlighn ([email protected]) * +* Unai Garro ([email protected]) * +* * +* Copyright (C) 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. * +***************************************************************************/ + +#ifndef RECIPELISTVIEW_H +#define RECIPELISTVIEW_H + +#include <tqdragobject.h> + +#include "categorylistview.h" +#include "datablocks/recipe.h" + +class TQDragObject; +class TQDropEvent; + +class KProgressDialog; + +class RecipeDB; + +#define RECIPELISTITEM_RTTI 1000 + +#define RECIPEITEMMIMETYPE "data/x-kde.recipe.item" + +class RecipeListItem : public TQListViewItem +{ +public: + RecipeListItem( TQListView* qlv, const Recipe &r ) : TQListViewItem( qlv ) + { + init( r ); + } + + RecipeListItem( TQListView* qlv, TQListViewItem *after, const Recipe &r ) : TQListViewItem( qlv, after ) + { + init( r ); + } + + RecipeListItem( CategoryListItem* it, const Recipe &r ) : TQListViewItem( it ) + { + init( r ); + } + + RecipeListItem( CategoryListItem* it, TQListViewItem *after, const Recipe &r ) : TQListViewItem( it, after ) + { + init( r ); + } + + RecipeListItem( TQListViewItem* it, const Recipe &r ) : TQListViewItem( it ) + { + init( r ); + } + + int rtti() const + { + return RECIPELISTITEM_RTTI; + } + + ~RecipeListItem( void ) + { + delete recipeStored; + } + + int recipeID() const + { + return recipeStored->recipeID; + } + TQString title() const + { + return recipeStored->title; + } + + void setRecipeID( int id ) + { + recipeStored->recipeID = id; + } + void setTitle( const TQString &title ) + { + recipeStored->title = title; + } + +protected: + Recipe *recipeStored; + +public: + virtual TQString text( int column ) const + { + switch ( column ) { + case 0: + return ( recipeStored->title ); + break; + case 1: + return ( TQString::number( recipeStored->recipeID ) ); + break; + default: + return ( TQString::null ); + } + } + +private: + void init( const Recipe &r ) + { + recipeStored = new Recipe(); + + //note: we only store the title and id + recipeStored->recipeID = r.recipeID; + recipeStored->title = r.title; + } +}; + +class RecipeItemDrag : public TQStoredDrag +{ +public: + RecipeItemDrag( RecipeListItem *recipeItem, TQWidget *dragSource = 0, const char *name = 0 ); + static bool canDecode( TQMimeSource* e ); + static bool decode( const TQMimeSource* e, RecipeListItem& item ); +}; + +class RecipeListView : public StdCategoryListView +{ + TQ_OBJECT + +public: + RecipeListView( TQWidget *parent, RecipeDB *db ); + +public slots: + void populateAll( TQListViewItem *parent = 0 ); + +protected slots: + virtual void createRecipe( const Recipe &, int parent_id ); + virtual void createRecipe( const Element &recipe, const ElementList &categories ); + virtual void modifyRecipe( const Element &recipe, const ElementList &categories ); + virtual void removeRecipe( int ); + virtual void removeRecipe( int, int ); + +protected: + virtual void init(); + virtual void createElement( TQListViewItem * ); + virtual void removeCategory( int id ); + virtual TQDragObject *dragObject(); + virtual bool acceptDrag( TQDropEvent *event ) const; + virtual void populate( TQListViewItem *item ); + virtual TQString tooltip(TQListViewItem *item, int column) const; + + friend class RecipeListToolTip; + + void load(int limit, int offset); + +private: + void moveChildrenToRoot( TQListViewItem * ); + + bool flat_list; + TQListViewItem *m_uncat_item; + TQListViewItem *lastElementCurrLevel; + + KProgressDialog *m_progress_dlg; +}; + +#endif //RECIPELISTVIEW_H diff --git a/src/widgets/unitcombobox.cpp b/src/widgets/unitcombobox.cpp new file mode 100644 index 0000000..526f61a --- /dev/null +++ b/src/widgets/unitcombobox.cpp @@ -0,0 +1,145 @@ +/*************************************************************************** +* Copyright (C) 2006 by * +* 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 "unitcombobox.h" + +#include <tqlistbox.h> + +#include <tdelocale.h> +#include <tdeconfig.h> +#include <tdeglobal.h> + +#include "backends/recipedb.h" +#include "datablocks/elementlist.h" + +UnitComboBox::UnitComboBox( TQWidget *parent, RecipeDB *db, Unit::Type type ) : KComboBox( parent ), + database( db ), m_type(type) +{ + connect( database, TQ_SIGNAL( unitCreated( const Unit & ) ), TQ_SLOT( createUnit( const Unit & ) ) ); + connect( database, TQ_SIGNAL( unitRemoved( int ) ), TQ_SLOT( removeUnit( int ) ) ); +} + +void UnitComboBox::popup() +{ + if ( count() == 1 ) + reload(); + KComboBox::popup(); +} + +Unit UnitComboBox::unit() const +{ + Unit u; + u.name = currentText(); + u.id = id(currentItem()); + return u; +} + +void UnitComboBox::reload() +{ + TQString remember_filter = currentText(); + + UnitList unitList; + database->loadUnits( &unitList, m_type ); + + clear(); + unitComboRows.clear(); + + //Now load the categories + loadUnits(unitList); + + if ( listBox()->findItem( remember_filter, TQt::ExactMatch ) ) { + setCurrentText( remember_filter ); + } +} + +void UnitComboBox::loadUnits( const UnitList &unitList ) +{ + int row = 0; + for ( UnitList::const_iterator it = unitList.begin(); it != unitList.end(); ++it ) { + insertItem( (*it).name ); + unitComboRows.insert( row, (*it).id ); // store unit id's in the combobox position to obtain the unit id later + row++; + } +} + +void UnitComboBox::setSelected( int unitID ) +{ + //do a reverse lookup on the row->id map + TQMap<int, int>::const_iterator it; + for ( it = unitComboRows.begin(); it != unitComboRows.end(); ++it ) { + if ( it.data() == unitID ) { + setCurrentItem(it.key()); + break; + } + } +} + +int UnitComboBox::id( int row ) const +{ + return unitComboRows[ row ]; +} + +void UnitComboBox::createUnit( const Unit &element ) +{ + int row = findInsertionPoint( element.name ); + + insertItem( element.name, row ); + + //now update the map by pushing everything after this item down + TQMap<int, int> new_map; + for ( TQMap<int, int>::iterator it = unitComboRows.begin(); it != unitComboRows.end(); ++it ) { + if ( it.key() >= row ) { + new_map.insert( it.key() + 1, it.data() ); + } + else + new_map.insert( it.key(), it.data() ); + } + unitComboRows = new_map; + unitComboRows.insert( row, element.id ); +} + +void UnitComboBox::removeUnit( int id ) +{ + int row = -1; + for ( TQMap<int, int>::iterator it = unitComboRows.begin(); it != unitComboRows.end(); ++it ) { + if ( it.data() == id ) { + row = it.key(); + removeItem( row ); + unitComboRows.remove( it ); + break; + } + } + + if ( row == -1 ) + return ; + + //now update the map by pushing everything after this item up + TQMap<int, int> new_map; + for ( TQMap<int, int>::iterator it = unitComboRows.begin(); it != unitComboRows.end(); ++it ) { + if ( it.key() > row ) { + new_map.insert( it.key() - 1, it.data() ); + } + else + new_map.insert( it.key(), it.data() ); + } + unitComboRows = new_map; +} + +int UnitComboBox::findInsertionPoint( const TQString &name ) +{ + for ( int i = 1; i < count(); i++ ) { + if ( TQString::localeAwareCompare( name, text( i ) ) < 0 ) + return i; + } + + return count(); +} + +#include "unitcombobox.moc" diff --git a/src/widgets/unitcombobox.h b/src/widgets/unitcombobox.h new file mode 100644 index 0000000..dd75107 --- /dev/null +++ b/src/widgets/unitcombobox.h @@ -0,0 +1,52 @@ +/*************************************************************************** +* Copyright (C) 2006 by * +* 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. * +***************************************************************************/ + +#ifndef UNITCOMBOBOX_H +#define UNITCOMBOBOX_H + +#include <kcombobox.h> + +#include <tqmap.h> + +#include "datablocks/unit.h" + +class RecipeDB; + +class UnitComboBox : public KComboBox +{ + TQ_OBJECT + +public: + UnitComboBox( TQWidget *parent, RecipeDB *db, Unit::Type type = Unit::All ); + + void reload(); + int id( int row ) const; + void setSelected( int unitID ); + Unit unit() const; + +protected: + virtual void popup(); + +private slots: + void createUnit( const Unit & ); + void removeUnit( int id ); + + int findInsertionPoint( const TQString &name ); + +private: + void loadUnits( const UnitList &unitList ); + + RecipeDB *database; + TQMap<int, int> unitComboRows; // Contains the unit id for every given row in the unit combobox + Unit::Type m_type; +}; + +#endif //UNITCOMBOBOX_H + diff --git a/src/widgets/unitlistview.cpp b/src/widgets/unitlistview.cpp new file mode 100644 index 0000000..9c70b1b --- /dev/null +++ b/src/widgets/unitlistview.cpp @@ -0,0 +1,369 @@ +/*************************************************************************** +* Copyright (C) 2004 by * +* Jason Kivlighn ([email protected]) * +* Unai Garro ([email protected]) * +* Cyril Bosselut ([email protected]) * +* * +* Copyright (C) 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 "unitlistview.h" + +#include <tqcombobox.h> +#include <tqheader.h> + +#include <tdemessagebox.h> +#include <tdeconfig.h> +#include <tdelocale.h> +#include <tdeglobal.h> +#include <kiconloader.h> +#include <tdepopupmenu.h> +#include <kdebug.h> + +#include "backends/recipedb.h" +#include "dialogs/createunitdialog.h" +#include "dialogs/dependanciesdialog.h" +#include "datablocks/unit.h" + +class UnitListViewItem : public TQListViewItem +{ +public: + UnitListViewItem( TQListView* qlv, const Unit &u ) : TQListViewItem( qlv ), m_unit(u) + { + updateType(m_unit.type); + } + + virtual TQString text( int column ) const + { + switch ( column ) { + case 0: return m_unit.name; + case 1: return m_unit.name_abbrev; + case 2: return m_unit.plural; + case 3: return m_unit.plural_abbrev; + case 4: return m_type; + case 5: return TQString::number(m_unit.id); + default: return TQString::null; + } + } + + void setType( Unit::Type type ){ m_unit.type = type; updateType(type); } + + Unit unit() const { return m_unit; }; + void setUnit( const Unit &u ) { m_unit = u; } + +protected: + virtual void setText( int column, const TQString &text ) { + switch ( column ) { + case 0: m_unit.name = text; break; + case 1: m_unit.name_abbrev = text; break; + case 2: m_unit.plural = text; break; + case 3: m_unit.plural_abbrev = text; break; + } + } + +private: + void updateType( Unit::Type t ) { + switch ( t ) { + case Unit::Other: m_type = i18n("Other"); break; + case Unit::Mass: m_type = i18n("Mass"); break; + case Unit::Volume: m_type = i18n("Volume"); break; + default: break; + } + } + + Unit m_unit; + TQString m_type; +}; + +UnitListView::UnitListView( TQWidget *parent, RecipeDB *db ) : DBListViewBase( parent,db,db->unitCount() ) +{ + setAllColumnsShowFocus( true ); + setDefaultRenameAction( TQListView::Reject ); +} + +void UnitListView::init() +{ + connect( database, TQ_SIGNAL( unitCreated( const Unit & ) ), TQ_SLOT( checkCreateUnit( const Unit & ) ) ); + connect( database, TQ_SIGNAL( unitRemoved( int ) ), TQ_SLOT( removeUnit( int ) ) ); +} + +void UnitListView::load( int limit, int offset ) +{ + UnitList unitList; + database->loadUnits( &unitList, Unit::All, limit, offset ); + + for ( UnitList::const_iterator it = unitList.begin(); it != unitList.end(); ++it ) { + if ( !( *it ).name.isEmpty() || !( *it ).plural.isEmpty() ) + createUnit( *it ); + } +} + +void UnitListView::checkCreateUnit( const Unit &el ) +{ + if ( handleElement(el.name) ) { //only create this unit if the base class okays it + createUnit(el); + } +} + + +StdUnitListView::StdUnitListView( TQWidget *parent, RecipeDB *db, bool editable ) : UnitListView( parent, db ) +{ + addColumn( i18n( "Unit" ) ); + addColumn( i18n( "Abbreviation" ) ); + addColumn( i18n( "Plural" ) ); + addColumn( i18n( "Abbreviation" ) ); + addColumn( i18n( "Type" ) ); + + 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 ); + setRenameable( 1, true ); + setRenameable( 2, true ); + setRenameable( 3, true ); + setRenameable( 4, true ); + + TDEIconLoader il; + + 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->polish(); + + typeComboBox = new TQComboBox( false, viewport() ); + typeComboBox->insertItem(i18n("Other")); + typeComboBox->insertItem(i18n("Mass")); + typeComboBox->insertItem(i18n("Volume")); + addChild( typeComboBox ); + typeComboBox->hide(); + + connect( typeComboBox, TQ_SIGNAL( activated(int) ), TQ_SLOT( updateType(int) ) ); + connect( this, TQ_SIGNAL( selectionChanged() ), TQ_SLOT( hideTypeCombo() ) ); + + 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 ) ), this, TQ_SLOT( modUnit( TQListViewItem*, const TQPoint &, int ) ) ); + connect( this, TQ_SIGNAL( itemRenamed( TQListViewItem*, const TQString &, int ) ), this, TQ_SLOT( saveUnit( TQListViewItem*, const TQString &, int ) ) ); + } +} + +void StdUnitListView::insertTypeComboBox( TQListViewItem* it ) +{ + TQRect r; + + // Constraints Box1 + r = header() ->sectionRect( 4 ); //start at the section 2 header + r.moveBy( 0, itemRect( it ).y() ); //Move down to the item, note that its height is same as header's right now. + + r.setHeight( it->height() ); // Set the item's height + r.setWidth( header() ->sectionRect( 4 ).width() ); // and width + typeComboBox->setGeometry( r ); + + UnitListViewItem *unit_it = (UnitListViewItem*)it; + typeComboBox->setCurrentItem( unit_it->unit().type ); + + typeComboBox->show(); +} + +void StdUnitListView::updateType( int type ) +{ + UnitListViewItem *unit_it = (UnitListViewItem*)currentItem(); + unit_it->setType((Unit::Type)type); + + database->modUnit( unit_it->unit() ); +} + +void StdUnitListView::hideTypeCombo() +{ + typeComboBox->hide(); +} + +void StdUnitListView::showPopup( TDEListView * /*l*/, TQListViewItem *i, const TQPoint &p ) +{ + if ( i ) + kpop->exec( p ); +} + +void StdUnitListView::createNew() +{ + CreateUnitDialog * unitDialog = new CreateUnitDialog( this ); + + if ( unitDialog->exec() == TQDialog::Accepted ) { + Unit result = unitDialog->newUnit(); + + //check bounds first + if ( checkBounds( result ) + && database->findExistingUnitByName( result.name ) == -1 + && database->findExistingUnitByName( result.plural ) == -1 + ) { + database->createNewUnit( result ); + } + } + delete unitDialog; +} + +void StdUnitListView::remove() +{ + // Find selected unit item + UnitListViewItem* it = (UnitListViewItem*)currentItem(); + + if ( it ) { + int unitID = it->unit().id; + + ElementList recipeDependancies, propertyDependancies, weightDependancies; + database->findUnitDependancies( unitID, &propertyDependancies, &recipeDependancies, &weightDependancies ); + + TQValueList<ListInfo> lists; + if ( !recipeDependancies.isEmpty() ) { + ListInfo info; + info.list = recipeDependancies; + info.name = i18n("Recipes"); + lists << info; + } + if ( !propertyDependancies.isEmpty() ) { + ListInfo info; + info.list = propertyDependancies; + info.name = i18n("Properties"); + lists << info; + } + if ( !weightDependancies.isEmpty() ) { + ListInfo info; + info.list = weightDependancies; + info.name = i18n("Ingredient Weights"); + lists << info; + } + + if ( lists.isEmpty() ) + database->removeUnit( unitID ); + else { // need warning! + DependanciesDialog warnDialog( this, lists ); + if ( !recipeDependancies.isEmpty() ) + warnDialog.setCustomWarning( i18n("You are about to permanantly delete recipes from your database.") ); + if ( warnDialog.exec() == TQDialog::Accepted ) + database->removeUnit( unitID ); + } + } +} + +void StdUnitListView::rename() +{ + TQListViewItem * item = currentItem(); + + if ( item ) { + CreateUnitDialog unitDialog( this, item->text(0), item->text(2), item->text(1), item->text(3), false ); + + if ( unitDialog.exec() == TQDialog::Accepted ) { + UnitListViewItem *unit_item = (UnitListViewItem*)item; + Unit origUnit = unit_item->unit(); + Unit newUnit = unitDialog.newUnit(); + + //for each changed entry, save the change individually + + Unit unit = origUnit; + + if ( newUnit.name != origUnit.name ) { + unit.name = newUnit.name; + unit_item->setUnit( unit ); + saveUnit( unit_item, newUnit.name, 0 ); + + //saveUnit will call database->modUnit which deletes the list item we were using + unit_item = (UnitListViewItem*) findItem( TQString::number(unit.id), 5 ); + } + + if ( newUnit.plural != origUnit.plural ) { + unit.plural = newUnit.plural; + unit_item->setUnit( unit ); + saveUnit( unit_item, newUnit.plural, 2 ); + unit_item = (UnitListViewItem*) findItem( TQString::number(unit.id), 5 ); + } + + if ( !newUnit.name_abbrev.stripWhiteSpace().isEmpty() && newUnit.name_abbrev != origUnit.name_abbrev ) { + unit.name_abbrev = newUnit.name_abbrev; + unit_item->setUnit( unit ); + saveUnit( unit_item, newUnit.name_abbrev, 1 ); + unit_item = (UnitListViewItem*) findItem( TQString::number(unit.id), 5 ); + } + if ( !newUnit.plural_abbrev.stripWhiteSpace().isEmpty() && newUnit.plural_abbrev != origUnit.plural_abbrev ) { + unit.plural_abbrev = newUnit.plural_abbrev; + unit_item->setUnit( unit ); + saveUnit( unit_item, newUnit.plural_abbrev, 3 ); + } + } + } +} + +void StdUnitListView::createUnit( const Unit &unit ) +{ + createElement(new UnitListViewItem( this, unit )); +} + +void StdUnitListView::removeUnit( int id ) +{ + TQListViewItem * item = findItem( TQString::number( id ), 5 ); + removeElement(item); +} + +void StdUnitListView::modUnit( TQListViewItem* i, const TQPoint & /*p*/, int c ) +{ + if ( i ) { + if ( c != 4 ) + UnitListView::rename( i, c ); + else { + insertTypeComboBox(i); + } + } +} + +void StdUnitListView::saveUnit( TQListViewItem* i, const TQString &text, int c ) +{ + //skip abbreviations + if ( c == 0 || c == 2 ) { + if ( !checkBounds( Unit( text, text ) ) ) { + reload(ForceReload); //reset the changed text + return ; + } + } + + int existing_id = database->findExistingUnitByName( text ); + + UnitListViewItem *unit_it = (UnitListViewItem*)i; + int unit_id = unit_it->unit().id; + if ( existing_id != -1 && existing_id != unit_id && !text.stripWhiteSpace().isEmpty() ) { //unit already exists with this label... merge the two + switch ( KMessageBox::warningContinueCancel( this, i18n( "This unit already exists. Continuing will merge these two units into one. Are you sure?" ) ) ) { + case KMessageBox::Continue: { + database->modUnit( unit_it->unit() ); + database->mergeUnits( unit_id, existing_id ); + break; + } + default: + reload(ForceReload); + break; + } + } + else { + database->modUnit( unit_it->unit() ); + } +} + +bool StdUnitListView::checkBounds( const Unit &unit ) +{ + if ( unit.name.length() > uint(database->maxUnitNameLength()) || unit.plural.length() > uint(database->maxUnitNameLength()) ) { + KMessageBox::error( this, TQString( i18n( "Unit name cannot be longer than %1 characters." ) ).arg( database->maxUnitNameLength() ) ); + return false; + } + else if ( unit.name.stripWhiteSpace().isEmpty() || unit.plural.stripWhiteSpace().isEmpty() ) + return false; + + return true; +} + +#include "unitlistview.moc" diff --git a/src/widgets/unitlistview.h b/src/widgets/unitlistview.h new file mode 100644 index 0000000..b71b67f --- /dev/null +++ b/src/widgets/unitlistview.h @@ -0,0 +1,76 @@ +/*************************************************************************** +* Copyright (C) 2004 by * +* Jason Kivlighn ([email protected]) * +* Unai Garro ([email protected]) * +* Cyril Bosselut ([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. * +***************************************************************************/ + +#ifndef UNITLISTVIEW_H +#define UNITLISTVIEW_H + +#include "dblistviewbase.h" + +#include "datablocks/unit.h" + +class TQComboBox; + +class RecipeDB; +class TDEPopupMenu; + +class UnitListView : public DBListViewBase +{ + TQ_OBJECT + +public: + UnitListView( TQWidget *parent, RecipeDB *db ); + +public slots: + virtual void load( int curr_limit, int curr_offset ); + +protected slots: + virtual void createUnit( const Unit & ) = 0; + virtual void removeUnit( int ) = 0; + + void checkCreateUnit( const Unit &el ); + +protected: + virtual void init(); +}; + +class StdUnitListView : public UnitListView +{ + TQ_OBJECT + +public: + StdUnitListView( TQWidget *parent, RecipeDB *db, bool editable = false ); + +protected: + virtual void createUnit( const Unit & ); + virtual void removeUnit( int ); + +private slots: + void showPopup( TDEListView *, TQListViewItem *, const TQPoint & ); + void hideTypeCombo(); + void updateType( int type ); + + void createNew(); + void remove(); + void rename(); + + void modUnit( TQListViewItem* i, const TQPoint &p, int c ); + void saveUnit( TQListViewItem* i, const TQString &text, int c ); + +private: + bool checkBounds( const Unit &unit ); + void insertTypeComboBox( TQListViewItem* ); + + TDEPopupMenu *kpop; + TQComboBox *typeComboBox; +}; + +#endif //UNITLISTVIEW_H diff --git a/src/widgets/weightinput.cpp b/src/widgets/weightinput.cpp new file mode 100644 index 0000000..17d3d0a --- /dev/null +++ b/src/widgets/weightinput.cpp @@ -0,0 +1,63 @@ +/*************************************************************************** +* Copyright (C) 2006 by * +* 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 "weightinput.h" + +#include <tdelocale.h> + +#include "backends/recipedb.h" +#include "datablocks/element.h" +#include "datablocks/weight.h" +#include "prepmethodcombobox.h" + +WeightInput::WeightInput( TQWidget *parent, RecipeDB *database, Unit::Type type, MixedNumber::Format format ) : + AmountUnitInput(parent,database,type,format), + m_database(database) +{ + prepMethodBox = new PrepMethodComboBox(false,this,database,i18n("-No Preparation-")); + prepMethodBox->reload(); + + connect( prepMethodBox, TQ_SIGNAL(activated(int)), TQ_SLOT(emitValueChanged()) ); +} + +void WeightInput::emitValueChanged() +{ + Weight w; + w.perAmount = amount().toDouble(); + + Unit u = unit(); + w.perAmountUnitID = u.id; + w.perAmountUnit = (w.perAmount>1)?u.plural:u.name; + + Element prep; + w.prepMethod = prep.name; + w.prepMethodID = prep.id; + emit valueChanged( w ); +} + +void WeightInput::setPrepMethod( const Element &prep ) +{ + if ( prep.id == -1 ) + prepMethodBox->setCurrentItem(0); + else + prepMethodBox->setSelected( prep.id ); + +} + +Element WeightInput::prepMethod() const +{ + Element prep; + prep.id = prepMethodBox->id( prepMethodBox->currentItem() ); + if ( prep.id != -1 ) + prep.name = prepMethodBox->currentText(); + return prep; +} + +#include "weightinput.moc" diff --git a/src/widgets/weightinput.h b/src/widgets/weightinput.h new file mode 100644 index 0000000..83ff6b3 --- /dev/null +++ b/src/widgets/weightinput.h @@ -0,0 +1,42 @@ +/*************************************************************************** +* Copyright (C) 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. * +***************************************************************************/ + +#ifndef WEIGHTINPUT_H +#define WEIGHTINPUT_H + +#include "widgets/amountunitinput.h" +#include "datablocks/unit.h" + +class RecipeDB; +class PrepMethodComboBox; +class Element; +class Weight; + +class WeightInput : public AmountUnitInput +{ +TQ_OBJECT + +public: + WeightInput( TQWidget *parent, RecipeDB *database, Unit::Type type = Unit::All, MixedNumber::Format f = MixedNumber::MixedNumberFormat ); + + Element prepMethod() const; + void setPrepMethod( const Element & ); + +public slots: + void emitValueChanged(); + +signals: + void valueChanged( const Weight & ); + +private: + PrepMethodComboBox *prepMethodBox; + + RecipeDB *m_database; +}; +#endif |