diff options
Diffstat (limited to 'kexi/widget')
132 files changed, 28826 insertions, 0 deletions
diff --git a/kexi/widget/Makefile.am b/kexi/widget/Makefile.am new file mode 100644 index 00000000..95dc30dd --- /dev/null +++ b/kexi/widget/Makefile.am @@ -0,0 +1,51 @@ +include $(top_srcdir)/kexi/Makefile.global + +lib_LTLIBRARIES = libkexiextendedwidgets.la +libkexiextendedwidgets_la_SOURCES = kexidataawareview.cpp \ + kexibrowser.cpp kexibrowseritem.cpp \ + kexidatatable.cpp kexiquerydesignersqleditor.cpp kexiqueryparameters.cpp \ + kexisectionheader.cpp pixmapcollection.cpp \ + kexiscrollview.cpp kexidbconnectionwidgetbase.ui kexidbconnectionwidget.cpp \ + kexidbconnectionwidgetdetailsbase.ui kexidbdrivercombobox.cpp \ + kexieditor.cpp \ + kexifieldlistview.cpp kexifieldcombobox.cpp kexidatasourcecombobox.cpp \ + kexipropertyeditorview.cpp kexismalltoolbutton.cpp \ + kexicustompropertyfactory.cpp kexicustompropertyfactory_p.cpp \ + kexicharencodingcombobox.cpp \ + kexiprjtypeselectorbase.ui kexiprjtypeselector.cpp + +libkexiextendedwidgets_la_LDFLAGS = $(all_libraries) $(VER_INFO) -Wnounresolved +libkexiextendedwidgets_la_LIBADD = $(LIB_KDEUI) ./utils/libkexiguiutils.la tableview/libkexidatatable.la ../core/libkexicore.la -lktexteditor + +SUBDIRS = utils tableview . relations + +# kde_appsdir Where your application's menu entry (.desktop) should go to. +# kde_icondir Where your icon should go to - better use KDE_ICON. +# kde_sounddir Where your sounds should go to. +# kde_htmldir Where your docs should go to. (contains lang subdirs) +# kde_datadir Where you install application data. (Use a subdir) +# kde_locale Where translation files should go to. (contains lang subdirs) +# kde_cgidir Where cgi-bin executables should go to. +# kde_confdir Where config files should go to (system-wide ones with default values). +# kde_mimedir Where mimetypes .desktop files should go to. +# kde_servicesdir Where services .desktop files should go to. +# kde_servicetypesdir Where servicetypes .desktop files should go to. +# kde_toolbardir Where general toolbar icons should go to (deprecated, use KDE_ICON). +# kde_wallpaperdir Where general wallpapers should go to. +# kde_templatesdir Where templates for the "New" menu (Konqueror/KDesktop) should go to. +# kde_bindir Where executables should go to. Use bin_PROGRAMS or bin_SCRIPTS. +# kde_libdir Where shared libraries should go to. Use lib_LTLIBRARIES. +# kde_moduledir Where modules (e.g. parts) should go to. Use kde_module_LTLIBRARIES. +# kde_styledir Where Qt/KDE widget styles should go to (new in KDE 3). +# kde_designerdir Where Qt Designer plugins should go to (new in KDE 3). + +# set the include path for X, qt and KDE - all_includes must remain last! +INCLUDES = -I$(top_srcdir)/kexi -I$(top_srcdir)/kexi/widget/tableview \ + $(LIB_KEXI_KMDI_INCLUDES) \ + -I$(top_srcdir)/kexi/core \ + -I$(top_srcdir)/lib/kofficecore -I$(top_srcdir)/lib $(all_includes) + +noinst_HEADERS = kexibrowseritem.h kexibrowser_p.h + +METASOURCES = AUTO + diff --git a/kexi/widget/kexibrowser.cpp b/kexi/widget/kexibrowser.cpp new file mode 100644 index 00000000..ca637cd2 --- /dev/null +++ b/kexi/widget/kexibrowser.cpp @@ -0,0 +1,890 @@ +/* This file is part of the KDE project + Copyright (C) 2002, 2003 Lucijan Busch <lucijan@gmx.at> + Copyright (C) 2003-2006 Jaroslaw Staniek <js@iidea.pl> + + 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 Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "kexibrowser.h" +#include "kexibrowser_p.h" +#include "kexibrowseritem.h" + +#include <qheader.h> +#include <qpoint.h> +#include <qpixmapcache.h> +#include <qtoolbutton.h> +#include <qtooltip.h> +#include <qwhatsthis.h> + +#include <kapplication.h> +#include <kiconloader.h> +#include <kdebug.h> +#include <klocale.h> +#include <kpopupmenu.h> +#include <klistview.h> +#include <kmessagebox.h> +#include <klineedit.h> +#include <kimageeffect.h> +#include <kconfig.h> + +#include <kexi.h> +#include <kexipart.h> +#include <kexipartinfo.h> +#include <kexipartitem.h> +#include <kexiproject.h> +#include <kexidialogbase.h> +#include <keximainwindow.h> +#include <kexiutils/identifier.h> +#include <widget/utils/kexiflowlayout.h> +#include <widget/kexismalltoolbutton.h> +#include <kexidb/utils.h> + +/* +class KexiBrowserView : public QWidget +{ + KexiBrowserView*( +}; + +KexiBrowserView::KexiBrowserView(KexiMainWindow *mainWin) + : QWidget(mainWin, "KexiBrowserView") +{ + QVBoxLayout *lyr = new QVBoxLayout(this); + KexiFlowLayout *buttons_flyr = new KexiFlowLayout(lyr); + m_browser = KexiBrowser(this, mainWin); + lyr->addWidget(m_browser); + setFocusProxy(m_browser); +}*/ + +KexiBrowser::KexiBrowser(QWidget* parent, KexiMainWindow *mainWin, int features) + : QWidget(parent, "KexiBrowser") + , m_mainWin(mainWin) + , m_features(features) + , m_actions( new KActionCollection(this) ) + , m_baseItems(199, false) + , m_normalItems(199) + , m_prevSelectedPart(0) + , m_singleClick(false) +// , m_nameEndsWithAsterisk(false) + , m_readOnly(false) +// , m_enableExecuteArea(true) +{ + setCaption(i18n("Project Navigator")); + setIcon(*m_mainWin->icon()); + + QVBoxLayout *lyr = new QVBoxLayout(this); + KexiFlowLayout *buttons_flyr = new KexiFlowLayout(lyr); + + m_list = new KexiBrowserListView(this); + lyr->addWidget(m_list); + m_list->installEventFilter(this); + m_list->renameLineEdit()->installEventFilter(this); + connect( kapp, SIGNAL( settingsChanged(int) ), SLOT( slotSettingsChanged(int) ) ); + slotSettingsChanged(0); + + m_list->header()->hide(); + m_list->addColumn(""); + m_list->setShowToolTips(true); + m_list->setSorting(0); + m_list->sort(); + m_list->setAllColumnsShowFocus(true); + m_list->setTooltipColumn(0); + m_list->renameLineEdit()->setValidator( new KexiUtils::IdentifierValidator(this) ); + m_list->setResizeMode(QListView::LastColumn); + connect(m_list, SIGNAL(contextMenu(KListView *, QListViewItem *, const QPoint &)), + this, SLOT(slotContextMenu(KListView*, QListViewItem *, const QPoint&))); + connect(m_list, SIGNAL(selectionChanged(QListViewItem*)), this, + SLOT(slotSelectionChanged(QListViewItem*))); + + KConfig *config = kapp->config(); + config->setGroup("MainWindow"); + if ((m_features & SingleClickOpensItemOptionEnabled) + && config->readBoolEntry("SingleClickOpensItem", false)) + { + connect(m_list, SIGNAL(executed(QListViewItem*)), this, + SLOT(slotExecuteItem(QListViewItem*))); + } + else { + connect(m_list, SIGNAL(doubleClicked(QListViewItem*)), this, + SLOT(slotExecuteItem(QListViewItem*))); + m_list->enableExecuteArea = false; + } + + // actions + m_openAction = new KAction(i18n("&Open"), "fileopen", 0/*Qt::Key_Enter conflict!*/, this, + SLOT(slotOpenObject()), this, "open_object"); + m_openAction->setToolTip(i18n("Open object")); + m_openAction->setWhatsThis(i18n("Opens object selected in the list")); + +// m_openAction->plug(m_toolbar); + KexiSmallToolButton *btn; + if (m_features & Toolbar) { + btn = new KexiSmallToolButton(this, m_openAction); + buttons_flyr->add(btn); + } + + if (m_mainWin->userMode()) { +//! @todo some of these actions can be supported once we deliver ACLs... + m_deleteAction = 0; + m_renameAction = 0; + m_designAction = 0; + m_editTextAction = 0; + m_newObjectAction = 0; + m_newObjectPopup = 0; + } + else { + m_deleteAction = new KAction(i18n("&Delete"), "editdelete", 0/*Qt::Key_Delete*/, + this, SLOT(slotRemove()), m_actions, "edit_delete"); + //! @todo 1.1: just add "Delete" tooltip and what's this + m_deleteAction->setToolTip(i18n("&Delete").replace("&","")); + + m_renameAction = new KAction(i18n("&Rename"), "", 0, + this, SLOT(slotRename()), m_actions, "edit_rename"); +#ifdef KEXI_SHOW_UNIMPLEMENTED + //todo plugSharedAction("edit_cut",SLOT(slotCut())); + //todo plugSharedAction("edit_copy",SLOT(slotCopy())); + //todo plugSharedAction("edit_paste",SLOT(slotPaste())); +#endif + + m_designAction = new KAction(i18n("&Design"), "edit", 0/*Qt::CTRL + Qt::Key_Enter conflict!*/, this, + SLOT(slotDesignObject()), this, "design_object"); + m_designAction->setToolTip(i18n("Design object")); + m_designAction->setWhatsThis(i18n("Starts designing of the object selected in the list")); + if (m_features & Toolbar) { + btn = new KexiSmallToolButton(this, m_designAction); + buttons_flyr->add(btn); + } + + m_editTextAction = new KAction(i18n("Open in &Text View"), "", 0, this, + SLOT(slotEditTextObject()), this, "editText_object"); + m_editTextAction->setToolTip(i18n("Open object in text view")); + m_editTextAction->setWhatsThis(i18n("Opens selected object in the list in text view")); + + m_newObjectAction = new KAction("", "filenew", 0, this, SLOT(slotNewObject()), this, "new_object"); + if (m_features & Toolbar) { + m_newObjectToolButton = new KexiSmallToolButton(this, "", QIconSet(), "new_object"); + m_newObjectPopup = new KPopupMenu(this, "newObjectPopup"); + connect(m_newObjectPopup, SIGNAL(aboutToShow()), this, SLOT(slotNewObjectPopupAboutToShow())); + // KexiPart::Part* part = Kexi::partManager().part("kexi/table"); + // m_newObjectPopup->insertItem( SmallIconSet(part->info()->createItemIcon()), part->instanceName() ); + m_newObjectToolButton->setPopup(m_newObjectPopup); + m_newObjectToolButton->setPopupDelay(QApplication::startDragTime()); + connect(m_newObjectToolButton, SIGNAL(clicked()), this, SLOT(slotNewObject())); + buttons_flyr->add(m_newObjectToolButton); + + m_deleteObjectToolButton = new KexiSmallToolButton(this, m_deleteAction); + m_deleteObjectToolButton->setTextLabel(""); + buttons_flyr->add(m_deleteObjectToolButton); + } + } + + m_executeAction = new KAction(i18n("Execute"), "player_play", 0, this, + SLOT(slotExecuteObject()), this, "data_execute"); + + m_exportActionMenu = new KActionMenu(i18n("Export")); + m_dataExportAction = new KAction(i18n("Export->To File as Data &Table... ", "To &File as Data Table..."), + "table", 0, this, SLOT(slotExportAsDataTable()), this, "exportAsDataTable"); + m_dataExportAction->setWhatsThis( + i18n("Exports data from the currently selected table or query data to a file.")); + m_exportActionMenu->insert( m_dataExportAction ); + + m_printAction = new KAction(i18n("&Print..."), "fileprint", 0, this, + SLOT(slotPrintItem()), this, "printItem"); + m_printAction->setWhatsThis( + i18n("Prints data from the currently selected table or query.")); + m_pageSetupAction = new KAction(i18n("Page Setup..."), "", 0, this, + SLOT(slotPageSetupForItem()), this, "pageSetupForItem"); + m_pageSetupAction->setWhatsThis( + i18n("Shows page setup for printing the active table or query.")); + + if (m_mainWin->userMode()) { +//! @todo some of these actions can be supported once we deliver ACLs... + m_partPopup = 0; + } + else { + m_partPopup = new KPopupMenu(this, "partPopup"); + m_partPopupTitle_id = m_partPopup->insertTitle(""); + m_newObjectAction->plug(m_partPopup); +#ifdef KEXI_SHOW_UNIMPLEMENTED + m_partPopup->insertSeparator(); + plugSharedAction("edit_paste", m_partPopup); +#endif + } + + if (m_features & ContextMenus) { + //init popups + m_itemPopup = new KPopupMenu(this, "itemPopup"); + m_itemPopupTitle_id = m_itemPopup->insertTitle(""); + m_openAction->plug(m_itemPopup); + m_openAction_id = m_itemPopup->idAt(m_itemPopup->count()-1); + + if (m_designAction) { + m_designAction->plug(m_itemPopup); + m_designAction_id = m_itemPopup->idAt(m_itemPopup->count()-1); + } + + if (m_editTextAction) { + m_editTextAction->plug(m_itemPopup); + m_editTextAction_id = m_itemPopup->idAt(m_itemPopup->count()-1); + } + + if (m_newObjectAction) { + m_newObjectAction->plug(m_itemPopup); + m_itemPopup->insertSeparator(); + } +#ifdef KEXI_SHOW_UNIMPLEMENTED + //todo plugSharedAction("edit_cut", m_itemPopup); + //todo plugSharedAction("edit_copy", m_itemPopup); + //todo m_itemPopup->insertSeparator(); +#endif + m_executeAction->plug(m_itemPopup); + m_executeAction_id = m_itemPopup->idAt(m_itemPopup->count()-1); + + m_exportActionMenu->plug(m_itemPopup); + m_exportActionMenu_id = m_exportActionMenu->menuId(0); + m_itemPopup->insertSeparator(); + m_exportActionMenu_id_sep = m_itemPopup->idAt(m_itemPopup->count()-1); + + m_printAction->plug(m_itemPopup); + m_printAction_id = m_itemPopup->idAt(m_itemPopup->count()-1); + + m_pageSetupAction->plug(m_itemPopup); + m_pageSetupAction_id = m_itemPopup->idAt(m_itemPopup->count()-1); + if (m_renameAction || m_deleteAction) { + m_itemPopup->insertSeparator(); + m_pageSetupAction_id_sep = m_itemPopup->idAt(m_itemPopup->count()-1); + } + else { + m_pageSetupAction_id_sep = -1; + } + + if (m_renameAction) + m_renameAction->plug(m_itemPopup); + if (m_deleteAction) + m_deleteAction->plug(m_itemPopup); + } + else { + m_itemPopup = 0; + } + + if (!(m_features & Writable)) { + setReadOnly(true); + } +} + +KexiBrowser::~KexiBrowser() +{ +} + +void KexiBrowser::setProject(KexiProject* prj, const QString& itemsMimeType, + QString* partManagerErrorMessages) +{ + clear(); + m_itemsMimeType = itemsMimeType; + m_list->setRootIsDecorated(m_itemsMimeType.isEmpty()); + + KexiPart::PartInfoList *pl = Kexi::partManager().partInfoList(); + for (KexiPart::Info *info = pl->first(); info; info = pl->next()) { + if (!info->isVisibleInNavigator()) + continue; + if (!m_itemsMimeType.isEmpty() && info->mimeType()!=m_itemsMimeType.latin1()) + continue; + +// kdDebug() << "KexiMainWindowImpl::initNavigator(): adding " << it->groupName() << endl; + +/* KexiPart::Part *p=Kexi::partManager().part(it); + if (!p) { + //TODO: js - OPTIONALLY: show error + continue; + } + p->createGUIClient(this);*/ + + //load part - we need this to have GUI merged with part's actions +//! @todo FUTURE - don't do that when DESIGN MODE is OFF + KexiPart::Part *p=Kexi::partManager().part(info); + if (p) { + KexiBrowserItem *groupItem = 0; + if (m_itemsMimeType.isEmpty()) { + groupItem = addGroup(*info); + if (!groupItem) + continue; + } + //lookup project's objects (part items) +//! @todo FUTURE - don't do that when DESIGN MODE is OFF + KexiPart::ItemDict *item_dict = prj->items(info); + if (!item_dict) + continue; + for (KexiPart::ItemDictIterator item_it( *item_dict ); item_it.current(); ++item_it) + addItem(*item_it.current(), groupItem, info); + if (!m_itemsMimeType.isEmpty()) + break; //the only group added, so our work is completed + } + else { + //add this error to the list that will be displayed later + QString msg, details; + KexiDB::getHTMLErrorMesage(&Kexi::partManager(), msg, details); + if (!msg.isEmpty() && partManagerErrorMessages) { + if (partManagerErrorMessages->isEmpty()) { + *partManagerErrorMessages = QString("<qt><p>") + +i18n("Errors encountered during loading plugins:")+"<ul>"; + } + partManagerErrorMessages->append( QString("<li>") + msg ); + if (!details.isEmpty()) + partManagerErrorMessages->append(QString("<br>")+details); + partManagerErrorMessages->append("</li>"); + } + } + } + if (partManagerErrorMessages && !partManagerErrorMessages->isEmpty()) + partManagerErrorMessages->append("</ul></p>"); +} + +QString KexiBrowser::itemsMimeType() const +{ + return m_itemsMimeType; +} + +KexiBrowserItem *KexiBrowser::addGroup(KexiPart::Info& info) +{ + if(!info.isVisibleInNavigator()) + return 0; + + KexiBrowserItem *item = new KexiBrowserItem(m_list, &info); + m_baseItems.insert(info.mimeType().lower(), item); + return item; +// kdDebug() << "KexiBrowser::addGroup()" << endl; +} + +KexiBrowserItem* KexiBrowser::addItem(KexiPart::Item& item) +{ + //part object for this item + KexiBrowserItem *parent = item.mimeType().isEmpty() ? 0 : m_baseItems.find(item.mimeType().lower()); + return addItem(item, parent, parent->info()); +} + +KexiBrowserItem* KexiBrowser::addItem(KexiPart::Item& item, KexiBrowserItem *parent, KexiPart::Info* info) +{ +// if (!parent) //TODO: add "Other" part group for that + // return 0; +// kdDebug() << "KexiBrowser::addItem() found parent:" << parent << endl; + KexiBrowserItem *bitem; + if (parent) + bitem = new KexiBrowserItem(parent, info, &item); + else + bitem = new KexiBrowserItem(m_list, info, &item); + m_normalItems.insert(item.identifier(), bitem); + return bitem; +} + +void +KexiBrowser::slotRemoveItem(const KexiPart::Item &item) +{ + KexiBrowserItem *to_remove=m_normalItems.take(item.identifier()); + if (!to_remove) + return; + + KexiBrowserItem *it = static_cast<KexiBrowserItem*>(m_list->selectedItem()); + + QListViewItem *new_item_to_select = 0; + if (it==to_remove) {//compute item to select if current one will be removed + new_item_to_select = it->itemBelow();//nearest item to select + if (!new_item_to_select || new_item_to_select->parent()!=it->parent()) { + new_item_to_select = it->itemAbove(); + } + } + delete to_remove; + + if (new_item_to_select) + m_list->setSelected(new_item_to_select, true); +} + +void +KexiBrowser::slotContextMenu(KListView* /*list*/, QListViewItem *item, const QPoint &pos) +{ + if(!item || !(m_features & ContextMenus)) + return; + KexiBrowserItem *bit = static_cast<KexiBrowserItem*>(item); + KPopupMenu *pm = 0; + if (bit->item()) { + pm = m_itemPopup; + //update popup title + QString title_text = bit->text(0).stripWhiteSpace(); + KexiBrowserItem *par_it = static_cast<KexiBrowserItem*>(bit->parent()); + KexiPart::Part* par_part = 0; + if (par_it && par_it->info() && ((par_part = Kexi::partManager().part(par_it->info()))) && !par_part->instanceCaption().isEmpty()) { + //add part type name + title_text += (" : " + par_part->instanceCaption()); + } + pm->changeTitle(m_itemPopupTitle_id, *bit->pixmap(0), title_text); + } + else if (m_partPopup) { + pm = m_partPopup; + QString title_text = bit->text(0).stripWhiteSpace(); + pm->changeTitle(m_partPopupTitle_id, *bit->pixmap(0), title_text); +/* KexiPart::Part* part = Kexi::partManager().part(bit->info()); + if (part) + m_newObjectAction->setText(i18n("&Create Object: %1...").arg( part->instanceName() )); + else + m_newObjectAction->setText(i18n("&Create Object...")); + m_newObjectAction->setIconSet( SmallIconSet(bit->info()->itemIcon()) );*/ + m_list->setCurrentItem(item); + m_list->repaintItem(item); + } + if (pm) + pm->exec(pos); +} + +void +KexiBrowser::slotExecuteItem(QListViewItem *vitem) +{ +// kdDebug() << "KexiBrowser::slotExecuteItem()" << endl; + KexiBrowserItem *it = static_cast<KexiBrowserItem*>(vitem); + if (!it) + return; + if (!it->item() && !m_singleClick /*annoying when in single click mode*/) { + m_list->setOpen( vitem, !vitem->isOpen() ); + return; + } + if (it->info()->isExecuteSupported()) + emit executeItem( it->item() ); + else + emit openOrActivateItem( it->item(), Kexi::DataViewMode ); +} + +void +KexiBrowser::slotSelectionChanged(QListViewItem* i) +{ + KexiBrowserItem *it = static_cast<KexiBrowserItem*>(i); + if (!it) + return; + KexiPart::Part* part = Kexi::partManager().part(it->info()); + if (!part) { + it = static_cast<KexiBrowserItem*>(it->parent()); + if (it) { + part = Kexi::partManager().part(it->info()); + } + } + + const bool gotitem = it && it->item(); + //bool gotgroup = it && !it->item(); +//TODO: also check if the item is not read only + if (m_deleteAction) { + m_deleteAction->setEnabled(gotitem && !m_readOnly); + if (m_features & Toolbar) { + m_deleteObjectToolButton->setEnabled(gotitem && !m_readOnly); + } + } +#ifdef KEXI_SHOW_UNIMPLEMENTED +//todo setAvailable("edit_cut",gotitem); +//todo setAvailable("edit_copy",gotitem); +//todo setAvailable("edit_edititem",gotitem); +#endif + + m_openAction->setEnabled(gotitem && part && (part->supportedViewModes() & Kexi::DataViewMode)); + if (m_designAction) + m_designAction->setEnabled(gotitem && part && (part->supportedViewModes() & Kexi::DesignViewMode)); + if (m_editTextAction) + m_editTextAction->setEnabled(gotitem && part && (part->supportedViewModes() & Kexi::TextViewMode)); + + if (m_features & ContextMenus) { + m_itemPopup->setItemVisible(m_openAction_id, m_openAction->isEnabled()); + if (m_designAction) + m_itemPopup->setItemVisible(m_designAction_id, m_designAction->isEnabled()); + if (m_editTextAction) + m_itemPopup->setItemVisible(m_editTextAction_id, part && m_editTextAction->isEnabled()); + if (m_executeAction) + m_itemPopup->setItemVisible(m_executeAction_id, gotitem && it->info()->isExecuteSupported()); + if (m_exportActionMenu) { + m_itemPopup->setItemVisible(m_exportActionMenu_id, gotitem && it->info()->isDataExportSupported()); + m_itemPopup->setItemVisible(m_exportActionMenu_id_sep, gotitem && it->info()->isDataExportSupported()); + } + if (m_printAction) + m_itemPopup->setItemVisible(m_printAction_id, gotitem && it->info()->isPrintingSupported()); + if (m_pageSetupAction) { + m_itemPopup->setItemVisible(m_pageSetupAction_id, gotitem && it->info()->isPrintingSupported()); + if (m_pageSetupAction_id_sep!=-1) + m_itemPopup->setItemVisible(m_pageSetupAction_id_sep, gotitem && it->info()->isPrintingSupported()); + } + } + + if (m_prevSelectedPart != part) { + m_prevSelectedPart = part; + if (part) { + if (m_newObjectAction) { + m_newObjectAction->setText(i18n("&Create Object: %1...").arg( part->instanceCaption() )); + m_newObjectAction->setIcon( part->info()->createItemIcon() ); + if (m_features & Toolbar) { + m_newObjectToolButton->setIconSet( part->info()->createItemIcon() ); + QToolTip::add(m_newObjectToolButton, + i18n("Create object: %1").arg( part->instanceCaption().lower() )); + QWhatsThis::add(m_newObjectToolButton, + i18n("Creates a new object: %1").arg( part->instanceCaption().lower() )); + } + } + } else { + if (m_newObjectAction) { + m_newObjectAction->setText(i18n("&Create Object...")); + // m_newObjectToolbarAction->setIconSet( SmallIconSet("filenew") ); + // m_newObjectToolbarAction->setText(m_newObjectAction->text()); + if (m_features & Toolbar) { + m_newObjectToolButton->setIconSet( "filenew" ); + QToolTip::add(m_newObjectToolButton, i18n("Create object")); + QWhatsThis::add(m_newObjectToolButton, i18n("Creates a new object")); + } + } + } + } + emit selectionChanged(it ? it->item() : 0); +} + +void KexiBrowser::installEventFilter ( const QObject * filterObj ) +{ + if (!filterObj) + return; + m_list->installEventFilter ( filterObj ); + QWidget::installEventFilter ( filterObj ); +} + +bool KexiBrowser::eventFilter ( QObject *o, QEvent * e ) +{ + if (o==m_list && e->type()==QEvent::Resize) { + kdDebug() << "resize!" << endl; + } + if (o==m_list->renameLineEdit()) { + if (e->type()==QEvent::Hide) + itemRenameDone(); + } + else if (e->type()==QEvent::KeyPress) { + QKeyEvent *ke = static_cast<QKeyEvent*>(e); + if (ke->key()==Qt::Key_Enter || ke->key()==Qt::Key_Return) { + if (0==(ke->state() & (Qt::ShiftButton|Qt::ControlButton|Qt::AltButton))) { + QListViewItem *it = m_list->selectedItem(); + if (it) + slotExecuteItem(it); + } + else if (Qt::ControlButton==(ke->state() & (Qt::ShiftButton|Qt::ControlButton|Qt::AltButton))) { + slotDesignObject(); + } + } + } + else if (e->type()==QEvent::AccelOverride) { + QKeyEvent *ke = static_cast<QKeyEvent*>(e); + //override delete action + if (ke->key()==Qt::Key_Delete && ke->state()==Qt::NoButton) { + slotRemove(); + ke->accept(); + return true; + } + //override rename action + if (ke->key()==Qt::Key_F2 && ke->state()==Qt::NoButton) { + slotRename(); + ke->accept(); + return true; + } +/* else if (ke->key()==Qt::Key_Enter || ke->key()==Qt::Key_Return) { + if (ke->state()==ControlButton) { + slotDesignObject(); + } + else if (ke->state()==0 && !m_list->renameLineEdit()->isVisible()) { + QListViewItem *it = m_list->selectedItem(); + if (it) + slotExecuteItem(it); + } + ke->accept(); + return true; + }*/ + } + return false; +} + +void KexiBrowser::slotRemove() +{ + if (!m_deleteAction || !m_deleteAction->isEnabled() || !(m_features & Writable)) + return; + KexiBrowserItem *it = static_cast<KexiBrowserItem*>(m_list->selectedItem()); + if (!it || !it->item()) + return; + emit removeItem( it->item() ); +} + +void KexiBrowser::slotNewObject() +{ + if (!m_newObjectAction || !(m_features & Writable)) + return; + KexiBrowserItem *it = static_cast<KexiBrowserItem*>(m_list->selectedItem()); + if (!it || !it->info()) + return; + emit newItem( it->info() ); +} + +void KexiBrowser::slotOpenObject() +{ + KexiBrowserItem *it = static_cast<KexiBrowserItem*>(m_list->selectedItem()); + if (!it || !it->item()) + return; + emit openItem( it->item(), Kexi::DataViewMode ); +} + +void KexiBrowser::slotDesignObject() +{ + if (!m_designAction) + return; + KexiBrowserItem *it = static_cast<KexiBrowserItem*>(m_list->selectedItem()); + if (!it || !it->item()) + return; + emit openItem( it->item(), Kexi::DesignViewMode ); +} + +void KexiBrowser::slotEditTextObject() +{ + if (!m_editTextAction) + return; + KexiBrowserItem *it = static_cast<KexiBrowserItem*>(m_list->selectedItem()); + if (!it || !it->item()) + return; + emit openItem( it->item(), Kexi::TextViewMode ); +} + +void KexiBrowser::slotCut() +{ + if (!(m_features & Writable)) + return; +// KEXI_UNFINISHED_SHARED_ACTION("edit_cut"); + //TODO +} + +void KexiBrowser::slotCopy() +{ +// KEXI_UNFINISHED_SHARED_ACTION("edit_copy"); + //TODO +} + +void KexiBrowser::slotPaste() +{ + if (!(m_features & Writable)) + return; +// KEXI_UNFINISHED_SHARED_ACTION("edit_paste"); + //TODO +} + +void KexiBrowser::slotRename() +{ + if (!m_renameAction || !(m_features & Writable)) + return; + KexiBrowserItem *it = static_cast<KexiBrowserItem*>(m_list->selectedItem()); + if (it) + m_list->rename(it, 0); +} + +void KexiBrowser::itemRenameDone() +{ + if (!(m_features & Writable)) + return; + KexiBrowserItem *it = static_cast<KexiBrowserItem*>(m_list->selectedItem()); + if (!it) + return; + QString txt = it->text(0).stripWhiteSpace(); + bool ok = it->item()->name().lower()!=txt.lower(); //the new name must be different + if (ok) { + /* TODO */ + emit renameItem(it->item(), txt, ok); + } + if (!ok) { + txt = it->item()->name(); //revert + } + //"modified" flag has been removed before editing: readd it + if (m_list->nameEndsWithAsterisk) { + txt += "*"; + m_list->nameEndsWithAsterisk = false; + } + it->setText(0, txt); + it->parent()->sort(); + setFocus(); +} + +void KexiBrowser::setFocus() +{ + if (!m_list->selectedItem() && m_list->firstChild())//select first + m_list->setSelected(m_list->firstChild(), true); + m_list->setFocus(); +} + +void KexiBrowser::updateItemName( KexiPart::Item& item, bool dirty ) +{ + if (!(m_features & Writable)) + return; + KexiBrowserItem *bitem = m_normalItems[item.identifier()]; + if (!bitem) + return; + bitem->setText( 0, item.name() + (dirty ? "*" : "") ); +} + +void KexiBrowser::slotSettingsChanged(int) +{ + m_singleClick = KGlobalSettings::singleClick(); +} + +void KexiBrowser::selectItem(KexiPart::Item& item) +{ + KexiBrowserItem *bitem = m_normalItems[item.identifier()]; + if (!bitem) + return; + m_list->setSelected(bitem, true); + m_list->ensureItemVisible(bitem); + m_list->setCurrentItem(bitem); +} + +void KexiBrowser::clearSelection() +{ + m_list->clearSelection(); + QListViewItem *item = m_list->firstChild(); + if (item) { + m_list->ensureItemVisible(item); + } +} + +void KexiBrowser::slotNewObjectPopupAboutToShow() +{ + if ((m_features & Toolbar) && m_newObjectPopup && m_newObjectPopup->count()==0) { + //preload items + KexiPart::PartInfoList *list = Kexi::partManager().partInfoList(); //this list is properly sorted + for (KexiPart::PartInfoListIterator it(*list); it.current(); ++it) { + //add an item to "New object" toolbar popup + KAction *action = m_mainWin->actionCollection()->action( + KexiPart::nameForCreateAction(*it.current()) ); + if (action) { + action->plug(m_newObjectPopup); + } + else { + //! @todo err + } + } + } +} + +void KexiBrowser::slotExecuteObject() +{ + if (!m_executeAction) + return; + KexiPart::Item* item = selectedPartItem(); + if (item) + emit executeItem( item ); +} + +void KexiBrowser::slotExportAsDataTable() +{ + if (!m_dataExportAction) + return; + KexiPart::Item* item = selectedPartItem(); + if (item) + emit exportItemAsDataTable( item ); +} + +KexiPart::Item* KexiBrowser::selectedPartItem() const +{ + KexiBrowserItem *it = static_cast<KexiBrowserItem*>(m_list->selectedItem()); + return it ? it->item() : 0; +} + +bool KexiBrowser::actionEnabled(const QCString& actionName) const +{ + if (actionName=="project_export_data_table" && (m_features & ContextMenus)) + return m_itemPopup->isItemVisible(m_exportActionMenu_id); + kdWarning() << "KexiBrowser::actionEnabled() no such action: " << actionName << endl; + return false; +} + +void KexiBrowser::slotPrintItem() +{ + if (!m_printAction) + return; + KexiPart::Item* item = selectedPartItem(); + if (item) + emit printItem( item ); +} + +void KexiBrowser::slotPageSetupForItem() +{ + if (!m_pageSetupAction) + return; + KexiPart::Item* item = selectedPartItem(); + if (item) + emit pageSetupForItem( item ); +} + + +void KexiBrowser::setReadOnly(bool set) +{ + m_readOnly = set; + if (m_deleteAction) + m_deleteAction->setEnabled(!m_readOnly); + if (m_renameAction) + m_renameAction->setEnabled(!m_readOnly); + if (m_newObjectAction) { + m_newObjectAction->setEnabled(!m_readOnly); + if (m_features & Toolbar) { + m_newObjectPopup->setEnabled(!m_readOnly); + m_newObjectToolButton->setEnabled(!m_readOnly); + } + } +} + +bool KexiBrowser::isReadOnly() const +{ + return m_readOnly; +} + +void KexiBrowser::clear() +{ + m_list->clear(); +} + +//-------------------------------------------- +KexiBrowserListView::KexiBrowserListView(QWidget *parent) + : KListView(parent, "KexiBrowserListView") + , nameEndsWithAsterisk(false) + , enableExecuteArea(true) +{ +} + +KexiBrowserListView::~KexiBrowserListView() +{ +} + +void KexiBrowserListView::rename(QListViewItem *item, int c) +{ + if (renameLineEdit()->isVisible()) + return; + KexiBrowserItem *it = static_cast<KexiBrowserItem*>(item); + if (it->item() && c==0) { + //only edit 1st column for items, not item groups +//TODO: also check it this item is not read-only +// item->setText(0, item->text(0).mid(1,item->text(0).length()-2)); + //remove "modified" flag for editing + nameEndsWithAsterisk = item->text(0).endsWith("*"); + if (nameEndsWithAsterisk) + item->setText(0, item->text(0).left(item->text(0).length()-1)); + KListView::rename(item, c); + adjustColumn(0); + } +} + +bool KexiBrowserListView::isExecuteArea( const QPoint& point ) +{ + return enableExecuteArea && KListView::isExecuteArea(point); +} + +#include "kexibrowser.moc" +#include "kexibrowser_p.moc" diff --git a/kexi/widget/kexibrowser.h b/kexi/widget/kexibrowser.h new file mode 100644 index 00000000..1bdc5d25 --- /dev/null +++ b/kexi/widget/kexibrowser.h @@ -0,0 +1,183 @@ +/* This file is part of the KDE project + Copyright (C) 2002, 2003 Lucijan Busch <lucijan@gmx.at> + Copyright (C) 2003-2006 Jaroslaw Staniek <js@iidea.pl> + + 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 Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIBROWSER_H +#define KEXIBROWSER_H + +#include <klistview.h> +#include <qasciidict.h> +#include <qintdict.h> + +class QListViewItem; +class KIconLoader; +class KPopupMenu; +class KAction; +class KActionMenu; +class KActionCollection; +class KListView; +class KToolBar; +class KexiBrowserItem; +class KexiView; +class KexiMainWindow; +class KexiSmallToolButton; +class KexiBrowserListView; + +namespace KexiPart +{ + class Info; + class Item; + class Part; +} +class KexiProject; + +//! @short The Main Kexi navigator widget +class KEXIEXTWIDGETS_EXPORT KexiBrowser : public QWidget +{ + Q_OBJECT + + public: + enum Features { + Writable = 1, //!< the browser supports actions that modify the project (e.g. delete, rename) + ContextMenus = 2, //!< the browser supports context menu + Toolbar = 4, //!< the browser displays + SingleClickOpensItemOptionEnabled = 8, //!< enables "SingleClickOpensItem" option + DefaultFeatures = Writable | ContextMenus | Toolbar + | SingleClickOpensItemOptionEnabled //!< the default + }; + + KexiBrowser(QWidget* parent, KexiMainWindow *mainWin, int features = DefaultFeatures); + virtual ~KexiBrowser(); + + /*! Sets project \a prj for this browser. If \a partManagerErrorMessages is not NULL + it will be set to error message if there's a problem with loading any KexiPart. + If \a itemsMimeType is empty (the default), items of all mime types are displayed, + items for only one mime type are displayed. In the latter case, no group (parent) + items are displayed. + Previous items are removed. */ + void setProject(KexiProject* prj, const QString& itemsMimeType = QString::null, + QString* partManagerErrorMessages = 0); + + /*! \return items' mime type previously set by setProject. Returns empty string + if setProject() was not executed yet or itemsMimeType argument of setProject() was + empty (i.e. all mime types are displayed). */ + QString itemsMimeType() const; + + KexiPart::Item* selectedPartItem() const; + + void installEventFilter ( const QObject * filterObj ); + virtual bool eventFilter ( QObject *o, QEvent * e ); + + bool actionEnabled(const QCString& actionName) const; + + public slots: + KexiBrowserItem* addGroup(KexiPart::Info& info); + KexiBrowserItem* addItem(KexiPart::Item& item); + void slotRemoveItem(const KexiPart::Item &item); + virtual void setFocus(); + void updateItemName(KexiPart::Item& item, bool dirty); + void selectItem(KexiPart::Item& item); + void clearSelection(); + void clear(); + + //! Sets by main window to disable actions that may try to modify the project. + //! Does not disable actions like opening objects. + void setReadOnly(bool set); + + bool isReadOnly() const; + + signals: + void openItem( KexiPart::Item*, int viewMode ); + + /*! this signal is emmited when user double clicked (or single -depending on settings) + or pressed return ky on the part item. + This signal differs from openItem() signal in that if the object is already opened + in view mode other than \a viewMode, the mode is not changed. */ + void openOrActivateItem( KexiPart::Item*, int viewMode ); + + void newItem( KexiPart::Info* ); + + void removeItem( KexiPart::Item* ); + + void renameItem( KexiPart::Item *item, const QString& _newName, bool &succes ); + + void selectionChanged( KexiPart::Item* item ); + + void executeItem( KexiPart::Item* ); + + void exportItemAsDataTable( KexiPart::Item* ); + + void printItem( KexiPart::Item* ); + + void pageSetupForItem( KexiPart::Item* ); + + protected slots: + void slotContextMenu(KListView*, QListViewItem *i, const QPoint &point); + void slotExecuteItem(QListViewItem *item); + void slotSelectionChanged(QListViewItem* i); + void slotSettingsChanged(int); + void slotNewObjectPopupAboutToShow(); + + void slotNewObject(); + void slotOpenObject(); + void slotDesignObject(); + void slotEditTextObject(); + void slotRemove(); + void slotCut(); + void slotCopy(); + void slotPaste(); + void slotRename(); + void slotExecuteObject(); + void slotExportAsDataTable(); + void slotPrintItem(); + void slotPageSetupForItem(); + + protected: + void itemRenameDone(); + KexiBrowserItem* addItem(KexiPart::Item& item, KexiBrowserItem *parent, KexiPart::Info* info); + + KexiMainWindow *m_mainWin; + int m_features; + KexiBrowserListView *m_list; + KActionCollection *m_actions; + QAsciiDict<KexiBrowserItem> m_baseItems; + QIntDict<KexiBrowserItem> m_normalItems; + KPopupMenu *m_itemPopup, *m_partPopup; + KAction *m_deleteAction, *m_renameAction, + *m_newObjectAction, // *m_newObjectToolbarAction, + *m_openAction, *m_designAction, *m_editTextAction, + *m_executeAction, + *m_dataExportAction, *m_printAction, *m_pageSetupAction; + KActionMenu* m_exportActionMenu; + KPopupMenu* m_newObjectPopup; + int m_itemPopupTitle_id, m_partPopupTitle_id, + m_openAction_id, m_designAction_id, m_editTextAction_id, + m_executeAction_id, + m_exportActionMenu_id, m_exportActionMenu_id_sep, + m_printAction_id, m_pageSetupAction_id, m_pageSetupAction_id_sep; + + KexiPart::Part *m_prevSelectedPart; + KToolBar *m_toolbar; + KexiSmallToolButton *m_newObjectToolButton, *m_deleteObjectToolButton; + QString m_itemsMimeType; + bool m_singleClick : 1; + bool m_readOnly : 1; +}; + +#endif diff --git a/kexi/widget/kexibrowser_p.h b/kexi/widget/kexibrowser_p.h new file mode 100644 index 00000000..b5610d24 --- /dev/null +++ b/kexi/widget/kexibrowser_p.h @@ -0,0 +1,43 @@ +/* This file is part of the KDE project + Copyright (C) 2002, 2003 Lucijan Busch <lucijan@gmx.at> + Copyright (C) 2003-2005 Jaroslaw Staniek <js@iidea.pl> + + 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 Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIBROWSER_P_H +#define KEXIBROWSER_P_H + +#include <klistview.h> + +/*! @internal */ +class KexiBrowserListView : public KListView +{ + Q_OBJECT + public: + KexiBrowserListView(QWidget *parent); + ~KexiBrowserListView(); + + virtual bool isExecuteArea( const QPoint& point ); + + bool nameEndsWithAsterisk : 1; + bool enableExecuteArea : 1; //!< used in isExecuteArea() + public slots: + virtual void rename(QListViewItem *item, int c); + protected: +}; + +#endif diff --git a/kexi/widget/kexibrowseritem.cpp b/kexi/widget/kexibrowseritem.cpp new file mode 100644 index 00000000..087039f0 --- /dev/null +++ b/kexi/widget/kexibrowseritem.cpp @@ -0,0 +1,91 @@ +/* This file is part of the KDE project + Copyright (C) 2002-2003 Lucijan Busch <lucijan@gmx.at> + Copyright (C) 2003-2004 Jaroslaw Staniek <js@iidea.pl> + + 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 Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "kexibrowseritem.h" + +#include <kdebug.h> +#include <kiconloader.h> +#include <core/kexipartinfo.h> + +KexiBrowserItem::KexiBrowserItem(KListView *parent, KexiPart::Info *i) + : KListViewItem(parent, i->groupName()) + , m_info(i) + , m_item(0) +{ + setPixmap(0, SmallIcon(i->itemIcon())); + setOpen(true); +//ugly setSelectable(false); + initItem(); + m_fifoSorting = 1; //because this is top level item +} + +KexiBrowserItem::KexiBrowserItem(KListViewItem *parent, KexiPart::Info *i, KexiPart::Item *item) + : KListViewItem(parent, item->name()) + , m_info(i) + , m_item(item) +{ + setPixmap(0, SmallIcon(i->itemIcon())); + initItem(); +} + +KexiBrowserItem::KexiBrowserItem(KListView *parent, KexiPart::Info *i, KexiPart::Item *item) + : KListViewItem(parent, item->name()) + , m_info(i) + , m_item(item) +{ + setPixmap(0, SmallIcon(i->itemIcon())); + initItem(); +} + +KexiBrowserItem::~KexiBrowserItem() +{ +} + +void KexiBrowserItem::initItem() +{ + m_fifoSorting = 0; + int sortKey = 0; + // set sorting key with FIFO order + if (parent()) { + sortKey = parent()->childCount(); + } else if (listView()) { + sortKey = listView()->childCount(); + } + m_sortKey.sprintf("%2.2d",sortKey); +// kdDebug() << "m_sortKey=" << m_sortKey << endl; +} + +void +KexiBrowserItem::clearChildren() +{ + KexiBrowserItem* child; + + while((child = static_cast<KexiBrowserItem*>(firstChild()))) + { + delete child; + } +} + +QString KexiBrowserItem::key( int column, bool ascending ) const +{ +// kdDebug() << "KexiBrowserItem::key() : " << (m_fifoSorting ? m_sortKey : KListViewItem::key(column,ascending)) << endl; + return m_fifoSorting ? m_sortKey : KListViewItem::key(column,ascending); +} + diff --git a/kexi/widget/kexibrowseritem.h b/kexi/widget/kexibrowseritem.h new file mode 100644 index 00000000..12c2fef6 --- /dev/null +++ b/kexi/widget/kexibrowseritem.h @@ -0,0 +1,70 @@ +/* This file is part of the KDE project + Copyright (C) 2002-2003 Lucijan Busch <lucijan@gmx.at> + Copyright (C) 2003-2004 Jaroslaw Staniek <js@iidea.pl> + + 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 Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIBROWSERITEM_H +#define KEXIBROWSERITEM_H + +#include <klistview.h> +#include <qstring.h> + +#include <core/kexipartitem.h> + +namespace KexiPart +{ + class Info; +} + +//! @short List view item for the navigator widget (KexiBrowser) +//! Used for creating group items as well as object items +class KEXIEXTWIDGETS_EXPORT KexiBrowserItem : public KListViewItem +{ + public: + //! Creates group item for part \a i + KexiBrowserItem(KListView *parent, KexiPart::Info *i); + + //! Creates item for object \a item defined by part \a i for \a parent + KexiBrowserItem(KListViewItem *parent, KexiPart::Info *i, KexiPart::Item *item); + + //! Creates item for object \a item defined by part \a i, without parent + //! (used in a case when KexiBrowser::itemsMimeType() is not empty) + KexiBrowserItem(KListView *parent, KexiPart::Info *i, KexiPart::Item *item); + + virtual ~KexiBrowserItem(); + + void clearChildren(); + + //! \return part info; should not be null. + KexiPart::Info *info() const { return m_info; } + + //! \return part item. Can be null if the browser item is a "folder", i.e. a parent node. + KexiPart::Item* item() const { return m_item; } + + protected: + void initItem(); + virtual QString key( int column, bool ascending ) const; + + KexiPart::Info *m_info; + KexiPart::Item *m_item; + + QString m_sortKey; + bool m_fifoSorting : 1; +}; + +#endif diff --git a/kexi/widget/kexicharencodingcombobox.cpp b/kexi/widget/kexicharencodingcombobox.cpp new file mode 100644 index 00000000..c1f50542 --- /dev/null +++ b/kexi/widget/kexicharencodingcombobox.cpp @@ -0,0 +1,114 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "kexicharencodingcombobox.h" + +#include <qtextcodec.h> + +#include <kdebug.h> +#include <klocale.h> +#include <kglobal.h> +#include <kcharsets.h> + +KexiCharacterEncodingComboBox::KexiCharacterEncodingComboBox( + QWidget* parent, const QString& selectedEncoding ) + : KComboBox( parent, "KexiCharacterEncodingComboBox" ) + , m_defaultEncodingAdded(false) +{ + QString defaultEncoding(QString::fromLatin1(KGlobal::locale()->encoding())); + QString defaultEncodingDescriptiveName; + + QString _selectedEncoding = selectedEncoding; + if (_selectedEncoding.isEmpty()) + _selectedEncoding = QString::fromLatin1(KGlobal::locale()->encoding()); + + QStringList descEncodings(KGlobal::charsets()->descriptiveEncodingNames()); + QStringList::ConstIterator it = descEncodings.constBegin(); + + for (uint id = 0; it!=descEncodings.constEnd(); ++it) + { + bool found = false; + QString name( KGlobal::charsets()->encodingForName( *it ) ); + QTextCodec *codecForEnc = KGlobal::charsets()->codecForName(name, found); + if (found) { + insertItem(*it); + if (codecForEnc->name() == defaultEncoding || name == defaultEncoding) { + defaultEncodingDescriptiveName = *it; + //remember, do not add, will be prepended later + } + else { + m_encodingDescriptionForName.insert(name, *it); + } + if (codecForEnc->name() == _selectedEncoding || name == _selectedEncoding) { + setCurrentItem(id); + } + id++; + } + } + + //prepend default encoding, if present + if (!defaultEncodingDescriptiveName.isEmpty()) { + m_defaultEncodingAdded = true; + QString desc = i18n("Text encoding: Default", "Default: %1") + .arg(defaultEncodingDescriptiveName); + insertItem( desc, 0 ); + if (_selectedEncoding==defaultEncoding) { + setCurrentItem(0); + } + else + setCurrentItem(currentItem()+1); + m_encodingDescriptionForName.insert(defaultEncoding, desc); + } +} + +KexiCharacterEncodingComboBox::~KexiCharacterEncodingComboBox() +{ +} + +QString KexiCharacterEncodingComboBox::selectedEncoding() const +{ + if (defaultEncodingSelected()) { + return QString::fromLatin1(KGlobal::locale()->encoding()); + } + else { + return KGlobal::charsets()->encodingForName( currentText() ); + } +} + +void KexiCharacterEncodingComboBox::setSelectedEncoding(const QString& encodingName) +{ + QString desc = m_encodingDescriptionForName[encodingName]; + if (desc.isEmpty()) { + kdWarning() << "KexiCharacterEncodingComboBox::setSelectedEncoding(): " + "no such encoding \"" << encodingName << "\"" << endl; + return; + } + setCurrentText(desc); +} + +bool KexiCharacterEncodingComboBox::defaultEncodingSelected() const +{ + return m_defaultEncodingAdded && 0==currentItem(); +} + +void KexiCharacterEncodingComboBox::selectDefaultEncoding() +{ + if (m_defaultEncodingAdded) + setCurrentItem(0); +} diff --git a/kexi/widget/kexicharencodingcombobox.h b/kexi/widget/kexicharencodingcombobox.h new file mode 100644 index 00000000..9af5a69f --- /dev/null +++ b/kexi/widget/kexicharencodingcombobox.h @@ -0,0 +1,48 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef KEXICHARENCODINGCOMBO_H +#define KEXICHARENCODINGCOMBO_H + +#include <qmap.h> +#include <kcombobox.h> + +/*! @short Combobox widget providing a list of possible character encodings. +*/ +class KEXIEXTWIDGETS_EXPORT KexiCharacterEncodingComboBox : public KComboBox +{ + public: + //! Constructs a new combobox. \a selectedEncoding can be provided to preselect encoding. + //! If it is not provided, default encoding is selected for current system settings. + KexiCharacterEncodingComboBox( QWidget* parent = 0, + const QString& selectedEncoding = QString::null ); + ~KexiCharacterEncodingComboBox(); + + QString selectedEncoding() const; + void setSelectedEncoding(const QString& encodingName); + //! Selects default encoding, if present + void selectDefaultEncoding(); + bool defaultEncodingSelected() const; + + protected: + QMap<QString,QString> m_encodingDescriptionForName; + bool m_defaultEncodingAdded : 1; +}; + +#endif diff --git a/kexi/widget/kexicustompropertyfactory.cpp b/kexi/widget/kexicustompropertyfactory.cpp new file mode 100644 index 00000000..3df9e233 --- /dev/null +++ b/kexi/widget/kexicustompropertyfactory.cpp @@ -0,0 +1,110 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl> + + 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 Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "kexicustompropertyfactory.h" +#include "kexicustompropertyfactory_p.h" +#include <kexiutils/identifier.h> + +#include <koproperty/customproperty.h> + +using namespace KoProperty; + +//! @internal +class PixmapIdCustomProperty : public CustomProperty +{ + public: + PixmapIdCustomProperty(Property *parent) + : CustomProperty(parent) { + } + virtual ~PixmapIdCustomProperty() {}; + virtual void setValue(const QVariant &value, bool rememberOldValue) { + Q_UNUSED( value ); + Q_UNUSED( rememberOldValue); + } + virtual QVariant value() const { return m_property->value(); } + virtual bool handleValue() const { + return false; //m_property->type()==KexiCustomPropertyFactory::PixmapData; + } +}; + +//! @internal +class IdentifierCustomProperty : public CustomProperty +{ + public: + IdentifierCustomProperty(Property *parent) + : CustomProperty(parent) { + } + virtual ~IdentifierCustomProperty() {}; + virtual void setValue(const QVariant &value, bool rememberOldValue) + { + Q_UNUSED(rememberOldValue); + if (!value.toString().isEmpty()) + m_value = KexiUtils::string2Identifier(value.toString()).lower(); + } + virtual QVariant value() const { return m_value; } + virtual bool handleValue() const { + return true; + } + QString m_value; +}; + +//--------------- + +KexiCustomPropertyFactory::KexiCustomPropertyFactory(QObject* parent) +: CustomPropertyFactory(parent) +{ +} + +KexiCustomPropertyFactory::~KexiCustomPropertyFactory() +{ +} + +CustomProperty* KexiCustomPropertyFactory::createCustomProperty(Property *parent) +{ + const int type = parent->type(); + if (type==(int)KexiCustomPropertyFactory::PixmapId) + return new PixmapIdCustomProperty(parent); + else if (type==(int)KexiCustomPropertyFactory::Identifier) + return new IdentifierCustomProperty(parent); + return 0; +} + +Widget* KexiCustomPropertyFactory::createCustomWidget(Property *prop) +{ + const int type = prop->type(); + if (type==(int)KexiCustomPropertyFactory::PixmapId) + return new KexiImagePropertyEdit(prop); + else if (type==(int)KexiCustomPropertyFactory::Identifier) + return new KexiIdentifierPropertyEdit(prop); + + return 0; +} + +void KexiCustomPropertyFactory::init() +{ + if (KoProperty::FactoryManager::self()->factoryForEditorType(KexiCustomPropertyFactory::PixmapId)) + return; //already registered + + // register custom editors and properties + KexiCustomPropertyFactory *factory = new KexiCustomPropertyFactory(KoProperty::FactoryManager::self()); + QValueList<int> types; + types << KexiCustomPropertyFactory::PixmapId << KexiCustomPropertyFactory::Identifier; + KoProperty::FactoryManager::self()->registerFactoryForProperties(types, factory); + KoProperty::FactoryManager::self()->registerFactoryForEditors(types, factory); +} diff --git a/kexi/widget/kexicustompropertyfactory.h b/kexi/widget/kexicustompropertyfactory.h new file mode 100644 index 00000000..c6e6e973 --- /dev/null +++ b/kexi/widget/kexicustompropertyfactory.h @@ -0,0 +1,45 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl> + + 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 Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXICUSTOMPROPFACTORY_H +#define KEXICUSTOMPROPFACTORY_H + +#include <koproperty/property.h> +#include <koproperty/factory.h> + +//! Kexi-specific custom property factory for KoProperty library +class KEXIEXTWIDGETS_EXPORT KexiCustomPropertyFactory : public KoProperty::CustomPropertyFactory +{ + public: + enum PropertyType { + PixmapId = KoProperty::UserDefined+0, //!< Shared Kexi pixmap + Identifier = KoProperty::UserDefined+1 //!< string allowing nonempty identifiers + }; + + //! Called once to register all propert and editor types provided by this factory. + static void init(); + + KexiCustomPropertyFactory(QObject* parent); + virtual ~KexiCustomPropertyFactory(); + + virtual KoProperty::CustomProperty* createCustomProperty(KoProperty::Property *parent); + virtual KoProperty::Widget* createCustomWidget(KoProperty::Property *prop); +}; + +#endif diff --git a/kexi/widget/kexicustompropertyfactory_p.cpp b/kexi/widget/kexicustompropertyfactory_p.cpp new file mode 100644 index 00000000..0e0a054d --- /dev/null +++ b/kexi/widget/kexicustompropertyfactory_p.cpp @@ -0,0 +1,108 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl> + + 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 Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "kexicustompropertyfactory_p.h" + +#include <qlineedit.h> +#include <kdebug.h> +#include <koproperty/property.h> +#include <kexiutils/identifier.h> + +using namespace KoProperty; + +KexiImagePropertyEdit::KexiImagePropertyEdit( + Property *property, QWidget *parent, const char *name) + : PixmapEdit(property, parent, name) + , m_id(0) +{ +} + +KexiImagePropertyEdit::~KexiImagePropertyEdit() +{ +} + +void KexiImagePropertyEdit::selectPixmap() +{ + QString fileName( PixmapEdit::selectPixmapFileName() ); + if (fileName.isEmpty()) + return; + KexiBLOBBuffer::Handle h(KexiBLOBBuffer::self()->insertPixmap( KURL(fileName) )); + setValue((uint)/*! @todo unsafe*/h.id()); +#if 0 //will be reenabled for new image collection + if(!m_manager->activeForm() || !property()) + return; + + ObjectTreeItem *item = m_manager->activeForm()->objectTree()->lookup(m_manager->activeForm()->selectedWidget()->name()); + QString name = item ? item->pixmapName(property()->name()) : ""; + PixmapCollectionChooser dialog( m_manager->activeForm()->pixmapCollection(), name, topLevelWidget() ); + if(dialog.exec() == QDialog::Accepted) { + setValue(dialog.pixmap(), true); + item->setPixmapName(property()->name(), dialog.pixmapName()); + } +#endif +} + +QVariant KexiImagePropertyEdit::value() const +{ + return (uint)/*! @todo unsafe*/m_id; +} + +void KexiImagePropertyEdit::setValue(const QVariant &value, bool emitChange) +{ + m_id = value.toInt(); + PixmapEdit::setValue(KexiBLOBBuffer::self()->objectForId(m_id).pixmap(), emitChange); +} + +void KexiImagePropertyEdit::drawViewer(QPainter *p, const QColorGroup &cg, const QRect &r, + const QVariant &value) +{ + KexiBLOBBuffer::Handle h( KexiBLOBBuffer::self()->objectForId(value.toInt()) ); + PixmapEdit::drawViewer(p, cg, r, h.pixmap()); +} + +//---------------------------------------------------------------- + +KexiIdentifierPropertyEdit::KexiIdentifierPropertyEdit( + Property *property, QWidget *parent, const char *name) + : StringEdit(property, parent, name) +{ + m_edit->setValidator( + new KexiUtils::IdentifierValidator(m_edit, "KexiIdentifierPropertyEdit Validator") ); +} + +KexiIdentifierPropertyEdit::~KexiIdentifierPropertyEdit() +{ +} + +void KexiIdentifierPropertyEdit::setValue(const QVariant &value, bool emitChange) +{ + QString string(value.toString()); + if (string.isEmpty()) { + kdWarning() << "KexiIdentifierPropertyEdit::setValue(): " + "Value cannot be empty. This call has no effect." << endl; + return; + } + QString identifier( KexiUtils::string2Identifier(string) ); + if (identifier!=string) + kdDebug() << QString("KexiIdentifierPropertyEdit::setValue(): " + "String \"%1\" converted to identifier \"%2\".").arg(string).arg(identifier) << endl; + StringEdit::setValue( identifier, emitChange ); +} + +#include "kexicustompropertyfactory_p.moc" diff --git a/kexi/widget/kexicustompropertyfactory_p.h b/kexi/widget/kexicustompropertyfactory_p.h new file mode 100644 index 00000000..f1ec4b0f --- /dev/null +++ b/kexi/widget/kexicustompropertyfactory_p.h @@ -0,0 +1,71 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl> + + 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 Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXICUSTOMPROPFACTORY_P_H +#define KEXICUSTOMPROPFACTORY_P_H + +#include <koproperty/editors/pixmapedit.h> +#include <koproperty/editors/stringedit.h> +#include <kexiblobbuffer.h> + +//! Kexi-specific image editor for property editor's item +class KexiImagePropertyEdit : public KoProperty::PixmapEdit +{ + Q_OBJECT + + public: + KexiImagePropertyEdit(KoProperty::Property *property, + QWidget *parent=0, const char *name=0); + virtual ~KexiImagePropertyEdit(); + + virtual QVariant value() const; + virtual void setValue(const QVariant &value, bool emitChange=true); + virtual void drawViewer(QPainter *p, const QColorGroup &cg, const QRect &r, + const QVariant &value); + + public slots: + virtual void selectPixmap(); + + protected: + KexiBLOBBuffer::Id_t m_id; +}; + +/*! Identifier editor based on ordinary string editor but always keeps a valid identifier + or empty value. It's line edit has IdentifierValidator::IdentifierValidator set, so user + is unable to enter invalid characters. Any chages to a null value or empty string, + have no effect. + + @todo move this to koproperty library (when KexiUtils is moves to kofficecore) + */ +class KexiIdentifierPropertyEdit : public KoProperty::StringEdit +{ + Q_OBJECT + + public: + KexiIdentifierPropertyEdit(KoProperty::Property *property, + QWidget *parent=0, const char *name=0); + virtual ~KexiIdentifierPropertyEdit(); + + /*! Reimplemented: sets \a value but it is converted to identifier + using KexiUtils::string2Identifier(). + If \a value is null or empty string, this method has no effect. */ + virtual void setValue(const QVariant &value, bool emitChange=true); +}; + +#endif diff --git a/kexi/widget/kexidataawareview.cpp b/kexi/widget/kexidataawareview.cpp new file mode 100644 index 00000000..418f0aa1 --- /dev/null +++ b/kexi/widget/kexidataawareview.cpp @@ -0,0 +1,383 @@ +/* This file is part of the KDE project + Copyright (C) 2005-2007 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "kexidataawareview.h" + +#include <kexidataawareobjectiface.h> +#include <utils/kexisharedactionclient.h> +#include <core/keximainwindow.h> + +#include <qlayout.h> + +#include <kpopupmenu.h> + +KexiDataAwareView::KexiDataAwareView(KexiMainWindow *mainWin, QWidget *parent, const char *name) + : KexiViewBase(mainWin, parent, name) + , KexiSearchAndReplaceViewInterface() + , m_internalView(0) + , m_actionClient(0) + , m_dataAwareObject(0) +{ +} + +void KexiDataAwareView::init( QWidget* viewWidget, KexiSharedActionClient* actionClient, + KexiDataAwareObjectInterface* dataAwareObject, bool noDataAware ) +{ + m_internalView = viewWidget; + m_actionClient = actionClient; + m_dataAwareObject = dataAwareObject; + setViewWidget(m_internalView, true); + + if (!noDataAware) { + m_dataAwareObject->connectCellSelectedSignal(this, SLOT(slotCellSelected(int,int))); + + //! before closing - we'are accepting editing + connect(this, SIGNAL(closing(bool&)), this, SLOT(slotClosing(bool&))); + + //! updating actions on start/stop editing + m_dataAwareObject->connectRowEditStartedSignal(this, SLOT(slotUpdateRowActions(int))); + m_dataAwareObject->connectRowEditTerminatedSignal(this, SLOT(slotUpdateRowActions(int))); + m_dataAwareObject->connectReloadActionsSignal(this, SLOT(reloadActions())); + } + + QVBoxLayout *box = new QVBoxLayout(this); + box->addWidget(m_internalView); + + setMinimumSize(m_internalView->minimumSizeHint().width(), + m_internalView->minimumSizeHint().height()); + resize( preferredSizeHint( m_internalView->sizeHint() ) ); + setFocusProxy(m_internalView); + + if (!noDataAware) { + initActions(); + reloadActions(); + } +} + +void KexiDataAwareView::initActions() +{ + plugSharedAction("edit_delete_row", this, SLOT(deleteCurrentRow())); + m_actionClient->plugSharedAction(sharedAction("edit_delete_row")); //for proper shortcut + + plugSharedAction("edit_delete", this, SLOT(deleteAndStartEditCurrentCell())); + m_actionClient->plugSharedAction(sharedAction("edit_delete")); //for proper shortcut + + plugSharedAction("edit_edititem", this, SLOT(startEditOrToggleValue())); + m_actionClient->plugSharedAction(sharedAction("edit_edititem")); //for proper shortcut + + plugSharedAction("data_save_row", this, SLOT(acceptRowEdit())); + m_actionClient->plugSharedAction(sharedAction("data_save_row")); //for proper shortcut + + plugSharedAction("data_cancel_row_changes", this, SLOT(cancelRowEdit())); + m_actionClient->plugSharedAction(sharedAction("data_cancel_row_changes")); //for proper shortcut + + if (m_dataAwareObject->isSortingEnabled()) { + plugSharedAction("data_sort_az", this, SLOT(sortAscending())); + plugSharedAction("data_sort_za", this, SLOT(sortDescending())); + } + + m_actionClient->plugSharedAction(sharedAction("edit_insert_empty_row")); //for proper shortcut + + setAvailable("data_sort_az", m_dataAwareObject->isSortingEnabled()); + setAvailable("data_sort_za", m_dataAwareObject->isSortingEnabled()); +//! \todo plugSharedAction("data_filter", this, SLOT(???())); + + plugSharedAction("data_go_to_first_record", this, SLOT(slotGoToFirstRow())); + plugSharedAction("data_go_to_previous_record", this, SLOT(slotGoToPreviusRow())); + plugSharedAction("data_go_to_next_record", this, SLOT(slotGoToNextRow())); + plugSharedAction("data_go_to_last_record", this, SLOT(slotGoToLastRow())); + plugSharedAction("data_go_to_new_record", this, SLOT(slotGoToNewRow())); + +//! \todo update availability + setAvailable("data_go_to_first_record", true); + setAvailable("data_go_to_previous_record", true); + setAvailable("data_go_to_next_record", true); + setAvailable("data_go_to_last_record", true); + setAvailable("data_go_to_new_record", true); + + plugSharedAction("edit_copy", this, SLOT(copySelection())); + m_actionClient->plugSharedAction(sharedAction("edit_copy")); //for proper shortcut + + plugSharedAction("edit_cut", this, SLOT(cutSelection())); + m_actionClient->plugSharedAction(sharedAction("edit_cut")); //for proper shortcut + + plugSharedAction("edit_paste", this, SLOT(paste())); + m_actionClient->plugSharedAction(sharedAction("edit_paste")); //for proper shortcut + +// plugSharedAction("edit_find", this, SLOT(editFind())); +// m_actionClient->plugSharedAction(sharedAction("edit_find")); //for proper shortcut + +// plugSharedAction("edit_findnext", this, SLOT(editFindNext())); +// m_actionClient->plugSharedAction(sharedAction("edit_findnext")); //for proper shortcut + +// plugSharedAction("edit_findprevious", this, SLOT(editFindPrevious())); +// m_actionClient->plugSharedAction(sharedAction("edit_findprev")); //for proper shortcut + +//! @todo plugSharedAction("edit_replace", this, SLOT(editReplace())); +//! @todo m_actionClient->plugSharedAction(sharedAction("edit_replace")); //for proper shortcut + +// setAvailable("edit_find", true); +// setAvailable("edit_findnext", true); +// setAvailable("edit_findprevious", true); +//! @todo setAvailable("edit_replace", true); +} + +void KexiDataAwareView::slotUpdateRowActions(int row) +{ + const bool ro = m_dataAwareObject->isReadOnly(); +// const bool inserting = m_dataAwareObject->isInsertingEnabled(); + const bool deleting = m_dataAwareObject->isDeleteEnabled(); + const bool emptyInserting = m_dataAwareObject->isEmptyRowInsertingEnabled(); + const bool editing = m_dataAwareObject->rowEditing(); + const bool sorting = m_dataAwareObject->isSortingEnabled(); + const int rows = m_dataAwareObject->rows(); + + setAvailable("edit_cut", !ro); + setAvailable("edit_paste", !ro); + setAvailable("edit_delete", !ro); // && !(inserting && row==rows)); + setAvailable("edit_delete_row", !ro && !(deleting && row==rows)); + setAvailable("edit_insert_empty_row", !ro && emptyInserting); + setAvailable("edit_clear_table", !ro && deleting && rows>0); + setAvailable("data_save_row", editing); + setAvailable("data_cancel_row_changes", editing); + setAvailable("data_sort_az", sorting); + setAvailable("data_sort_za", sorting); +} + +QWidget* KexiDataAwareView::mainWidget() +{ + return m_internalView; +} + +QSize KexiDataAwareView::minimumSizeHint() const +{ + return m_internalView ? m_internalView->minimumSizeHint() : QSize(0,0);//KexiViewBase::minimumSizeHint(); +} + +QSize KexiDataAwareView::sizeHint() const +{ + return m_internalView ? m_internalView->sizeHint() : QSize(0,0);//KexiViewBase::sizeHint(); +} + +void KexiDataAwareView::updateActions(bool activated) +{ + setAvailable("data_sort_az", m_dataAwareObject->isSortingEnabled()); + setAvailable("data_sort_za", m_dataAwareObject->isSortingEnabled()); + KexiViewBase::updateActions(activated); +} + +void KexiDataAwareView::reloadActions() +{ +// m_view->initActions(guiClient()->actionCollection()); +//warning FIXME Move this to the table part +/* + kdDebug()<<"INIT ACTIONS***********************************************************************"<<endl; + new KAction(i18n("Filter"), "filter", 0, this, SLOT(filter()), actionCollection(), "tablepart_filter"); + setXMLFile("kexidatatableui.rc"); +*/ + m_dataAwareObject->contextMenu()->clear(); + + plugSharedAction("edit_cut", m_dataAwareObject->contextMenu()); + plugSharedAction("edit_copy", m_dataAwareObject->contextMenu()); + plugSharedAction("edit_paste", m_dataAwareObject->contextMenu()); + + bool separatorNeeded = true; + + unplugSharedAction("edit_clear_table"); + plugSharedAction("edit_clear_table", this, SLOT(deleteAllRows())); + + if (m_dataAwareObject->isEmptyRowInsertingEnabled()) { + unplugSharedAction("edit_insert_empty_row"); + plugSharedAction("edit_insert_empty_row", m_internalView, SLOT(insertEmptyRow())); + if (separatorNeeded) + m_dataAwareObject->contextMenu()->insertSeparator(); + plugSharedAction("edit_insert_empty_row", m_dataAwareObject->contextMenu()); + } + else { + unplugSharedAction("edit_insert_empty_row"); + unplugSharedAction("edit_insert_empty_row", m_dataAwareObject->contextMenu()); + } + + if (m_dataAwareObject->isDeleteEnabled()) { + if (separatorNeeded) + m_dataAwareObject->contextMenu()->insertSeparator(); + plugSharedAction("edit_delete", m_dataAwareObject->contextMenu()); + plugSharedAction("edit_delete_row", m_dataAwareObject->contextMenu()); + } + else { + unplugSharedAction("edit_delete_row", m_dataAwareObject->contextMenu()); + unplugSharedAction("edit_delete_row", m_dataAwareObject->contextMenu()); + } + //if (!m_view->isSortingEnabled()) { +// unplugSharedAction("data_sort_az"); +// unplugSharedAction("data_sort_za"); + //} + setAvailable("data_sort_az", m_dataAwareObject->isSortingEnabled()); + setAvailable("data_sort_za", m_dataAwareObject->isSortingEnabled()); + + slotCellSelected( m_dataAwareObject->currentColumn(), m_dataAwareObject->currentRow() ); +} + +void KexiDataAwareView::slotCellSelected(int /*col*/, int row) +{ + slotUpdateRowActions(row); +} + +void KexiDataAwareView::deleteAllRows() +{ + m_dataAwareObject->deleteAllRows(true/*ask*/, true/*repaint*/); +} + +void KexiDataAwareView::deleteCurrentRow() +{ + m_dataAwareObject->deleteCurrentRow(); +} + +void KexiDataAwareView::deleteAndStartEditCurrentCell() +{ + m_dataAwareObject->deleteAndStartEditCurrentCell(); +} + +void KexiDataAwareView::startEditOrToggleValue() +{ + m_dataAwareObject->startEditOrToggleValue(); +} + +bool KexiDataAwareView::acceptRowEdit() +{ + return m_dataAwareObject->acceptRowEdit(); +} + +void KexiDataAwareView::slotClosing(bool& cancel) +{ + if (!acceptRowEdit()) + cancel = true; +} + +void KexiDataAwareView::cancelRowEdit() +{ + m_dataAwareObject->cancelRowEdit(); +} + +void KexiDataAwareView::sortAscending() +{ + m_dataAwareObject->sortAscending(); +} + +void KexiDataAwareView::sortDescending() +{ + m_dataAwareObject->sortDescending(); +} + +void KexiDataAwareView::copySelection() +{ + m_dataAwareObject->copySelection(); +} + +void KexiDataAwareView::cutSelection() +{ + m_dataAwareObject->cutSelection(); +} + +void KexiDataAwareView::paste() +{ + m_dataAwareObject->paste(); +} + +void KexiDataAwareView::slotGoToFirstRow() { m_dataAwareObject->selectFirstRow(); } +void KexiDataAwareView::slotGoToPreviusRow() { m_dataAwareObject->selectPrevRow(); } +void KexiDataAwareView::slotGoToNextRow() { m_dataAwareObject->selectNextRow(); } +void KexiDataAwareView::slotGoToLastRow() { m_dataAwareObject->selectLastRow(); } +void KexiDataAwareView::slotGoToNewRow() { m_dataAwareObject->addNewRecordRequested(); } + +bool KexiDataAwareView::setupFindAndReplace(QStringList& columnNames, QStringList& columnCaptions, + QString& currentColumnName) +{ + if (!dataAwareObject() || !dataAwareObject()->data()) + return false; + KexiTableViewColumn::List columns( dataAwareObject()->data()->columns ); + for (KexiTableViewColumn::ListIterator it(columns); it.current(); ++it) { + if (!it.current()->visible()) + continue; + columnNames.append( it.current()->field()->name() ); + columnCaptions.append( it.current()->captionAliasOrName() ); + } + + //update "look in" selection if there was any + const int currentColumnNumber = dataAwareObject()->currentColumn(); + if (currentColumnNumber!=-1) { + KexiTableViewColumn *col = columns.at( currentColumnNumber ); + if (col && col->field()) + currentColumnName = col->field()->name(); + } + return true; +} + +tristate KexiDataAwareView::find(const QVariant& valueToFind, + const KexiSearchAndReplaceViewInterface::Options& options, bool next) +{ + if (!dataAwareObject() || !dataAwareObject()->data()) + return cancelled; + +// const KexiDataAwareObjectInterface::FindAndReplaceOptions options(dlg->options()); +/* if (res == KexiFindDialog::Find) {*/ +// QVariant valueToFind(dlg->valueToFind()); + return dataAwareObject()->find( valueToFind, options, next ); +/* +//! @todo result... + + } + else if (res == KexiFindDialog::Replace) { +//! @todo + } + else if (res == KexiFindDialog::ReplaceAll) { +//! @todo + } + */ +} + +tristate KexiDataAwareView::findNextAndReplace(const QVariant& valueToFind, + const QVariant& replacement, + const KexiSearchAndReplaceViewInterface::Options& options, bool replaceAll) +{ + if (!dataAwareObject() || !dataAwareObject()->data()) + return cancelled; + + return dataAwareObject()->findNextAndReplace(valueToFind, replacement, options, replaceAll); +} + +/* +void KexiDataAwareView::editFindNext() +{ + //! @todo reuse code from editFind() +} + +void KexiDataAwareView::editFindPrevious() +{ + //! @todo reuse code from editFind() +} + +void KexiDataAwareView::editReplace() +{ + //! @todo editReplace() + //! @todo reuse code from editFind() + // When ready, update KexiDataAwareView::initActions() and KexiMainWindowImpl +}*/ + +#include "kexidataawareview.moc" diff --git a/kexi/widget/kexidataawareview.h b/kexi/widget/kexidataawareview.h new file mode 100644 index 00000000..8f1d369f --- /dev/null +++ b/kexi/widget/kexidataawareview.h @@ -0,0 +1,117 @@ +/* This file is part of the KDE project + Copyright (C) 2005-2007 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIDATAAWAREVIEW_H +#define KEXIDATAAWAREVIEW_H + +#include <kexiviewbase.h> +#include <kexisearchandreplaceiface.h> + +class KexiDataAwareObjectInterface; +class KexiSharedActionClient; + +/*! @short Provides a view displaying record-based data. + + The KexiDataAwareView is used to implement differently-looking views + for displaying record-based data in a consistent way: + - tabular data views + - form data view + + Action implementations like data editing and deleting are shared for different + view types to keep even better consistency. + The view also implements KexiSearchAndReplaceViewInterface to support search/replace features + used by shared KexiFindDialog. +*/ +class KEXIEXTWIDGETS_EXPORT KexiDataAwareView : public KexiViewBase, + public KexiSearchAndReplaceViewInterface +{ + Q_OBJECT + + public: + KexiDataAwareView(KexiMainWindow *mainWin, QWidget *parent, const char *name = 0); + + QWidget* mainWidget(); + + virtual QSize minimumSizeHint() const; + + virtual QSize sizeHint() const; + + KexiDataAwareObjectInterface* dataAwareObject() const { return m_dataAwareObject; } + + /*! Sets up data for find/replace dialog, based on view's data model. + Implemented for KexiSearchAndReplaceViewInterface. */ + virtual bool setupFindAndReplace(QStringList& columnNames, QStringList& columnCaptions, + QString& currentColumnName); + + /*! Finds \a valueToFind within the view. + Implemented for KexiSearchAndReplaceViewInterface. */ + virtual tristate find(const QVariant& valueToFind, + const KexiSearchAndReplaceViewInterface::Options& options, bool next); + + /*! Finds \a valueToFind within the view and replaces with \a replacement. + Implemented for KexiSearchAndReplaceViewInterface. */ + virtual tristate findNextAndReplace(const QVariant& valueToFind, + const QVariant& replacement, + const KexiSearchAndReplaceViewInterface::Options& options, bool replaceAll); + + public slots: + void deleteAllRows(); + void deleteCurrentRow(); + void deleteAndStartEditCurrentCell(); + void startEditOrToggleValue(); + bool acceptRowEdit(); + void cancelRowEdit(); + void sortAscending(); + void sortDescending(); + void copySelection(); + void cutSelection(); + void paste(); + void slotGoToFirstRow(); + void slotGoToPreviusRow(); + void slotGoToNextRow(); + void slotGoToLastRow(); + void slotGoToNewRow(); +/* void editFind(); + void slotFind(); + void editFindNext(); + void editFindPrevious(); + void editReplace();*/ + + protected slots: +// void slotCellSelected(const QVariant& v); //!< @internal + void slotCellSelected(int col, int row); + void reloadActions(); + void slotUpdateRowActions(int row); + void slotClosing(bool& cancel); + + protected: + void init( QWidget* viewWidget, KexiSharedActionClient* actionClient, + KexiDataAwareObjectInterface* dataAwareObject, + // temporary, for KexiFormView in design mode + bool noDataAware = false + ); + void initActions(); + virtual void updateActions(bool activated); + + QWidget* m_internalView; + KexiSharedActionClient* m_actionClient; + KexiDataAwareObjectInterface* m_dataAwareObject; +}; + +#endif diff --git a/kexi/widget/kexidatasourcecombobox.cpp b/kexi/widget/kexidatasourcecombobox.cpp new file mode 100644 index 00000000..77dc771f --- /dev/null +++ b/kexi/widget/kexidatasourcecombobox.cpp @@ -0,0 +1,333 @@ +/* This file is part of the KDE project + Copyright (C) 2005-2006 Jaroslaw Staniek <js@iidea.pl> + + 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 Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "kexidatasourcecombobox.h" + +#include <kdebug.h> +#include <kiconloader.h> +#include <klistbox.h> + +#include <kexi.h> +#include <kexiproject.h> +#include <keximainwindow.h> +#include <kexipart.h> +#include <kexipartmanager.h> +#include <kexipartinfo.h> +#include <kexipartitem.h> + +#include <kexidb/connection.h> + +#ifdef KEXI_SHOW_UNIMPLEMENTED +#define ADD_DEFINEQUERY_ROW +#endif + +//! @internal +class KexiDataSourceComboBox::Private +{ + public: + Private() + : tablesCount(0) + , prevIndex(-1) + , showTables(true) + , showQueries(true) + { + } + int firstTableIndex() const { + int index = 1; //skip empty row +#ifdef ADD_DEFINEQUERY_ROW + index++; /*skip 'define query' row*/ +#endif + return index; + } + int firstQueryIndex() const { + return firstTableIndex() + tablesCount; + } + + QGuardedPtr<KexiProject> prj; + QPixmap tableIcon, queryIcon; + int tablesCount; + int prevIndex; //!< Used in slotActivated() + bool showTables : 1; + bool showQueries : 1; +}; + +//------------------------ + +KexiDataSourceComboBox::KexiDataSourceComboBox(QWidget *parent, const char *name) + : KComboBox(true/*rw*/, parent, name) + , d(new Private()) +{ + setInsertionPolicy(NoInsertion); + setCompletionMode(KGlobalSettings::CompletionPopupAuto); + setSizeLimit( 16 ); + connect(this, SIGNAL(activated(int)), this, SLOT(slotActivated(int))); + connect(this, SIGNAL(returnPressed(const QString &)), this, SLOT(slotReturnPressed(const QString &))); + + d->tableIcon = SmallIcon("table"); + d->queryIcon = SmallIcon("query"); +} + +KexiDataSourceComboBox::~KexiDataSourceComboBox() +{ + delete d; +} + +KexiProject* KexiDataSourceComboBox::project() const +{ + return d->prj; +} + +void KexiDataSourceComboBox::setProject(KexiProject *prj, bool showTables, bool showQueries) +{ + if ((KexiProject*)d->prj == prj) + return; + + if (d->prj) { + disconnect(d->prj, 0, this, 0); + } + d->prj = prj; + d->showTables = showTables; + d->showQueries = showQueries; + clear(); + d->tablesCount = 0; + if (!d->prj) + return; + + //needed for updating contents of the combo box + connect(d->prj, SIGNAL(newItemStored(KexiPart::Item&)), + this, SLOT(slotNewItemStored(KexiPart::Item&))); + connect(d->prj, SIGNAL(itemRemoved(const KexiPart::Item&)), + this, SLOT(slotItemRemoved(const KexiPart::Item&))); + connect(d->prj, SIGNAL(itemRenamed(const KexiPart::Item&, const QCString&)), + this, SLOT(slotItemRenamed(const KexiPart::Item&, const QCString&))); + + KexiDB::Connection *conn = d->prj->dbConnection(); + if (!conn) + return; + + //special item: empty + insertItem(""); +#ifdef ADD_DEFINEQUERY_ROW + //special item: define query + insertItem(i18n("Define Query...")); +#endif + + KCompletion *comp = completionObject(); + + if (d->showTables) { + //tables + KexiPart::Info* partInfo = Kexi::partManager().infoForMimeType("kexi/table"); + if (!partInfo) + return; + KexiPart::ItemList list; + prj->getSortedItems(list, partInfo); + list.sort(); + d->tablesCount = 0; + for (KexiPart::ItemListIterator it(list); it.current(); ++it, d->tablesCount++) { + insertItem(d->tableIcon, it.current()->name()); //or caption()? + comp->addItem(it.current()->name()); + } + } + + if (d->showQueries) { + //queries + KexiPart::Info* partInfo = Kexi::partManager().infoForMimeType("kexi/query"); + if (!partInfo) + return; + KexiPart::ItemList list; + prj->getSortedItems(list, partInfo); + list.sort(); + for (KexiPart::ItemListIterator it(list); it.current(); ++it) { + insertItem(d->queryIcon, it.current()->name()); //or caption()? + comp->addItem(it.current()->name()); + } + } +// setCurrentText(""); + setCurrentItem(0); +} + +void KexiDataSourceComboBox::setDataSource(const QString& mimeType, const QString& name) +{ + if (name.isEmpty()) { + clearEdit(); + setCurrentItem(0); + d->prevIndex = -1; + emit dataSourceChanged(); + return; + } + + QString mt(mimeType); + if (mimeType.isEmpty()) + mt="kexi/table"; + int i = findItem(mt, name); + if (i==-1) { + if (mimeType.isEmpty()) + i = findItem("kexi/query", name); + if (i==-1) { + setCurrentItem(0); + return; + } + } + setCurrentItem(i); + slotActivated(i); +} + +void KexiDataSourceComboBox::slotNewItemStored(KexiPart::Item& item) +{ + QString name(item.name()); + //insert a new item, maintaining sort order and splitting to tables and queries + if (item.mimeType()=="kexi/table") { + int i = 1; /*skip empty row*/ +#ifdef ADD_DEFINEQUERY_ROW + i++; /*skip 'define query' row*/ +#endif + for (; i < d->firstQueryIndex() && name>=text(i); i++) + ; + insertItem(d->tableIcon, name, i); + completionObject()->addItem(name); + d->tablesCount++; + } + else if (item.mimeType()=="kexi/query") { + int i; + for (i=d->firstQueryIndex(); i<count() && name>=text(i); i++) + ; + insertItem(d->queryIcon, name, i); + completionObject()->addItem(name); + } +} + +int KexiDataSourceComboBox::findItem(const QString& mimeType, const QString& name) +{ + int i, end; + if (mimeType=="kexi/table") { + i = 0; +#ifdef ADD_DEFINEQUERY_ROW + i++; //skip 'define query' +#endif + end = d->firstQueryIndex(); + } + else if (mimeType=="kexi/query") { + i = d->firstQueryIndex(); + end = count(); + } + else + return -1; + + QString nameString(name); + + for (; i<end; i++) + if (text(i)==nameString) + return i; + + return -1; +} + +void KexiDataSourceComboBox::slotItemRemoved(const KexiPart::Item& item) +{ + const int i = findItem(item.mimeType(), item.name()); + if (i==-1) + return; + removeItem(i); + completionObject()->removeItem(item.name()); + if (item.mimeType()=="kexi/table") + d->tablesCount--; +#if 0 //disabled because even invalid data source can be set + if (currentItem()==i) { + if (i==(count()-1)) + setCurrentItem(i-1); + else + setCurrentItem(i); + } +#endif +} + +void KexiDataSourceComboBox::slotItemRenamed(const KexiPart::Item& item, const QCString& oldName) +{ + const int i = findItem(item.mimeType(), QString(oldName)); + if (i==-1) + return; + changeItem(item.name(), i); + completionObject()->removeItem(QString(oldName)); + completionObject()->addItem(item.name()); + setCurrentText(oldName); //still keep old name +} + +void KexiDataSourceComboBox::slotActivated( int index ) +{ + if (index >= d->firstTableIndex() && index < count() && d->prevIndex!=currentItem()) { + d->prevIndex = currentItem(); + emit dataSourceChanged(); + } +} + +QString KexiDataSourceComboBox::selectedMimeType() const +{ + if (selectedName().isEmpty()) + return ""; + const int index = currentItem(); + if (index >= d->firstTableIndex() && index < (int)d->firstQueryIndex()) + return "kexi/table"; + else if (index >= (int)d->firstQueryIndex() && index < count()) + return "kexi/query"; + return ""; +} + +QString KexiDataSourceComboBox::selectedName() const +{ + if (isSelectionValid()) + return text(currentItem()); + return currentText(); +} + +bool KexiDataSourceComboBox::isSelectionValid() const +{ + const int index = currentItem(); + return index >= d->firstTableIndex() && index < count() && text(index)==currentText(); +} + +void KexiDataSourceComboBox::slotReturnPressed(const QString & text) +{ + //text is available: select item for this text: + bool changed = false; + if (text.isEmpty() && 0!=currentItem()) { + setCurrentItem(0); + changed = true; + } + else { + QListBoxItem *item = listBox()->findItem( text, Qt::ExactMatch ); + if (item) { + int index = listBox()->index( item ); + //if (index < d->firstTableIndex()) + if (index>=0 && index!=currentItem()) { + setCurrentItem( index ); + changed = true; + } + } + } + if (changed) + emit dataSourceChanged(); +} + +void KexiDataSourceComboBox::focusOutEvent( QFocusEvent *e ) +{ + KComboBox::focusOutEvent( e ); + slotReturnPressed(currentText()); +} + +#include "kexidatasourcecombobox.moc" diff --git a/kexi/widget/kexidatasourcecombobox.h b/kexi/widget/kexidatasourcecombobox.h new file mode 100644 index 00000000..01a02d03 --- /dev/null +++ b/kexi/widget/kexidatasourcecombobox.h @@ -0,0 +1,88 @@ +/* This file is part of the KDE project + Copyright (C) 2005-2006 Jaroslaw Staniek <js@iidea.pl> + + 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 Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIDATASOURCECOMBOBOX_H +#define KEXIDATASOURCECOMBOBOX_H + +#include <kcombobox.h> + +class KexiProject; +namespace KexiPart { + class Item; +} + +/** + * A combo box listing availabe data sources (tables and queries) + * with icons. "Define query..." item can be also prepended. + */ +class KEXIEXTWIDGETS_EXPORT KexiDataSourceComboBox : public KComboBox +{ + Q_OBJECT + + public: + KexiDataSourceComboBox(QWidget *parent, const char *name=0); + ~KexiDataSourceComboBox(); + + //! \return global project that is used to retrieve schema informationm for this combo box. + KexiProject* project() const; + + //! \return name of selected table or query. Can return null string. + //! You should use isSelectionValid() to check validity of the input. + QString selectedMimeType() const; + + //! \return name of selected table or query. Can return null string or nonexisting name, + //! so you should use isSelectionValid() to check validity of the input. + QString selectedName() const; + + //! \return true if current selection is valid + bool isSelectionValid() const; + + /*! \return index of item of mime type \a mimeType and name \a name. + Returs -1 of no such item exists. */ + int findItem(const QString& mimeType, const QString& name); + + public slots: + //! Sets global project that is used to retrieve schema informationm for this combo box. + //! Tables visibility can be set using \a showTables queries visibility using \a showQueries. + void setProject(KexiProject *prj, bool showTables = true, bool showQueries = true); + + /*! Sets item for data source described by \a mimeType and \a name. + If \a mimeType is empty, either "kexi/table" and "kexi/query" are tried. */ + void setDataSource(const QString& mimeType, const QString& name); + + signals: + //! Emitted whenever data source changes. + //! Even setting invalid data source or clearing it will emit this signal. + void dataSourceChanged(); + + protected slots: + void slotNewItemStored(KexiPart::Item& item); + void slotItemRemoved(const KexiPart::Item& item); + void slotItemRenamed(const KexiPart::Item& item, const QCString& oldName); + void slotActivated( int index ); + void slotReturnPressed(const QString & text); + + protected: + virtual void focusOutEvent( QFocusEvent *e ); + + class Private; + Private *d; +}; + +#endif diff --git a/kexi/widget/kexidatatable.cpp b/kexi/widget/kexidatatable.cpp new file mode 100644 index 00000000..b9a0aeb2 --- /dev/null +++ b/kexi/widget/kexidatatable.cpp @@ -0,0 +1,82 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Lucijan Busch <lucijan@kde.org> + Copyright (C) 2003 Joseph Wenninger <jowenn@kde.org> + Copyright (C) 2003-2006 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + + +#include <qlayout.h> +#include <qlabel.h> + +#include <kiconloader.h> +#include <klocale.h> +#include <kaction.h> +#include <kpopupmenu.h> + +#include <kexidb/cursor.h> + +#include "kexidatatableview.h" +#include "kexidatatable.h" +#include "kexidialogbase.h" + +KexiDataTable::KexiDataTable(KexiMainWindow *mainWin, QWidget *parent, + const char *name, bool dbAware) +// : KexiViewBase(mainWin, parent, name) + : KexiDataAwareView( mainWin, parent, name ) +{ + KexiTableView *view; + if (dbAware) + view = new KexiDataTableView(this, + QString("%1_datatableview").arg(name ? name : "KexiDataTableView").latin1()); + else + view = new KexiTableView(0, this, + QString("%1_tableview").arg(name ? name : "KexiTableView").latin1()); + + KexiDataAwareView::init( view, view, view ); +} + +KexiDataTable::KexiDataTable(KexiMainWindow *mainWin, QWidget *parent, + KexiDB::Cursor *cursor, const char *name) + : KexiDataAwareView( mainWin, parent, name ) +{ + KexiTableView *view = new KexiDataTableView(this, "view", cursor); + KexiDataAwareView::init( view, view, view ); +} + +KexiDataTable::~KexiDataTable() +{ +} + +void +KexiDataTable::setData(KexiDB::Cursor *c) +{ + if (!dynamic_cast<KexiDataTableView*>(mainWidget())) + return; + dynamic_cast<KexiDataTableView*>(mainWidget())->setData(c); +} + +void KexiDataTable::filter() +{ +} + +KexiTableView* KexiDataTable::tableView() const +{ + return dynamic_cast<KexiTableView*>(m_internalView); +} + +#include "kexidatatable.moc" diff --git a/kexi/widget/kexidatatable.h b/kexi/widget/kexidatatable.h new file mode 100644 index 00000000..9fb73225 --- /dev/null +++ b/kexi/widget/kexidatatable.h @@ -0,0 +1,80 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Lucijan Busch <lucijan@kde.org> + Copyright (C) 2003 Joseph Wenninger <jowenn@kde.org> + Copyright (C) 2003-2006 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIDATATABLE_H +#define KEXIDATATABLE_H + +#include "kexidataawareview.h" + +class KexiMainWindow; +class KexiDataTableView; +class KexiTableView; +class KexiTableViewData; +class KPopupMenu; + +namespace KexiDB +{ + class Cursor; +} + +/*! @short Provides a data-driven (record-based) tabular view. + + The KexiDataTable can display data provided "by hand" + or from KexiDB-compatible database source. + @see KexiFormView +*/ +class KEXIEXTWIDGETS_EXPORT KexiDataTable : public KexiDataAwareView +{ + Q_OBJECT + + public: + /*! CTOR1: Creates, empty table view that can be initialized later + with setData(). + If \a dbAware is true, table will be db-aware, + and KexiDataTableView is used internally. + Otherwise, table will be not-db-aware, + and KexiTableView is used internally. In the latter case, + data can be set by calling tableView()->setData(KexiTableViewData* data). */ + KexiDataTable(KexiMainWindow *mainWin, QWidget *parent, const char *name = 0, + bool dbAware = true); + + /*! CTOR2: Creates db-aware, table view initialized with \a cursor. + KexiDataTableView is used internally. */ + KexiDataTable(KexiMainWindow *mainWin, QWidget *parent, + KexiDB::Cursor *cursor, const char *name = 0); + + virtual ~KexiDataTable(); + + KexiTableView* tableView() const; + + public slots: + /*! Sets data. Only works for db-aware table. */ + void setData(KexiDB::Cursor *cursor); + + protected slots: +//! @todo + void filter(); + + protected: + void init(); +}; + +#endif diff --git a/kexi/widget/kexidbconnectionwidget.cpp b/kexi/widget/kexidbconnectionwidget.cpp new file mode 100644 index 00000000..5b1b4538 --- /dev/null +++ b/kexi/widget/kexidbconnectionwidget.cpp @@ -0,0 +1,407 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "kexidbconnectionwidget.h" +#include "kexidbconnectionwidgetdetailsbase.h" + +#include <kexi.h> +#include <kexiguimsghandler.h> +#include <kexidb/connection.h> +#include <kexidb/utils.h> +#include "kexidbdrivercombobox.h" + +#include <kdebug.h> +#include <kiconloader.h> +#include <klineedit.h> +#include <knuminput.h> +#include <kpassdlg.h> +#include <kurlrequester.h> +#include <ktextedit.h> +#include <kprogress.h> + +#include <qlabel.h> +#include <qcheckbox.h> +#include <qbuttongroup.h> +#include <qwidgetstack.h> +#include <qlayout.h> +#include <qvbox.h> +#include <qtooltip.h> +#include <qwhatsthis.h> +#include <qthread.h> +#include <qradiobutton.h> + +//! Templorary hides db list +//! @todo reenable this when implemented +#define NO_LOAD_DB_LIST + +// @internal +class KexiDBConnectionWidget::Private +{ + public: + Private() + : connectionOnly(false) + { + } + + KPushButton *btnSaveChanges, *btnTestConnection; + bool connectionOnly : 1; +}; + +//--------- + +KexiDBConnectionWidget::KexiDBConnectionWidget( QWidget* parent, const char* name ) + : KexiDBConnectionWidgetBase( parent, name ) + , d(new Private()) +{ + iconLabel->setPixmap(DesktopIcon("network")); + + QVBoxLayout *driversComboLyr = new QVBoxLayout(frmEngine); + m_driversCombo = new KexiDBDriverComboBox(frmEngine, Kexi::driverManager().driversInfo(), + KexiDBDriverComboBox::ShowServerDrivers); + lblEngine->setBuddy( m_driversCombo ); + lblEngine->setFocusProxy( m_driversCombo ); + driversComboLyr->addWidget( m_driversCombo ); + +#ifdef NO_LOAD_DB_LIST + btnLoadDBList->hide(); +#endif + btnLoadDBList->setIconSet(SmallIconSet("reload")); + QToolTip::add(btnLoadDBList, i18n("Load database list from the server")); + QWhatsThis::add(btnLoadDBList, + i18n("Loads database list from the server, so you can select one using the \"Name\" combo box.")); + + QHBoxLayout *hbox = new QHBoxLayout(frmBottom); + hbox->addStretch(2); + d->btnSaveChanges = new KPushButton(KGuiItem(i18n("Save Changes"), "filesave", + i18n("Save all changes made to this connection information"), + i18n("Save all changes made to this connection information. You can later reuse this information.")), + frmBottom, "savechanges"); + hbox->addWidget( d->btnSaveChanges ); + hbox->addSpacing( KDialogBase::spacingHint() ); + QWidget::setTabOrder(titleEdit, d->btnSaveChanges); + d->btnSaveChanges->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ); + + d->btnTestConnection = new KPushButton(KGuiItem(i18n("&Test Connection"), "", + i18n("Test database connection"), + i18n("Tests database connection. You can ensure that valid connection information is provided.")), + frmBottom, "testConnection"); + hbox->addWidget( d->btnTestConnection ); + QWidget::setTabOrder(d->btnSaveChanges, d->btnTestConnection); + d->btnTestConnection->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ); + + connect( locationBGrp, SIGNAL(clicked(int)), this, SLOT(slotLocationBGrpClicked(int)) ); + connect( chkPortDefault, SIGNAL(toggled(bool)), this , SLOT(slotCBToggled(bool)) ); + connect( btnLoadDBList, SIGNAL(clicked()), this, SIGNAL(loadDBList()) ); + connect( d->btnSaveChanges, SIGNAL(clicked()), this, SIGNAL(saveChanges()) ); +} + +KexiDBConnectionWidget::~KexiDBConnectionWidget() +{ + delete d; +} + +bool KexiDBConnectionWidget::connectionOnly() const +{ return d->connectionOnly; } + +void KexiDBConnectionWidget::setDataInternal(const KexiProjectData& data, bool connectionOnly, + const QString& shortcutFileName) +{ + m_data = data; + d->connectionOnly = connectionOnly; + + if (d->connectionOnly) { + nameLabel->hide(); + nameCombo->hide(); + btnLoadDBList->hide(); + dbGroupBox->setTitle(i18n("Database Connection")); + } + else { + nameLabel->show(); + nameCombo->show(); +#ifndef NO_LOAD_DB_LIST + btnLoadDBList->show(); +#endif + nameCombo->setCurrentText(m_data.databaseName()); + dbGroupBox->setTitle(i18n("Database")); + } +//! @todo what if there's no such driver name? + m_driversCombo->setDriverName(m_data.connectionData()->driverName); + hostEdit->setText(m_data.connectionData()->hostName); + locationBGrp->setButton( m_data.connectionData()->hostName.isEmpty() ? 0 : 1 ); + slotLocationBGrpClicked( locationBGrp->selectedId() ); + if (m_data.connectionData()->port!=0) { + chkPortDefault->setChecked(false); + customPortEdit->setValue(m_data.connectionData()->port); + } + else { + chkPortDefault->setChecked(true); +/* @todo default port # instead of 0 */ + customPortEdit->setValue(0); + } + userEdit->setText(m_data.connectionData()->userName); + passwordEdit->setText(m_data.connectionData()->password); + if (d->connectionOnly) + titleEdit->setText(m_data.connectionData()->caption); + else + titleEdit->setText(m_data.caption()); + + if (shortcutFileName.isEmpty()) { + d->btnSaveChanges->hide(); +// chkSavePassword->hide(); + } + else { + if (!QFileInfo(shortcutFileName).isWritable()) { + d->btnSaveChanges->setEnabled(false); + } + } +// chkSavePassword->setChecked(!m_data.connectionData()->password.isEmpty()); + chkSavePassword->setChecked(m_data.connectionData()->savePassword); + adjustSize(); +} + +void KexiDBConnectionWidget::setData(const KexiProjectData& data, const QString& shortcutFileName) +{ + setDataInternal(data, false /*!connectionOnly*/, shortcutFileName); +} + +void KexiDBConnectionWidget::setData(const KexiDB::ConnectionData& data, const QString& shortcutFileName) +{ + KexiProjectData pdata(data); + setDataInternal(pdata, true /*connectionOnly*/, shortcutFileName); +} + +KPushButton* KexiDBConnectionWidget::saveChangesButton() const +{ + return d->btnSaveChanges; +} + +KPushButton* KexiDBConnectionWidget::testConnectionButton() const +{ + return d->btnTestConnection; +} + +KexiProjectData KexiDBConnectionWidget::data() +{ + return m_data; +} + +void KexiDBConnectionWidget::slotLocationBGrpClicked(int id) +{ + if (id != 0 && id != 1) //only support local/remove radio buttons + return; + hostLbl->setEnabled(id==1); + hostEdit->setEnabled(id==1); +} + +void KexiDBConnectionWidget::slotCBToggled(bool on) +{ + if (sender()==chkPortDefault) { + customPortEdit->setEnabled(!on); + } +// else if (sender()==chkSocketDefault) { +// customSocketEdit->setEnabled(!on); +// } +} + +//----------- + +KexiDBConnectionTabWidget::KexiDBConnectionTabWidget( QWidget* parent, const char* name ) + : KTabWidget( parent, name ) +{ + mainWidget = new KexiDBConnectionWidget( this, "mainWidget" ); + mainWidget->layout()->setMargin(KDialog::marginHint()); + addTab( mainWidget, i18n("Parameters") ); + +// QVBox *page2 = new QVBox(this); +// page2->setMargin(KDialog::marginHint()); +// page2->setSpacing(KDialog::spacingHint()); +// QLabel *lbl = new QLabel(i18n("&Description:"), page2); +// m_descriptionEdit = new KTextEdit(page2); +// lbl->setBuddy(m_descriptionEdit); + detailsWidget = new KexiDBConnectionWidgetDetailsBase(this, "detailsWidget"); + addTab( detailsWidget, i18n("Details") ); + + connect( mainWidget->testConnectionButton(), SIGNAL(clicked()), this, SLOT(slotTestConnection()) ); +} + +KexiDBConnectionTabWidget::~KexiDBConnectionTabWidget() +{ +} + +void KexiDBConnectionTabWidget::setData(const KexiProjectData& data, const QString& shortcutFileName) +{ + mainWidget->setData( data, shortcutFileName ); + detailsWidget->chkUseSocket->setChecked( data.constConnectionData()->useLocalSocketFile ); + detailsWidget->customSocketEdit->setURL( data.constConnectionData()->localSocketFileName ); + detailsWidget->customSocketEdit->setEnabled( detailsWidget->chkUseSocket->isChecked() ); + detailsWidget->chkSocketDefault->setChecked( data.constConnectionData()->localSocketFileName.isEmpty() ); + detailsWidget->chkSocketDefault->setEnabled( detailsWidget->chkUseSocket->isChecked() ); + detailsWidget->descriptionEdit->setText( data.description() ); +} + +void KexiDBConnectionTabWidget::setData(const KexiDB::ConnectionData& data, + const QString& shortcutFileName) +{ + mainWidget->setData( data, shortcutFileName ); + detailsWidget->chkUseSocket->setChecked( data.useLocalSocketFile ); + detailsWidget->customSocketEdit->setURL( data.localSocketFileName ); + detailsWidget->customSocketEdit->setEnabled( detailsWidget->chkUseSocket->isChecked() ); + detailsWidget->chkSocketDefault->setChecked( data.localSocketFileName.isEmpty() ); + detailsWidget->chkSocketDefault->setEnabled( detailsWidget->chkUseSocket->isChecked() ); + detailsWidget->descriptionEdit->setText( data.description ); +} + +KexiProjectData KexiDBConnectionTabWidget::currentProjectData() +{ + KexiProjectData data; + +//! @todo check if that's database of connection shortcut. Now we're assuming db shortcut only! + + // collect data from the form's fields +// if (d->isDatabaseShortcut) { + if (mainWidget->connectionOnly()) { + data.connectionData()->caption = mainWidget->titleEdit->text(); + data.setCaption( QString::null ); + data.connectionData()->description = detailsWidget->descriptionEdit->text(); + data.setDatabaseName( QString::null ); + } + else { + data.connectionData()->caption = QString::null; /* connection name is not specified... */ + data.setCaption( mainWidget->titleEdit->text() ); + data.setDescription( detailsWidget->descriptionEdit->text() ); + data.setDatabaseName( mainWidget->nameCombo->currentText() ); + } +// } +/* else { + data.setCaption( QString::null ); + data.connectionData()->connName = config.readEntry("caption"); + data.setDescription( QString::null ); + data.connectionData()->description = config.readEntry("comment"); + data.setDatabaseName( QString::null ); + }*/ + data.connectionData()->driverName = mainWidget->driversCombo()->selectedDriverName(); + +/* if (data.connectionData()->driverName.isEmpty()) { + //ERR: "No valid "engine" field specified for %1 section" group + return false; + }*/ + data.connectionData()->hostName = + (mainWidget->remotehostRBtn->isChecked()/*remote*/) ? mainWidget->hostEdit->text() + : QString::null; + data.connectionData()->port = mainWidget->chkPortDefault->isChecked() + ? 0 : mainWidget->customPortEdit->value(); + data.connectionData()->localSocketFileName = detailsWidget->chkSocketDefault->isChecked() + ? QString::null : detailsWidget->customSocketEdit->url(); + data.connectionData()->useLocalSocketFile = detailsWidget->chkUseSocket->isChecked(); +//UNSAFE!!!! + data.connectionData()->userName = mainWidget->userEdit->text(); + data.connectionData()->password = mainWidget->passwordEdit->text(); + data.connectionData()->savePassword = mainWidget->chkSavePassword->isChecked(); +/* @todo add "options=", eg. as string list? */ + return data; +} + +bool KexiDBConnectionTabWidget::savePasswordOptionSelected() const +{ + return mainWidget->chkSavePassword->isChecked(); +} + + + + +void KexiDBConnectionTabWidget::slotTestConnection() +{ + KexiGUIMessageHandler msgHandler; + KexiDB::connectionTestDialog(this, *currentProjectData().connectionData(), + msgHandler); +} + +//-------- + +//! @todo set proper help ctxt ID + +KexiDBConnectionDialog::KexiDBConnectionDialog(const KexiProjectData& data, + const QString& shortcutFileName, const KGuiItem& acceptButtonGuiItem) + : KDialogBase(0, "dlg", true, i18n("Open Database"), + KDialogBase::User1|KDialogBase::Cancel|KDialogBase::Help, + KDialogBase::User1, false, + acceptButtonGuiItem.text().isEmpty() + ? KGuiItem(i18n("&Open"), "fileopen", i18n("Open Database Connection")) + : acceptButtonGuiItem + ) +{ + tabWidget = new KexiDBConnectionTabWidget(this, "tabWidget"); + tabWidget->setData(data, shortcutFileName); + init(); +} + +KexiDBConnectionDialog::KexiDBConnectionDialog(const KexiDB::ConnectionData& data, + const QString& shortcutFileName, const KGuiItem& acceptButtonGuiItem) + : KDialogBase(0, "dlg", true, i18n("Connect to a Database Server"), + KDialogBase::User1|KDialogBase::Cancel|KDialogBase::Help, + KDialogBase::User1, false, + acceptButtonGuiItem.text().isEmpty() + ? KGuiItem(i18n("&Open"), "fileopen", i18n("Open Database Connection")) + : acceptButtonGuiItem + ) +{ + tabWidget = new KexiDBConnectionTabWidget(this, "tabWidget"); + tabWidget->setData(data, shortcutFileName); + init(); +} + +KexiDBConnectionDialog::~KexiDBConnectionDialog() +{ +} + +void KexiDBConnectionDialog::init() +{ + connect( this, SIGNAL(user1Clicked()), this, SLOT(accept())); + setMainWidget(tabWidget); + connect(tabWidget->mainWidget, SIGNAL(saveChanges()), this, SIGNAL(saveChanges())); + connect(tabWidget, SIGNAL(testConnection()), this, SIGNAL(testConnection())); + + adjustSize(); + resize(width(), tabWidget->height()); + if (tabWidget->mainWidget->connectionOnly()) + tabWidget->mainWidget->driversCombo()->setFocus(); + else if (tabWidget->mainWidget->nameCombo->currentText().isEmpty()) + tabWidget->mainWidget->nameCombo->setFocus(); + else if (tabWidget->mainWidget->userEdit->text().isEmpty()) + tabWidget->mainWidget->userEdit->setFocus(); + else if (tabWidget->mainWidget->passwordEdit->text().isEmpty()) + tabWidget->mainWidget->passwordEdit->setFocus(); + else //back + tabWidget->mainWidget->nameCombo->setFocus(); +} + +KexiProjectData KexiDBConnectionDialog::currentProjectData() +{ return tabWidget->currentProjectData(); } + +bool KexiDBConnectionDialog::savePasswordOptionSelected() const +{ return tabWidget->savePasswordOptionSelected(); } + +KexiDBConnectionWidget* KexiDBConnectionDialog::mainWidget() const +{ return tabWidget->mainWidget; } + +KexiDBConnectionWidgetDetailsBase* KexiDBConnectionDialog::detailsWidget() const +{ return tabWidget->detailsWidget; } + +#include "kexidbconnectionwidget.moc" + diff --git a/kexi/widget/kexidbconnectionwidget.h b/kexi/widget/kexidbconnectionwidget.h new file mode 100644 index 00000000..dd8559e5 --- /dev/null +++ b/kexi/widget/kexidbconnectionwidget.h @@ -0,0 +1,179 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef KEXIDBCONNECTIONWIDGET_H +#define KEXIDBCONNECTIONWIDGET_H + +#include "kexidbconnectionwidgetbase.h" + +#include <kexiprojectdata.h> + +#include <ktabwidget.h> +#include <kdialogbase.h> + +class KTextEdit; +class KPushButton; +class KexiDBDriverComboBox; +class KexiDBConnectionWidgetDetailsBase; +class KexiDBConnectionTabWidget; + +class KEXIEXTWIDGETS_EXPORT KexiDBConnectionWidget : public KexiDBConnectionWidgetBase +{ + Q_OBJECT + + public: + KexiDBConnectionWidget( QWidget* parent = 0, const char* name = 0 ); + virtual ~KexiDBConnectionWidget(); + + /*! Sets project data \a data. + \a shortcutFileName is only used to check if the file is writable + (if no, "save changes" button will be disabled). */ + void setData(const KexiProjectData& data, const QString& shortcutFileName = QString::null); + + /*! Sets connection data \a data. + \a shortcutFileName is only used to check if the file is writable + (if no, "save changes" button will be disabled). */ + void setData(const KexiDB::ConnectionData& data, const QString& shortcutFileName = QString::null); + + KexiProjectData data(); + + //! \return a pointer to 'save changes' button. You can call hide() for this to hide it. + KPushButton* saveChangesButton() const; + + //! \return a pointer to 'test connection' button. You can call hide() for this to hide it. + KPushButton* testConnectionButton() const; + + KexiDBDriverComboBox *driversCombo() const { return m_driversCombo; } + + //! \return true if only connection data is managed by this widget + bool connectionOnly() const; + + signals: + //! emitted when data saving is needed + void saveChanges(); + + void loadDBList(); + + protected slots: + void slotLocationBGrpClicked(int id); + void slotCBToggled(bool on); + virtual void languageChange() { KexiDBConnectionWidgetBase::languageChange(); } + + protected: + void setDataInternal(const KexiProjectData& data, bool connectionOnly, + const QString& shortcutFileName); + + KexiProjectData m_data; + KexiDBDriverComboBox *m_driversCombo; + + class Private; + Private *d; + +// friend class KexiDBConnectionTabWidget; +}; + +class KEXIEXTWIDGETS_EXPORT KexiDBConnectionTabWidget : public KTabWidget +{ + Q_OBJECT + + public: + KexiDBConnectionTabWidget( QWidget* parent = 0, const char* name = 0 ); + virtual ~KexiDBConnectionTabWidget(); + + /*! Sets connection data \a data. + \a shortcutFileName is only used to check if the file is writable + (if no, "save changes" button will be disabled). */ + void setData(const KexiProjectData& data, const QString& shortcutFileName = QString::null); + void setData(const KexiDB::ConnectionData& data, const QString& shortcutFileName = QString::null); + KexiProjectData currentProjectData(); + + //! \return true if 'save password' option is selected + bool savePasswordOptionSelected() const; + + signals: + //! emitted when test connection is needed + void testConnection(); + + protected slots: + void slotTestConnection(); + + protected: + KexiDBConnectionWidget *mainWidget; + KexiDBConnectionWidgetDetailsBase* detailsWidget; + + friend class KexiDBConnectionDialog; +}; + +class KEXIEXTWIDGETS_EXPORT KexiDBConnectionDialog : public KDialogBase +{ + Q_OBJECT + + public: + /*! Creates a new connection dialog for project data \a data. + Not only connection data is visible but also database name and and title. + \a shortcutFileName is only used to check if the shortcut file is writable + (if no, "save changes" button will be disabled). + The shortcut file is in .KEXIS format. + Connect to saveChanges() signal to react on saving changes. + If \a shortcutFileName is empty, the button will be hidden. + \a acceptButtonGuiItem allows to override default "Open" button's appearance. */ + KexiDBConnectionDialog(const KexiProjectData& data, + const QString& shortcutFileName = QString::null, + const KGuiItem& acceptButtonGuiItem = KGuiItem("")); + + /*! Creates a new connection dialog for connection data \a data. + Only connection data is visible: database name and and title fields are hidden. + \a shortcutFileName is only used to check if the shortcut file is writable + (if no, "save changes" button will be disabled). + The shortcut file is in .KEXIC format. + See above constructor for more details. */ + KexiDBConnectionDialog(const KexiDB::ConnectionData& data, + const QString& shortcutFileName = QString::null, + const KGuiItem& acceptButtonGuiItem = KGuiItem("")); + + ~KexiDBConnectionDialog(); + + /*! \return project data displayed within the dialog. + Information about database name and title can be empty if the dialog + contain only a connection data (if second constructor was used). */ + KexiProjectData currentProjectData(); + + //! \return true if 'save password' option is selected + bool savePasswordOptionSelected() const; + + KexiDBConnectionWidget *mainWidget() const; + KexiDBConnectionWidgetDetailsBase* detailsWidget() const; + + signals: + //! emitted when data saving is needed + void saveChanges(); + + //! emitted when test connection is needed + void testConnection(); + + void loadDBList(); + + protected: + KexiDBConnectionTabWidget *tabWidget; + + private: + void init(); +}; + +#endif // KEXIDBCONNECTIONWIDGET_H diff --git a/kexi/widget/kexidbconnectionwidgetbase.ui b/kexi/widget/kexidbconnectionwidgetbase.ui new file mode 100644 index 00000000..70fcddca --- /dev/null +++ b/kexi/widget/kexidbconnectionwidgetbase.ui @@ -0,0 +1,464 @@ +<!DOCTYPE UI><UI version="3.3" stdsetdef="1"> +<class>KexiDBConnectionWidgetBase</class> +<widget class="QWidget"> + <property name="name"> + <cstring>KexiDBConnectionWidgetBase</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>381</width> + <height>395</height> + </rect> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <widget class="QLayoutWidget" row="0" column="0" rowspan="3" colspan="1"> + <property name="name"> + <cstring>layout7</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>iconLabel</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>48</width> + <height>48</height> + </size> + </property> + </widget> + <spacer> + <property name="name"> + <cstring>spacer7</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>20</width> + <height>331</height> + </size> + </property> + </spacer> + </vbox> + </widget> + <spacer row="4" column="1"> + <property name="name"> + <cstring>spacer6</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>MinimumExpanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>20</width> + <height>4</height> + </size> + </property> + </spacer> + <widget class="QButtonGroup" row="0" column="1"> + <property name="name"> + <cstring>locationBGrp</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>4</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>Database Server</string> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <spacer row="1" column="5" rowspan="1" colspan="2"> + <property name="name"> + <cstring>spacer9</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>41</width> + <height>20</height> + </size> + </property> + </spacer> + <widget class="KIntNumInput" row="3" column="4" rowspan="1" colspan="2"> + <property name="name"> + <cstring>customPortEdit</cstring> + </property> + <property name="minValue"> + <number>0</number> + </property> + </widget> + <widget class="QLabel" row="3" column="0" rowspan="1" colspan="2"> + <property name="name"> + <cstring>portLbl</cstring> + </property> + <property name="text"> + <string>Port:</string> + </property> + </widget> + <widget class="KLineEdit" row="2" column="2" rowspan="1" colspan="5"> + <property name="name"> + <cstring>hostEdit</cstring> + </property> + </widget> + <widget class="QRadioButton" row="1" column="0" rowspan="1" colspan="3"> + <property name="name"> + <cstring>localhostRBtn</cstring> + </property> + <property name="text"> + <string>Local server</string> + </property> + <property name="accel"> + <string></string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + <spacer row="3" column="6"> + <property name="name"> + <cstring>spacer1</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>90</width> + <height>20</height> + </size> + </property> + </spacer> + <widget class="QLabel" row="2" column="0" rowspan="1" colspan="2"> + <property name="name"> + <cstring>hostLbl</cstring> + </property> + <property name="text"> + <string>&Hostname:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>hostEdit</cstring> + </property> + </widget> + <widget class="QCheckBox" row="3" column="2" rowspan="1" colspan="2"> + <property name="name"> + <cstring>chkPortDefault</cstring> + </property> + <property name="text"> + <string>Default</string> + <comment>port: default</comment> + </property> + <property name="buttonGroupId"> + <number>2</number> + </property> + </widget> + <widget class="QRadioButton" row="1" column="3" rowspan="1" colspan="2"> + <property name="name"> + <cstring>remotehostRBtn</cstring> + </property> + <property name="text"> + <string>Remote server</string> + </property> + <property name="buttonGroupId"> + <number>1</number> + </property> + </widget> + <widget class="QLabel" row="0" column="0"> + <property name="name"> + <cstring>lblEngine</cstring> + </property> + <property name="focusPolicy"> + <enum>TabFocus</enum> + </property> + <property name="text"> + <string>&Engine:</string> + </property> + <property name="buddy" stdset="0"> + <cstring></cstring> + </property> + </widget> + <widget class="QFrame" row="0" column="1" rowspan="1" colspan="6"> + <property name="name"> + <cstring>frmEngine</cstring> + </property> + <property name="frameShape"> + <enum>NoFrame</enum> + </property> + <property name="frameShadow"> + <enum>Raised</enum> + </property> + </widget> + </grid> + </widget> + <widget class="QGroupBox" row="1" column="1"> + <property name="name"> + <cstring>authenticationGBox</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>4</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>Authentication</string> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel" row="0" column="0"> + <property name="name"> + <cstring>userLbl</cstring> + </property> + <property name="text"> + <string>&Username:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>userEdit</cstring> + </property> + </widget> + <widget class="KLineEdit" row="0" column="1"> + <property name="name"> + <cstring>userEdit</cstring> + </property> + </widget> + <widget class="QLabel" row="1" column="0"> + <property name="name"> + <cstring>passwordLbl</cstring> + </property> + <property name="text"> + <string>&Password:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>passwordEdit</cstring> + </property> + </widget> + <widget class="KLineEdit" row="1" column="1"> + <property name="name"> + <cstring>passwordEdit</cstring> + </property> + <property name="echoMode"> + <enum>Password</enum> + </property> + </widget> + <widget class="QCheckBox" row="2" column="1"> + <property name="name"> + <cstring>chkSavePassword</cstring> + </property> + <property name="text"> + <string>Save password in the shortcut file</string> + </property> + </widget> + </grid> + </widget> + <widget class="QGroupBox" row="2" column="1"> + <property name="name"> + <cstring>dbGroupBox</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>4</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>Database</string> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel" row="0" column="0"> + <property name="name"> + <cstring>nameLabel</cstring> + </property> + <property name="text"> + <string>&Name:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>nameCombo</cstring> + </property> + </widget> + <widget class="KLineEdit" row="1" column="1" rowspan="1" colspan="2"> + <property name="name"> + <cstring>titleEdit</cstring> + </property> + </widget> + <widget class="QLabel" row="1" column="0"> + <property name="name"> + <cstring>titleLabel</cstring> + </property> + <property name="text"> + <string>&Title (optional):</string> + </property> + <property name="alignment"> + <set>AlignTop</set> + </property> + <property name="buddy" stdset="0"> + <cstring>titleEdit</cstring> + </property> + </widget> + <widget class="KPushButton" row="0" column="2"> + <property name="name"> + <cstring>btnLoadDBList</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>4</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="maximumSize"> + <size> + <width>32</width> + <height>32767</height> + </size> + </property> + <property name="text"> + <string></string> + </property> + </widget> + <widget class="KComboBox" row="0" column="1"> + <property name="name"> + <cstring>nameCombo</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>1</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>1</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="editable"> + <bool>true</bool> + </property> + <property name="insertionPolicy"> + <enum>NoInsertion</enum> + </property> + <property name="autoCompletion"> + <bool>true</bool> + </property> + <property name="trapReturnKey"> + <bool>true</bool> + </property> + </widget> + </grid> + </widget> + <widget class="QLayoutWidget" row="3" column="0" rowspan="1" colspan="2"> + <property name="name"> + <cstring>layout5</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <spacer> + <property name="name"> + <cstring>spacer13</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Minimum</enum> + </property> + <property name="sizeHint"> + <size> + <width>80</width> + <height>20</height> + </size> + </property> + </spacer> + <widget class="QFrame"> + <property name="name"> + <cstring>frmBottom</cstring> + </property> + <property name="frameShape"> + <enum>NoFrame</enum> + </property> + <property name="frameShadow"> + <enum>Raised</enum> + </property> + </widget> + </hbox> + </widget> + </grid> +</widget> +<customwidgets> +</customwidgets> +<tabstops> + <tabstop>lblEngine</tabstop> + <tabstop>localhostRBtn</tabstop> + <tabstop>hostEdit</tabstop> + <tabstop>chkPortDefault</tabstop> + <tabstop>customPortEdit</tabstop> + <tabstop>userEdit</tabstop> + <tabstop>passwordEdit</tabstop> + <tabstop>chkSavePassword</tabstop> + <tabstop>nameCombo</tabstop> + <tabstop>btnLoadDBList</tabstop> + <tabstop>titleEdit</tabstop> + <tabstop>frmEngine</tabstop> +</tabstops> +<layoutdefaults spacing="6" margin="11"/> +<includehints> + <includehint>knuminput.h</includehint> + <includehint>knuminput.h</includehint> + <includehint>klineedit.h</includehint> + <includehint>klineedit.h</includehint> + <includehint>klineedit.h</includehint> + <includehint>klineedit.h</includehint> + <includehint>kpushbutton.h</includehint> + <includehint>kcombobox.h</includehint> + <includehint>klineedit.h</includehint> +</includehints> +</UI> diff --git a/kexi/widget/kexidbconnectionwidgetdetailsbase.ui b/kexi/widget/kexidbconnectionwidgetdetailsbase.ui new file mode 100644 index 00000000..b729c9bc --- /dev/null +++ b/kexi/widget/kexidbconnectionwidgetdetailsbase.ui @@ -0,0 +1,194 @@ +<!DOCTYPE UI><UI version="3.2" stdsetdef="1"> +<class>KexiDBConnectionWidgetDetailsBase</class> +<widget class="QWidget"> + <property name="name"> + <cstring>KexiDBConnectionWidgetDetailsBase</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>297</width> + <height>171</height> + </rect> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLayoutWidget" row="1" column="0"> + <property name="name"> + <cstring>layout8</cstring> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <property name="spacing"> + <number>0</number> + </property> + <widget class="QCheckBox" row="0" column="0"> + <property name="name"> + <cstring>chkUseSocket</cstring> + </property> + <property name="text"> + <string>Use socket &file instead of TCP/IP port:</string> + </property> + </widget> + <widget class="QLayoutWidget" row="1" column="0" rowspan="1" colspan="2"> + <property name="name"> + <cstring>layout6</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <property name="spacing"> + <number>0</number> + </property> + <spacer> + <property name="name"> + <cstring>spacer7</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Fixed</enum> + </property> + <property name="sizeHint"> + <size> + <width>16</width> + <height>20</height> + </size> + </property> + </spacer> + <widget class="QCheckBox"> + <property name="name"> + <cstring>chkSocketDefault</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>1</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Default</string> + <comment>socket: default</comment> + </property> + <property name="accel"> + <string></string> + </property> + </widget> + <widget class="KURLRequester"> + <property name="name"> + <cstring>customSocketEdit</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>3</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>1</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </hbox> + </widget> + <spacer row="0" column="1"> + <property name="name"> + <cstring>spacer6</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>129</width> + <height>20</height> + </size> + </property> + </spacer> + </grid> + </widget> + <widget class="QLayoutWidget" row="0" column="0"> + <property name="name"> + <cstring>layout9</cstring> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <property name="spacing"> + <number>0</number> + </property> + <widget class="KTextEdit" row="1" column="0"> + <property name="name"> + <cstring>descriptionEdit</cstring> + </property> + </widget> + <widget class="QLabel" row="0" column="0"> + <property name="name"> + <cstring>textLabel1_2</cstring> + </property> + <property name="text"> + <string>&Description:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>descriptionEdit</cstring> + </property> + </widget> + </grid> + </widget> + </grid> +</widget> +<customwidgets> +</customwidgets> +<connections> + <connection> + <sender>chkSocketDefault</sender> + <signal>toggled(bool)</signal> + <receiver>KexiDBConnectionWidgetDetailsBase</receiver> + <slot>slotCBToggled(bool)</slot> + </connection> + <connection> + <sender>chkUseSocket</sender> + <signal>toggled(bool)</signal> + <receiver>KexiDBConnectionWidgetDetailsBase</receiver> + <slot>slotCBToggled(bool)</slot> + </connection> +</connections> +<tabstops> + <tabstop>customSocketEdit</tabstop> + <tabstop>descriptionEdit</tabstop> + <tabstop>chkUseSocket</tabstop> + <tabstop>chkSocketDefault</tabstop> +</tabstops> +<includes> + <include location="local" impldecl="in implementation">kexidbconnectionwidgetdetailsbase.ui.h</include> +</includes> +<slots> + <slot access="protected">slotCBToggled( bool on )</slot> +</slots> +<layoutdefaults spacing="6" margin="11"/> +<includehints> + <includehint>kurlrequester.h</includehint> + <includehint>klineedit.h</includehint> + <includehint>kpushbutton.h</includehint> +</includehints> +</UI> diff --git a/kexi/widget/kexidbconnectionwidgetdetailsbase.ui.h b/kexi/widget/kexidbconnectionwidgetdetailsbase.ui.h new file mode 100644 index 00000000..90d6a4b8 --- /dev/null +++ b/kexi/widget/kexidbconnectionwidgetdetailsbase.ui.h @@ -0,0 +1,29 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +void KexiDBConnectionWidgetDetailsBase::slotCBToggled( bool on ) +{ + if (sender()==chkSocketDefault) { + customSocketEdit->setEnabled( !on ); + } + else if (sender()==chkUseSocket) { + customSocketEdit->setEnabled( on && !chkSocketDefault->isChecked() ); + chkSocketDefault->setEnabled( on ); + } +} diff --git a/kexi/widget/kexidbdrivercombobox.cpp b/kexi/widget/kexidbdrivercombobox.cpp new file mode 100644 index 00000000..e3431531 --- /dev/null +++ b/kexi/widget/kexidbdrivercombobox.cpp @@ -0,0 +1,92 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl> + + 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 Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "kexidbdrivercombobox.h" + +#include <qlistbox.h> + +#include <kiconloader.h> + +KexiDBDriverComboBox::KexiDBDriverComboBox(QWidget* parent, const KexiDB::Driver::InfoMap& driversInfo, + Options options) + : KComboBox(parent, "KexiDBDriverComboBox") +{ + //retrieve list of drivers and sort it: file-based first, then server-based + QStringList captionsForFileBasedDrivers, captionsForServerBasedDrivers; + QMap<QString,QString> fileBasedDriversDict, serverBasedDriversDict; //a map from caption to name + foreach(KexiDB::Driver::InfoMap::ConstIterator, it, driversInfo) { + if (it.data().fileBased) { + captionsForFileBasedDrivers += it.data().caption; + fileBasedDriversDict[it.data().caption] = it.data().name.lower(); + } + else { + captionsForServerBasedDrivers += it.data().caption; + serverBasedDriversDict[it.data().caption] = it.data().name.lower(); + } + } + captionsForFileBasedDrivers.sort(); + captionsForServerBasedDrivers.sort(); + //insert file-based + if (options & ShowFileDrivers) { + foreach(QStringList::ConstIterator, it, captionsForFileBasedDrivers) { + const KexiDB::Driver::Info& info = driversInfo[ fileBasedDriversDict[ *it ] ]; + //! @todo change this if better icon is available + insertItem( SmallIcon("gear"), info.caption ); + m_driversMap.insert(info.caption, info.name.lower()); + } + } + //insert server-based + if (options & ShowServerDrivers) { + foreach(QStringList::ConstIterator, it, captionsForServerBasedDrivers) { + const KexiDB::Driver::Info& info = driversInfo[ serverBasedDriversDict[ *it ] ]; + //! @todo change this if better icon is available + insertItem( SmallIcon("gear"), info.caption ); + m_driversMap.insert(info.caption, info.name.lower()); + } + } +// if (listBox()) +// listBox()->sort(); + + // Build the names list after sorting + for (int i=0; i<count(); i++) + m_driverNames += m_driversMap[ text(i) ]; +} + +KexiDBDriverComboBox::~KexiDBDriverComboBox() +{ +} + +QString KexiDBDriverComboBox::selectedDriverName() const +{ + QMapConstIterator<QString,QString> it( m_driversMap.find( text( currentItem() ) ) ); + if (it==m_driversMap.constEnd()) + return QString::null; + return it.data(); +} + +void KexiDBDriverComboBox::setDriverName(const QString& driverName) +{ + int index = m_driverNames.findIndex( driverName.lower() ); + if (index==-1) { + return; + } + setCurrentItem(index); +} + +#include "kexidbdrivercombobox.moc" diff --git a/kexi/widget/kexidbdrivercombobox.h b/kexi/widget/kexidbdrivercombobox.h new file mode 100644 index 00000000..981b67c3 --- /dev/null +++ b/kexi/widget/kexidbdrivercombobox.h @@ -0,0 +1,102 @@ +/* This file is part of the KDE project + Copyright (C) 2005-2006 Jaroslaw Staniek <js@iidea.pl> + + 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 Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIDBDRIVERCOMBOBOX_H +#define KEXIDBDRIVERCOMBOBOX_H + +#include <qwidget.h> +#include <qmap.h> + +#include <kcombobox.h> + +#include <kexidb/driver.h> + +//! \brief Combo box widget for selecting a database driver +/*! This widget provides a combobox for selecting a database driver. + + +\b Usage: \n +\code + KexiDB::DriverManager manager; + KexiDB::Driver::InfoMap drvs = manager.driversInfo(); + + KexiDBDriverComboBox* combo = new KexiDBDriverComboBox(drvs, true, 0); +\endcode + +A more complete example can be found in +<a href="http://websvn.kde.org/trunk/koffice/kexi/tests/widgets/kexidbdrivercombotest.cpp?&view=auto">koffice/kexi/tests/widgets/</a>. + +*/ + +class KEXIEXTWIDGETS_EXPORT KexiDBDriverComboBox : public KComboBox +{ + Q_OBJECT + + public: + enum Options { + ShowFileDrivers = 1, + ShowServerDrivers = 2, + ShowAll = ShowFileDrivers|ShowServerDrivers + }; + + /*! Constructs a KexiDBDriverComboBox object. + + The combobox is populated with the names of the drivers in + \a driversInfo. A suitable value for \a driversInfo can be obtained + from KexiDB::DriverManager::driversInfo(). + + If \a includeFileBasedDrivers is set to false, then only those drivers + that are for database servers (those which have X-Kexi-DriverType=Network + in their .desktop file) are shown. */ + KexiDBDriverComboBox(QWidget* parent, const KexiDB::Driver::InfoMap& driversInfo, + Options options = ShowAll ); + + ~KexiDBDriverComboBox(); + + /*! Gets a list of the names of all drivers. + + Note that this returns just the names of those drivers that are in the + combobox: if the includeFileBasedDrivers argument to the constructor + was false, this won't include the file based drivers either. + + \return a list of names of drivers that were found */ + QStringList driverNames() const { return m_driverNames; } + + /*! Get the name of the currrently selected driver. If the combobox is empty, + QString::null will be returned. + + \return the name of the currently selected driver */ + QString selectedDriverName() const; + + /*! Set the currrently selected driver. + + The combobox entry for \a driverName is selected. If \a driverName + is not listed in the combobox then there is no change. The search + is case insensitive. + + */ + void setDriverName(const QString& driverName); + + protected: + QMap<QString,QString> m_driversMap; + QStringList m_driverNames; +}; + +#endif + diff --git a/kexi/widget/kexidswelcome.cpp b/kexi/widget/kexidswelcome.cpp new file mode 100644 index 00000000..957132a1 --- /dev/null +++ b/kexi/widget/kexidswelcome.cpp @@ -0,0 +1,90 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Lucijan Busch <lucijan@kde.org> + + 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 Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include <qlayout.h> +#include <qcheckbox.h> +#include <qpushbutton.h> + +#include <klocale.h> +#include <kiconloader.h> +#include <kglobalsettings.h> +#include <kstdguiitem.h> +#include <kdeversion.h> + +#include "kexidatasourcewizard.h" +#include "kexidswelcome.h" + +KexiDSWelcome::KexiDSWelcome(KexiDataSourceWizard *parent) + : QWidget(parent) +{ + m_wiz = parent; + KexiDSPixmap *pic = new KexiDSPixmap(this); + + QLabel *lText = new QLabel(i18n("Kexi can help you with creation of %2 using data sources in almost no time with the \"%1 Wizard\""), this); + lText->setAlignment(AlignTop | AlignLeft | WordBreak); + QCheckBox *useWizard = new QCheckBox(i18n("Create %1 using the \"%1 Wizard\""), this); + connect(useWizard, SIGNAL(toggled(bool)), this, SLOT(setUseWizard(bool))); + useWizard->setChecked(true); + + QSpacerItem *spacer = new QSpacerItem(320, 220); + QCheckBox *dontShow = new QCheckBox(i18n("Do not show this wizard again"), this); + + QGridLayout *g = new QGridLayout(this); + + g->addMultiCellWidget(pic, 0, 4, 0, 0); + g->addWidget(lText, 0, 1); + g->addWidget(useWizard, 2, 1); + g->addItem(spacer, 3, 1); + g->addWidget(dontShow, 4, 1); +} + +void +KexiDSWelcome::setUseWizard(bool use) +{ +#if KDE_IS_VERSION(3,1,9) && !defined(Q_WS_WIN) + bool useIcons = KGlobalSettings::showIconsOnPushButtons(); +#else + bool useIcons = true; +#endif + if(use) + { + KGuiItem forward = KStdGuiItem::forward(KStdGuiItem::UseRTL); + + if(useIcons) + m_wiz->nextButton()->setIconSet( forward.iconSet() ); + + m_wiz->nextButton()->setText(i18n("&Next")); + } + else + { + if(useIcons) + m_wiz->nextButton()->setIconSet(SmallIconSet("apply")); + + m_wiz->nextButton()->setText(i18n("&Finish")); + } + + m_wiz->finishNext(!use); +} + +KexiDSWelcome::~KexiDSWelcome() +{ +} + +#include "kexidswelcome.moc" + diff --git a/kexi/widget/kexidswelcome.h b/kexi/widget/kexidswelcome.h new file mode 100644 index 00000000..23cb64fe --- /dev/null +++ b/kexi/widget/kexidswelcome.h @@ -0,0 +1,48 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Lucijan Busch <lucijan@kde.org> + + 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 Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIDSWELCOME_H +#define KEXIDSWELCOME_H + +#include <qwidget.h> + +class KexiDataSourceWizard; + +/** + * This page is part of the KexiDataSourceWizard + * it is the greeting page per default, where people + * can choose whether they want to use the wizard or not. + */ +class KEXIEXTWIDGETS_EXPORT KexiDSWelcome : public QWidget +{ + Q_OBJECT + + public: + KexiDSWelcome(KexiDataSourceWizard *parent); + ~KexiDSWelcome(); + + protected slots: + void setUseWizard(bool use); + + private: + KexiDataSourceWizard *m_wiz; +}; + +#endif + diff --git a/kexi/widget/kexieditor.cpp b/kexi/widget/kexieditor.cpp new file mode 100644 index 00000000..f482584e --- /dev/null +++ b/kexi/widget/kexieditor.cpp @@ -0,0 +1,261 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Lucijan Busch <lucijan@gmx.at> + Copyright (C) 2004-2005 Jaroslaw Staniek <js@iidea.pl> + Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "kexieditor.h" + +#include <keximainwindow.h> + +#include <qlayout.h> +#include <qframe.h> +#include <klocale.h> +#include <kdebug.h> + +//uncomment this to enable KTextEdit-based editor +//#define KTEXTEDIT_BASED_SQL_EDITOR + +#ifdef KTEXTEDIT_BASED_SQL_EDITOR +# include <ktextedit.h> +#else +# include <ktexteditor/document.h> +# include <ktexteditor/view.h> +# include <ktexteditor/editorchooser.h> +# include <ktexteditor/editinterface.h> +# include <ktexteditor/viewcursorinterface.h> +# include <ktexteditor/popupmenuinterface.h> +# include <ktexteditor/undointerface.h> +# include <ktexteditor/configinterface.h> +# include <ktexteditor/highlightinginterface.h> +#endif + +/** Used for the shared action framework to redirect shared actions like +copy and paste to the editor. */ +class KexiEditorSharedActionConnector : public KexiSharedActionConnector +{ +public: + KexiEditorSharedActionConnector( KexiActionProxy* proxy, QObject* obj ) + : KexiSharedActionConnector( proxy, obj ) + { +#ifdef KTEXTEDIT_BASED_SQL_EDITOR + plugSharedAction("edit_cut", SLOT(cut())); + plugSharedAction("edit_copy", SLOT(copy())); + plugSharedAction("edit_paste", SLOT(paste())); + plugSharedAction("edit_clear", SLOT(clear())); + plugSharedAction("edit_undo", SLOT(undo())); + plugSharedAction("edit_redo", SLOT(redo())); + plugSharedAction("edit_select_all", SLOT(selectAll())); +#else + QValueList<QCString> actions; + actions << "edit_cut" << "edit_copy" << "edit_paste" << "edit_clear" + << "edit_undo" << "edit_redo" << "edit_select_all"; + plugSharedActionsToExternalGUI(actions, dynamic_cast<KXMLGUIClient*>(obj)); +#endif + } +}; + +//! @internal +class KexiEditorPrivate { + public: +#ifdef KTEXTEDIT_BASED_SQL_EDITOR + KTextEdit *view; +#else + KTextEditor::Document *doc; + KTextEditor::View *view; +#endif +}; + +KexiEditor::KexiEditor(KexiMainWindow *mainWin, QWidget *parent, const char *name) + : KexiViewBase(mainWin, parent, name) + , d(new KexiEditorPrivate()) +{ + QVBoxLayout *layout = new QVBoxLayout(this); +#ifdef KTEXTEDIT_BASED_SQL_EDITOR + d->view = new KTextEdit( "", QString::null, this, "kexi_editor" ); + //adjust font + connect(d->view, SIGNAL(textChanged()), this, SIGNAL(textChanged())); + QFont f("Courier"); + f.setStyleStrategy(QFont::PreferAntialias); + f.setPointSize(d->view->font().pointSize()); + d->view->setFont( f ); + d->view->setCheckSpellingEnabled(false); +#else + QFrame *fr = new QFrame(this); + fr->setFrameStyle(QFrame::Sunken|QFrame::WinPanel); + layout->addWidget(fr); + layout = new QVBoxLayout(fr); + layout->setMargin( 2 ); + + d->doc = KTextEditor::EditorChooser::createDocument(fr); + if (!d->doc) + return; + d->view = d->doc->createView(fr, 0L); + + KTextEditor::PopupMenuInterface *popupInt = dynamic_cast<KTextEditor::PopupMenuInterface*>( d->view ); + if(popupInt) { + QPopupMenu *pop = (QPopupMenu*) mainWin->factory()->container("edit", mainWin); + if(pop) { + //plugSharedAction("edit_undo", pop); + popupInt->installPopup(pop); + } + } + + connect(d->doc, SIGNAL(textChanged()), this, SIGNAL(textChanged())); +#endif + KexiEditorSharedActionConnector c(this, d->view); + d->view->installEventFilter(this); + + layout->addWidget(d->view); + setViewWidget(d->view, true/*focus*/); + d->view->show(); +} + +KexiEditor::~KexiEditor() +{ + delete d; +} + +void KexiEditor::updateActions(bool activated) +{ + KexiViewBase::updateActions(activated); +} + +bool KexiEditor::isAdvancedEditor() +{ +#ifdef KTEXTEDIT_BASED_SQL_EDITOR + return false; +#else + return true; +#endif +} + +QString KexiEditor::text() +{ +#ifdef KTEXTEDIT_BASED_SQL_EDITOR + return d->view->text(); +#else + if (!d->doc) + return QString::null; + KTextEditor::EditInterface *eIface = KTextEditor::editInterface(d->doc); + return eIface->text(); +#endif +} + +void KexiEditor::setText(const QString &text) +{ +#ifdef KTEXTEDIT_BASED_SQL_EDITOR + const bool was_dirty = m_parentView ? m_parentView->dirty() : dirty(); + d->view->setText(text); + setDirty(was_dirty); +#else + if (!d->doc) + return; + const bool was_dirty = dirty(); + KTextEditor::EditInterface *eIface = KTextEditor::editInterface(d->doc); + eIface->setText(text); + KTextEditor::UndoInterface *undoIface = KTextEditor::undoInterface(d->doc); + undoIface->clearUndo(); + undoIface->clearRedo(); + setDirty(was_dirty); +#endif +} + +void KexiEditor::setHighlightMode(const QString& highlightmodename) +{ +#ifdef KTEXTEDIT_BASED_SQL_EDITOR +#else + KTextEditor::HighlightingInterface *hl = KTextEditor::highlightingInterface( d->doc ); + for(uint i = 0; i < hl->hlModeCount(); i++) { + //kdDebug() << "hlmode("<<i<<"): " << hl->hlModeName(i) << endl; + if (hl->hlModeName(i).contains(highlightmodename, false)) { + hl->setHlMode(i); + return; + } + } + hl->setHlMode(0); // 0=None, don't highlight anything. +#endif +} + +void KexiEditor::slotConfigureEditor() +{ +#ifdef KTEXTEDIT_BASED_SQL_EDITOR + //TODO show errormessage? +#else + KTextEditor::ConfigInterface *config = KTextEditor::configInterface( d->doc ); + if (config) + config->configDialog(); +#endif +} + +void KexiEditor::jump(int character) +{ +#ifdef KTEXTEDIT_BASED_SQL_EDITOR + const int numRows = d->view->paragraphs(); + int row = 0, col = 0; + for (int ch = 0; row < numRows; row++) { + const int rowLen = d->view->paragraphLength(row)+1; + if ((ch + rowLen) > character) { + col = character-ch; + break; + } + ch += rowLen; + } + d->view->setCursorPosition(row, col); +#else + if (!d->doc) + return; + KTextEditor::EditInterface *ei = KTextEditor::editInterface(d->doc); + const int numRows = ei->numLines(); + int row = 0, col = 0; + for (int ch = 0; row < numRows; row++) { + const int rowLen = ei->lineLength(row)+1; + if ((ch + rowLen) > character) { + col = character-ch; + break; + } + ch += rowLen; + } + KTextEditor::ViewCursorInterface *ci = KTextEditor::viewCursorInterface(d->view); + ci->setCursorPositionReal(row, col); +#endif +} + +void KexiEditor::setCursorPosition(int line, int col) +{ +#ifdef KTEXTEDIT_BASED_SQL_EDITOR + d->view->setCursorPosition(line, col); +#else + KTextEditor::ViewCursorInterface *ci = KTextEditor::viewCursorInterface( d->view ); + ci->setCursorPosition(line, col); +#endif +} + +void KexiEditor::clearUndoRedo() +{ +#ifdef KTEXTEDIT_BASED_SQL_EDITOR + //TODO how to remove undo/redo from a KTextEdit? +#else + KTextEditor::UndoInterface* u = KTextEditor::undoInterface( d->doc ); + u->clearUndo(); + u->clearRedo(); +#endif +} + +#include "kexieditor.moc" + diff --git a/kexi/widget/kexieditor.h b/kexi/widget/kexieditor.h new file mode 100644 index 00000000..fce5f45b --- /dev/null +++ b/kexi/widget/kexieditor.h @@ -0,0 +1,120 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Lucijan Busch <lucijan@gmx.at> + Copyright (C) 2004-2005 Jaroslaw Staniek <js@iidea.pl> + Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIEDITOR_H +#define KEXIEDITOR_H + +#include <qwidget.h> +#include "kexiviewbase.h" + +class KTextEdit; +class KexiEditorPrivate; + +namespace KTextEditor +{ + class Document; + class View; +} + +//! An text editor view that uses both KTextEditor and KTextEdit +/*! It is used for SQL and script editor. */ +class KEXIEXTWIDGETS_EXPORT KexiEditor : public KexiViewBase +{ + Q_OBJECT + + public: + + /** + * Constructor. + * + * \param mainWin The \a KexiMainWindow instance this KexiEditor + * belongs too. + * \param parent The parent \a QWidget this KexiEditor is child + * of. You don't need to free the KexiEditor cause Qt + * will handle that for us. + * \param name The name this KexiEditor has. Used only for debugging. + */ + KexiEditor(KexiMainWindow *mainWin, QWidget *parent, const char *name = 0); + + /** + * Destructor. + */ + virtual ~KexiEditor(); + + /** + * \return true if internally the KTextEditor::EditorChooser got + * used else, if a simple KTextEdit is used, false is returned. + */ + static bool isAdvancedEditor(); + + /** + * \return the text displayed in the editor-widget. + */ + QString text(); + + /** + * Set the highlight-mode to \p highlightmodename . If + * \a isAdvancedEditor returns false (KTextEdit is used + * rather then KTextEditor), then the method just does + * nothing. The \p highlightmodename could be any kind + * of string like e.g. "python", "kjs" or "sql" + * KTextEditor supports. + */ + void setHighlightMode(const QString& highlightmodename); + + /** + * Find row and column for this \p character and jump to the + * position. + */ + void jump(int character); + + /** + * Set the cursor position to \p line and \p col . + */ + void setCursorPosition(int line, int col); + + /** + * Clear all remembered undo/redo-actions. Only + * avaiable if \a isAdvancedEditor returns true. + */ + void clearUndoRedo(); + + public slots: + /*! Sets editor's text to \a text. 'Dirty' flag remains unchanged. + Undo/redo buffer is cleared.*/ + void setText(const QString &text); + /*! Display the configuration-dialog. Only avaiable if isAdvancedEditor() returns true. */ + void slotConfigureEditor(); + + protected: + /*! Update the actions. This call is redirected to \a KexiViewBase::updateActions */ + virtual void updateActions(bool activated); + + signals: + /*! Emitted if the text displayed in the editor changed. */ + void textChanged(); + + private: + /*! Private d-pointer class. */ + KexiEditorPrivate *d; +}; + +#endif diff --git a/kexi/widget/kexifieldcombobox.cpp b/kexi/widget/kexifieldcombobox.cpp new file mode 100644 index 00000000..c0355e9c --- /dev/null +++ b/kexi/widget/kexifieldcombobox.cpp @@ -0,0 +1,250 @@ +/* This file is part of the KDE project + Copyright (C) 2005-2006 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "kexifieldcombobox.h" + +#include <qheader.h> +#include <qlayout.h> +#include <qlabel.h> +#include <qpushbutton.h> +#include <qcursor.h> +#include <qpoint.h> +#include <qapplication.h> +#include <qbitmap.h> +#include <qstyle.h> +#include <qlistbox.h> + +#include <kdebug.h> +#include <kiconloader.h> +#include <kdeversion.h> +#include <kconfig.h> +#include <kglobalsettings.h> +#include <klocale.h> + +#include <kexidb/tableschema.h> +#include <kexidb/queryschema.h> +#include <kexidb/utils.h> +#include <kexiutils/utils.h> +#include <kexidragobjects.h> +#include <kexiproject.h> + +//! @internal +class KexiFieldComboBox::Private +{ + public: + Private() +// : schema(0) + : keyIcon( SmallIcon("key") ) + , noIcon( KexiUtils::emptyIcon(KIcon::Small) ) + , table(true) + { + } + ~Private() + { +// delete schema; + } + QGuardedPtr<KexiProject> prj; +// KexiDB::TableOrQuerySchema* schema; + QPixmap keyIcon, noIcon; + QString tableOrQueryName; + QString fieldOrExpression; + QMap<QString, QString> captions; + bool table : 1; +}; + +//------------------------ + +KexiFieldComboBox::KexiFieldComboBox(QWidget *parent, const char *name) + : KComboBox(true/*rw*/, parent, name) + , d(new Private()) +{ + setInsertionPolicy(NoInsertion); + setCompletionMode(KGlobalSettings::CompletionPopupAuto); + setSizeLimit( 16 ); + connect(this, SIGNAL(activated(int)), this, SLOT(slotActivated(int))); + connect(this, SIGNAL(returnPressed(const QString &)), this, SLOT(slotReturnPressed(const QString &))); + +// setAcceptDrops(true); +// viewport()->setAcceptDrops(true); +} + +KexiFieldComboBox::~KexiFieldComboBox() +{ + delete d; +} + +void KexiFieldComboBox::setProject(KexiProject *prj) +{ + if ((KexiProject*)d->prj==prj) + return; + d->prj = prj; + setTableOrQuery("", true); +} + +KexiProject* KexiFieldComboBox::project() const +{ + return d->prj; +} + +void KexiFieldComboBox::setTableOrQuery(const QString& name, bool table) +{ + d->tableOrQueryName = name; + d->table = table; + clear(); + d->captions.clear(); + insertItem(""); +// delete d->schema; + if (d->tableOrQueryName.isEmpty() || !d->prj) + return; + + KexiDB::TableOrQuerySchema tableOrQuery(d->prj->dbConnection(), d->tableOrQueryName.latin1(), d->table); + if (!tableOrQuery.table() && !tableOrQuery.query()) + return; + +// bool hasPKeys = true; //t->hasPrimaryKeys(); + KexiDB::QueryColumnInfo::Vector columns = tableOrQuery.columns(); + const int count = columns.count(); + for(int i=0; i < count; i++) + { + KexiDB::QueryColumnInfo *colinfo = columns[i]; + insertItem( + (colinfo && (colinfo->field->isPrimaryKey() || colinfo->field->isUniqueKey())) + ? d->keyIcon + : d->noIcon + , colinfo->aliasOrName()); + completionObject()->addItem(colinfo->aliasOrName()); + //store user-friendly caption (used by fieldOrExpressionCaption()) + d->captions.insert( colinfo->aliasOrName(), colinfo->captionOrAliasOrName() ); + } + + //update selection + setFieldOrExpression(d->fieldOrExpression); +} + +QString KexiFieldComboBox::tableOrQueryName() const +{ + return d->tableOrQueryName; +} + +bool KexiFieldComboBox::isTableAssigned() const +{ + return d->table; +} + +void KexiFieldComboBox::setFieldOrExpression(const QString& string) +{ + const QString name(string); //string.stripWhiteSpace().lower()); + const int pos = name.find('.'); + if (pos==-1) { + d->fieldOrExpression = name; + } + else { + QString objectName = name.left(pos); + if (d->tableOrQueryName!=objectName) { + d->fieldOrExpression = name; + setCurrentItem(0); + setCurrentText(name); +//! @todo show error + kexiwarn << "KexiFieldComboBox::setField(): invalid table/query name in '" << name << "'" << endl; + return; + } + d->fieldOrExpression = name.mid(pos+1); + } + + QListBoxItem *item = listBox()->findItem(d->fieldOrExpression); + if (!item) { + setCurrentItem(0); + setCurrentText(d->fieldOrExpression); + //todo: show 'the item doesn't match' info? + return; + } + setCurrentItem( listBox()->index(item) ); +} + +void KexiFieldComboBox::setFieldOrExpression(int index) +{ + index++; //skip 1st empty item + if (index>=count()) { + kexiwarn << QString("KexiFieldComboBox::setFieldOrExpression(int index): index %1 " + "out of range (0..%2)").arg(index).arg(count()-1) << endl; + index = -1; + } + if (index<=0) { + setCurrentItem(0); + d->fieldOrExpression = QString::null; + } + else { + setCurrentItem(index); + d->fieldOrExpression = currentText(); + } +} + +QString KexiFieldComboBox::fieldOrExpression() const +{ + return d->fieldOrExpression; +} + +int KexiFieldComboBox::indexOfField() const +{ + KexiDB::TableOrQuerySchema tableOrQuery(d->prj->dbConnection(), d->tableOrQueryName.latin1(), d->table); + if (!tableOrQuery.table() && !tableOrQuery.query()) + return -1; + + return currentItem()>0 ? (currentItem()-1) : -1; +} + +QString KexiFieldComboBox::fieldOrExpressionCaption() const +{ + return d->captions[ d->fieldOrExpression ]; +} + +void KexiFieldComboBox::slotActivated(int i) +{ + d->fieldOrExpression = text(i); + emit selected(); +} + +void KexiFieldComboBox::slotReturnPressed(const QString & text) +{ + //text is available: select item for this text: + int index; + if (text.isEmpty()) { + index = 0; + } + else { + QListBoxItem *item = listBox()->findItem( text, Qt::ExactMatch ); + if (!item) + return; + index = listBox()->index( item ); + if (index < 1) + return; + } + setCurrentItem( index ); + slotActivated( index ); +} + +void KexiFieldComboBox::focusOutEvent( QFocusEvent *e ) +{ + KComboBox::focusOutEvent( e ); + // accept changes if the focus is moved + if (!KexiUtils::hasParent(this, focusWidget())) //(a check needed because drop-down listbox also causes a focusout) + slotReturnPressed(currentText()); +} + +#include "kexifieldcombobox.moc" diff --git a/kexi/widget/kexifieldcombobox.h b/kexi/widget/kexifieldcombobox.h new file mode 100644 index 00000000..238168d2 --- /dev/null +++ b/kexi/widget/kexifieldcombobox.h @@ -0,0 +1,82 @@ +/* This file is part of the KDE project + Copyright (C) 2005-2006 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIFIELDCOMBOBOX_H +#define KEXIFIELDCOMBOBOX_H + +#include <qpixmap.h> +#include <kcombobox.h> + +namespace KexiDB { + class TableOrQuerySchema; +} +class KexiProject; + +/*! This widget provides a list of fields from a table or query + within a combobox, so user can pick one of them. +*/ +class KEXIEXTWIDGETS_EXPORT KexiFieldComboBox : public KComboBox +{ + Q_OBJECT + + public: + KexiFieldComboBox(QWidget *parent, const char *name = 0); + virtual ~KexiFieldComboBox(); + +// /*! Sets table or query schema \a schema. +// The schema object will be owned by the KexiFieldComboBox object. */ +// void setSchema(KexiDB::TableOrQuerySchema* schema); + +// KexiDB::TableOrQuerySchema* schema() const { return m_schema; } + + public slots: + //! \return global project that is used to retrieve schema informationm for this combo box. + KexiProject* project() const; + + //! Sets global project that is used to retrieve schema informationm for this combo box. + void setProject(KexiProject *prj); + + void setTableOrQuery(const QString& name, bool table); + QString tableOrQueryName() const; + bool isTableAssigned() const; + void setFieldOrExpression(const QString& string); + void setFieldOrExpression(int index); + QString fieldOrExpression() const; + QString fieldOrExpressionCaption() const; + + /*! \return index of selected table or query field. + -1 is returned if there is nothing selected or expression is selected + of project is not assigned or table or query is not assigned. */ + int indexOfField() const; + + signals: + void selected(); + + protected slots: + void slotActivated(int); + void slotReturnPressed(const QString & text); + + protected: + virtual void focusOutEvent( QFocusEvent *e ); + + class Private; + Private *d; +}; + +#endif diff --git a/kexi/widget/kexifieldlistview.cpp b/kexi/widget/kexifieldlistview.cpp new file mode 100644 index 00000000..84b577a6 --- /dev/null +++ b/kexi/widget/kexifieldlistview.cpp @@ -0,0 +1,180 @@ +/* This file is part of the KDE project + Copyright (C) 2003-2005 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "kexifieldlistview.h" + +#include <qheader.h> +#include <qlayout.h> +#include <qlabel.h> +#include <qpushbutton.h> +#include <qcursor.h> +#include <qpoint.h> +#include <qapplication.h> +#include <qbitmap.h> +#include <qstyle.h> + +#include <kdebug.h> +#include <kiconloader.h> +#include <kdeversion.h> +#include <kconfig.h> +#include <kglobalsettings.h> +#include <klocale.h> + +#include <kexidb/tableschema.h> +#include <kexidb/queryschema.h> +#include <kexidb/utils.h> +#include <kexidragobjects.h> +#include <kexiutils/utils.h> + +KexiFieldListView::KexiFieldListView(QWidget *parent, const char *name, int options) + : KListView(parent, name) + , m_schema(0) + , m_keyIcon(SmallIcon("key")) + , m_noIcon(KexiUtils::emptyIcon(KIcon::Small)) + , m_options(options) + , m_allColumnsItem(0) +{ + setAcceptDrops(true); + viewport()->setAcceptDrops(true); + setDropVisualizer(false); + setDropHighlighter(true); + setAllColumnsShowFocus(true); + addColumn(i18n("Field Name")); + if (m_options & ShowDataTypes) + addColumn(i18n("Data Type")); + if (m_options & AllowMultiSelection) + setSelectionMode(QListView::Extended); + setResizeMode(QListView::LastColumn); +// header()->hide(); + setSorting(-1, true); // disable sorting + setDragEnabled(true); + + connect(this, SIGNAL(doubleClicked(QListViewItem*, const QPoint &, int)), + this, SLOT(slotDoubleClicked(QListViewItem*))); +} + +KexiFieldListView::~KexiFieldListView() +{ + delete m_schema; +} + +void KexiFieldListView::setSchema(KexiDB::TableOrQuerySchema* schema) +{ + if (schema && m_schema == schema) + return; + m_allColumnsItem = 0; + clear(); + delete m_schema; + m_schema = schema; + if (!m_schema) + return; + + int order=0; + bool hasPKeys = true; //t->hasPrimaryKeys(); + KListViewItem *item = 0; + KexiDB::QueryColumnInfo::Vector columns = m_schema->columns(true /*unique*/); + const int count = columns.count(); + for(int i=-1; i < count; i++) + { + KexiDB::QueryColumnInfo *colinfo = 0; + if (i==-1) { + if (! (m_options & ShowAsterisk)) + continue; + item = new KListViewItem(this, item, i18n("* (All Columns)")); + m_allColumnsItem = item; + } + else { + colinfo = columns[i]; + item = new KListViewItem(this, item, colinfo->aliasOrName()); + if (m_options & ShowDataTypes) + item->setText(1, colinfo->field->typeName()); + } + if(colinfo && (colinfo->field->isPrimaryKey() || colinfo->field->isUniqueKey())) + item->setPixmap(0, m_keyIcon); + else if (hasPKeys) { + item->setPixmap(0, m_noIcon); + } + order++; + } + + setCurrentItem(firstChild()); +} + +#if 0 +QSize KexiFieldListView::sizeHint() +{ + QFontMetrics fm(font()); + + kdDebug() << m_table->name() << " cw=" << columnWidth(1) + fm.width("i") << ", " << fm.width(m_table->name()+" ") << endl; + + QSize s( + QMAX( columnWidth(1) + fm.width("i"), fm.width(m_table->name()+" ")), + childCount()*firstChild()->totalHeight() + 4 ); +// QSize s( columnWidth(1), childCount()*firstChild()->totalHeight() + 3*firstChild()->totalHeight()/10); + return s; +} + +void KexiFieldListView::setReadOnly(bool b) +{ + setAcceptDrops(!b); + viewport()->setAcceptDrops(!b); +} +#endif + +QDragObject* KexiFieldListView::dragObject() +{ + if (!schema()) + return 0; + const QStringList selectedFields( selectedFieldNames() ); + return new KexiFieldDrag(m_schema->table() ? "kexi/table" : "kexi/query", + m_schema->name(), selectedFields, this, "KexiFieldDrag"); +/* if (selectedItem()) { + KexiFieldDrag *drag = new KexiFieldDrag("kexi/table", m_schema->name(), + selectedItem()->text(1), this, "KexiFieldDrag"); + return drag; + }*/ +} + +QStringList KexiFieldListView::selectedFieldNames() const +{ + if (!schema()) + return QStringList(); + QStringList selectedFields; + for (QListViewItem *item = firstChild(); item; item = item->nextSibling()) { + if (item->isSelected()) { +//! @todo what about query fields/aliases? it.current()->text(0) can be not enough + if (item == m_allColumnsItem && m_allColumnsItem) + selectedFields.append("*"); + else + selectedFields.append(item->text(0)); + } + } + return selectedFields; +} + +void KexiFieldListView::slotDoubleClicked(QListViewItem* item) +{ + if (schema() && item) { + //! @todo what about query fields/aliases? it.current()->text(0) can be not enough + emit fieldDoubleClicked(schema()->table() ? "kexi/table" : "kexi/query", + schema()->name(), item->text(0)); + } +} + +#include "kexifieldlistview.moc" diff --git a/kexi/widget/kexifieldlistview.h b/kexi/widget/kexifieldlistview.h new file mode 100644 index 00000000..fcbe1c5f --- /dev/null +++ b/kexi/widget/kexifieldlistview.h @@ -0,0 +1,82 @@ +/* This file is part of the KDE project + Copyright (C) 2003-2005 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIFIELDLISTVIEW_H +#define KEXIFIELDLISTVIEW_H + +#include <qframe.h> +#include <qpixmap.h> +#include <klistview.h> + +class KListViewItem; + +namespace KexiDB { + class TableOrQuerySchema; +} + +/*! This widget provides a list of fields from a table or query. +*/ +class KEXIEXTWIDGETS_EXPORT KexiFieldListView : public KListView +{ + Q_OBJECT + + public: + //! Flags used to alter list's behaviour and appearance + enum Options { + ShowDataTypes = 1, //!< if set, 'data type' column is added + ShowAsterisk = 2, //!< if set, asterisk ('*') item is prepended to the list + AllowMultiSelection = 4 //!< if set, multiple selection is allowed + }; + + KexiFieldListView(QWidget *parent, const char *name = 0, + int options = ShowDataTypes | AllowMultiSelection ); + virtual ~KexiFieldListView(); + + /*! Sets table or query schema \a schema. + The schema object will be owned by the KexiFieldListView object. */ + void setSchema(KexiDB::TableOrQuerySchema* schema); + + /*! \return table or query schema schema set for this widget. */ + KexiDB::TableOrQuerySchema* schema() const { return m_schema; } + + /*! \return list of selected field names. */ + QStringList selectedFieldNames() const; + +// void setReadOnly(bool); +// virtual QSize sizeHint(); + + signals: + /*! Emitted when a field is double clicked */ + void fieldDoubleClicked(const QString& sourceMimeType, const QString& sourceName, + const QString& fieldName); + + protected slots: + void slotDoubleClicked(QListViewItem* item); + + protected: + virtual QDragObject *dragObject(); + + KexiDB::TableOrQuerySchema* m_schema; + QPixmap m_keyIcon; //!< a small "primary key" icon for 0-th column + QPixmap m_noIcon; //!< blank icon of the same size as m_keyIcon + int m_options; + KListViewItem *m_allColumnsItem; +}; + +#endif diff --git a/kexi/widget/kexifilterdlg.cpp b/kexi/widget/kexifilterdlg.cpp new file mode 100644 index 00000000..6f847e61 --- /dev/null +++ b/kexi/widget/kexifilterdlg.cpp @@ -0,0 +1,149 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Lucijan Busch <lucijan@kde.org> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include <qlistview.h> +#include <qpushbutton.h> +#include <qlayout.h> +#include <qheader.h> +#include <qstringlist.h> + +#include "kexiproject.h" +#include "kexiprojecthandler.h" +#include "kexiprojecthandleritem.h" +#include "kexidataprovider.h" +#include "kexifilterdlg.h" +#include "kexiquerydesignersqleditor.h" + +KexiFilterDlg::KexiFilterDlg(KexiProject *project, QWidget *parent, const char *name) + : QDialog(parent, name) +{ + m_project = project; + + QHBoxLayout *lbraces = new QHBoxLayout(0, 0, 4); + + QPushButton *bsBO = createMiniButton("["); + QPushButton *bBO = createMiniButton("("); + QPushButton *bBC = createMiniButton(")"); + QPushButton *bsBC = createMiniButton("]"); + lbraces->addWidget(bsBO); + lbraces->addWidget(bBO); + lbraces->addWidget(bBC); + lbraces->addWidget(bsBC); + + QHBoxLayout *lcond = new QHBoxLayout(0, 0, 4); + QPushButton *blt = createMiniButton("<"); + QPushButton *beq = createMiniButton("="); + QPushButton *bgt = createMiniButton(">"); + QPushButton *bp = createMiniButton("%"); + lcond->addWidget(blt); + lcond->addWidget(beq); + lcond->addWidget(bgt); + lcond->addWidget(bp); + + QHBoxLayout *lbool = new QHBoxLayout(0, 0, 4); + QPushButton *bAnd = new QPushButton("AND", this); + bAnd->setFlat(true); + QPushButton *bOr = new QPushButton("OR", this); + bOr->setFlat(true); + QPushButton *bLike = new QPushButton("LIKE", this); + bLike->setFlat(true); + lbool->addWidget(bLike); + lbool->addWidget(bAnd); + lbool->addWidget(bOr); + + m_catalog = new QListView(this); + m_catalog->addColumn("a"); + m_catalog->header()->hide(); + + KexiQueryDesignerSQLEditor *e = new KexiQueryDesignerSQLEditor(this); + + setupCatalog(QString("kexi/table")); + + QGridLayout *g = new QGridLayout(this); + g->setSpacing(6); + g->addMultiCellWidget(e, 0, 0, 0, 2); + g->addItem(lbraces, 1, 0); + g->addItem(lcond, 1, 1); + g->addItem(lbool, 1, 2); + g->addMultiCellWidget(m_catalog, 2, 2, 0, 2); +} + +QPushButton* +KexiFilterDlg::createMiniButton(const QString &text) +{ + QPushButton *p = new QPushButton(text, this); + p->setFlat(true); + p->setMaximumSize(QSize(20, 300)); + + return p; +} + +void +KexiFilterDlg::setupCatalog(const QStringList &mimes) +{ + m_catalog->clear(); + m_catalog->setRootIsDecorated(true); + QStringList::ConstIterator it, end( mimes.constEnd() ); + for( it = mimes.constBegin(); it != end; ++it) + { + KexiProjectHandler *h = m_project->handlerForMime(*it); + if(h) + { + QListViewItem *base = new QListViewItem(m_catalog, h->name()); + base->setPixmap(0, h->groupPixmap()); + + QDictIterator<KexiProjectHandlerItem> iit(*h->items()); // See QDictIterator + for(; iit.current(); ++iit ) + { + QListViewItem *bi = new QListViewItem(base, iit.current()->name()); + bi->setPixmap(0, h->itemPixmap()); + + KexiDataProvider *prov=KEXIDATAPROVIDER(h); + if(prov) + { + QStringList fields = prov->fields(0, iit.current()->identifier()); + QStringList::ConstIterator fit, end( fields.constEnd() ); + for( fit = fields.constBegin(); fit != end; ++fit) + { + QListViewItem *bif = new QListViewItem(bi, (*fit)); + } + } + } + } + } +} + +void +KexiFilterDlg::setupCatalog(const QString &mime) +{ + QStringList l; + l.append(mime); + setupCatalog(l); +} + +void +KexiFilterDlg::insert(QListViewItem *) +{ +} + +KexiFilterDlg::~KexiFilterDlg() +{ +} + +#include "kexifilterdlg.moc" diff --git a/kexi/widget/kexifilterdlg.h b/kexi/widget/kexifilterdlg.h new file mode 100644 index 00000000..e5544888 --- /dev/null +++ b/kexi/widget/kexifilterdlg.h @@ -0,0 +1,50 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Lucijan Busch <lucijan@kde.org> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIFILTERDLG_H +#define KEXIFILTERDLG_H + +#include <qdialog.h> + +class QPushButton; +class QListView; +class QListViewItem; +class KexiProject; + +class KEXIEXTWIDGETS_EXPORT KexiFilterDlg : public QDialog +{ + Q_OBJECT + + public: + KexiFilterDlg(KexiProject *p, QWidget *parent=0, const char *name=0); + ~KexiFilterDlg(); + + QPushButton *createMiniButton(const QString &text); + void setupCatalog(const QStringList &mimes); + void setupCatalog(const QString &mime); + + protected slots: + void insert(QListViewItem *); + + protected: + QListView *m_catalog; + KexiProject *m_project; +}; + +#endif diff --git a/kexi/widget/kexiprjtypeselector.cpp b/kexi/widget/kexiprjtypeselector.cpp new file mode 100644 index 00000000..71dafc6c --- /dev/null +++ b/kexi/widget/kexiprjtypeselector.cpp @@ -0,0 +1,44 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "kexiprjtypeselector.h" +#include <qlabel.h> +#include <kiconloader.h> +#include <kmimetype.h> +#include <kexidb/driver.h> + +KexiPrjTypeSelector::KexiPrjTypeSelector( QWidget* parent ) + : KexiPrjTypeSelectorBase( parent, "KexiPrjTypeSelector" ) +{ + QString none; + icon_file->setPixmap( + KGlobal::iconLoader()->loadIcon( KMimeType::mimeType( + KexiDB::Driver::defaultFileBasedDriverMimeType() )->icon(none,0), KIcon::Desktop, 48 + ) + ); + icon_file->setFixedSize(icon_file->pixmap()->size()/2); + icon_server->setPixmap( DesktopIcon("network", 48) ); + icon_server->setFixedSize(icon_server->pixmap()->size()/2); +} + +KexiPrjTypeSelector::~KexiPrjTypeSelector() +{ +} + +#include "kexiprjtypeselector.moc" diff --git a/kexi/widget/kexiprjtypeselector.h b/kexi/widget/kexiprjtypeselector.h new file mode 100644 index 00000000..efb8a294 --- /dev/null +++ b/kexi/widget/kexiprjtypeselector.h @@ -0,0 +1,38 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef KEXIPRJTYPESELECTOR_H +#define KEXIPRJTYPESELECTOR_H + +#include "kexiprjtypeselectorbase.h" + +//! @short A simple widget with radio buttons with "show file/server-based projects" options +class KEXIEXTWIDGETS_EXPORT KexiPrjTypeSelector : public KexiPrjTypeSelectorBase +{ + Q_OBJECT + + public: + KexiPrjTypeSelector( QWidget* parent = 0 ); + ~KexiPrjTypeSelector(); + + protected slots: + virtual void languageChange() { KexiPrjTypeSelectorBase::languageChange(); } +}; + +#endif // KEXIPRJTYPESELECTOR_H diff --git a/kexi/widget/kexiprjtypeselectorbase.ui b/kexi/widget/kexiprjtypeselectorbase.ui new file mode 100644 index 00000000..a8031909 --- /dev/null +++ b/kexi/widget/kexiprjtypeselectorbase.ui @@ -0,0 +1,162 @@ +<!DOCTYPE UI><UI version="3.3" stdsetdef="1"> +<class>KexiPrjTypeSelectorBase</class> +<widget class="QWidget"> + <property name="name"> + <cstring>KexiPrjTypeSelectorBase</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>254</width> + <height>61</height> + </rect> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <widget class="QButtonGroup"> + <property name="name"> + <cstring>buttonGroup</cstring> + </property> + <property name="frameShape"> + <enum>NoFrame</enum> + </property> + <property name="frameShadow"> + <enum>Plain</enum> + </property> + <property name="lineWidth"> + <number>0</number> + </property> + <property name="title"> + <string></string> + </property> + <property name="selectedId" stdset="0"> + <number>1</number> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <widget class="QRadioButton" row="1" column="1"> + <property name="name"> + <cstring>option_server</cstring> + </property> + <property name="text"> + <string>Projects stored on a database server</string> + </property> + <property name="buttonGroupId"> + <number>2</number> + </property> + </widget> + <widget class="QRadioButton" row="0" column="1"> + <property name="name"> + <cstring>option_file</cstring> + </property> + <property name="text"> + <string>Projects stored in a file</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + <property name="buttonGroupId"> + <number>1</number> + </property> + </widget> + <widget class="QLabel" row="0" column="0"> + <property name="name"> + <cstring>icon_file</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string></string> + </property> + <property name="scaledContents"> + <bool>true</bool> + </property> + <property name="alignment"> + <set>AlignVCenter|AlignLeft</set> + </property> + </widget> + <widget class="QLabel" row="1" column="0"> + <property name="name"> + <cstring>icon_server</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string></string> + </property> + <property name="scaledContents"> + <bool>true</bool> + </property> + <property name="alignment"> + <set>AlignVCenter|AlignLeft</set> + </property> + </widget> + <widget class="QFrame" row="2" column="1"> + <property name="name"> + <cstring>frame_server</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>1</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="frameShape"> + <enum>NoFrame</enum> + </property> + <property name="frameShadow"> + <enum>Plain</enum> + </property> + </widget> + </grid> + </widget> + </vbox> +</widget> +<connections> + <connection> + <sender>buttonGroup</sender> + <signal>clicked(int)</signal> + <receiver>KexiPrjTypeSelectorBase</receiver> + <slot>slotSelectionChanged(int)</slot> + </connection> +</connections> +<tabstops> + <tabstop>option_file</tabstop> + <tabstop>option_server</tabstop> +</tabstops> +<includes> + <include location="local" impldecl="in implementation">kexiprjtypeselectorbase.ui.h</include> +</includes> +<slots> + <slot access="protected" specifier="non virtual">slotSelectionChanged( int id )</slot> +</slots> +<functions> + <function access="private" specifier="non virtual">init()</function> +</functions> +<layoutdefaults spacing="6" margin="11"/> +</UI> diff --git a/kexi/widget/kexiprjtypeselectorbase.ui.h b/kexi/widget/kexiprjtypeselectorbase.ui.h new file mode 100644 index 00000000..03f1301f --- /dev/null +++ b/kexi/widget/kexiprjtypeselectorbase.ui.h @@ -0,0 +1,23 @@ +/**************************************************************************** +** ui.h extension file, included from the uic-generated form implementation. +** +** If you want to add, delete, or rename functions or slots, use +** Qt Designer to update this file, preserving your code. +** +** You should not define a constructor or destructor in this file. +** Instead, write your code in functions called init() and destroy(). +** These will automatically be called by the form's constructor and +** destructor. +*****************************************************************************/ + + +void KexiPrjTypeSelectorBase::init() +{ + slotSelectionChanged( 1 ); +} + + +void KexiPrjTypeSelectorBase::slotSelectionChanged( int id ) +{ + frame_server->setEnabled(id==2); +} diff --git a/kexi/widget/kexipropertyeditorview.cpp b/kexi/widget/kexipropertyeditorview.cpp new file mode 100644 index 00000000..5225a7af --- /dev/null +++ b/kexi/widget/kexipropertyeditorview.cpp @@ -0,0 +1,223 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr> + Copyright (C) 2004-2006 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "kexipropertyeditorview.h" +#include "keximainwindow.h" +#include <koproperty/set.h> +#include <koproperty/editor.h> +#include <koproperty/property.h> + +#include <klocale.h> +#include <kiconloader.h> + +#include <qlayout.h> +#include <qlabel.h> + +KexiObjectInfoLabel::KexiObjectInfoLabel(QWidget* parent, const char* name) + : QWidget(parent, name) +{ + QHBoxLayout *hlyr = new QHBoxLayout(this); + m_objectIconLabel = new QLabel(this); + m_objectIconLabel->setMargin(2); + setFixedHeight( IconSize(KIcon::Small) + 2 + 2 ); + hlyr->addWidget(m_objectIconLabel); + m_objectNameLabel = new QLabel(this); + m_objectNameLabel->setMargin(2); + m_objectNameLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); + hlyr->addWidget(m_objectNameLabel); +} + +KexiObjectInfoLabel::~KexiObjectInfoLabel() +{ +} + +void KexiObjectInfoLabel::setObjectClassIcon(const QString& name) +{ + m_classIcon = name; + if (m_classIcon.isEmpty()) + m_objectIconLabel->setFixedWidth( 0 ); + else + m_objectIconLabel->setFixedWidth( IconSize(KIcon::Small) + 2 + 2 ); + m_objectIconLabel->setPixmap( SmallIcon(name) ); +} + +void KexiObjectInfoLabel::setObjectClassName(const QString& name) +{ + m_className = name; + updateName(); +} + +void KexiObjectInfoLabel::setObjectName(const QString& name) +{ + m_objectName = name; + updateName(); +} + +void KexiObjectInfoLabel::updateName() +{ + QString txt( m_className ); + if (txt.isEmpty()) + txt = m_objectName; + else if (!m_objectName.isEmpty()) + txt += QString(" \"%1\"").arg(m_objectName); + m_objectNameLabel->setText(txt); +} + +void KexiObjectInfoLabel::setBuddy( QWidget * buddy ) +{ + m_objectNameLabel->setBuddy(buddy); +} + +//------------------------------ + +//! @internal +class KexiPropertyEditorView::Private +{ + public: + Private() + { + } + KoProperty::Editor *editor; +// QLabel *objectIcon; +// QString iconName; +// QLabel *objectClassName; + KexiObjectInfoLabel *objectInfoLabel; +}; + +//------------------------------ + +KexiPropertyEditorView::KexiPropertyEditorView(KexiMainWindow *mainWin, QWidget* parent) + : QWidget(parent, "KexiPropertyEditorView") + , d(new Private()) +{ + setCaption(i18n("Properties")); + //TODO: set a nice icon + setIcon(*mainWin->icon()); + + QVBoxLayout *lyr = new QVBoxLayout(this); + + //add object class info + d->objectInfoLabel = new KexiObjectInfoLabel(this, "KexiObjectInfoLabel"); + lyr->addWidget(d->objectInfoLabel); + + /* + QHBoxLayout *vlyr = new QHBoxLayout(lyr); + d->objectIcon = new QLabel(this); + d->objectIcon->setMargin(2); + d->objectIcon->setFixedHeight( IconSize(KIcon::Small) + 2 + 2 ); + vlyr->addWidget(d->objectIcon); + d->objectClassName = new QLabel(this); + d->objectClassName->setMargin(2); + d->objectClassName->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); + vlyr->addWidget(d->objectClassName);*/ + + d->editor = new KoProperty::Editor(this, true /*AutoSync*/, "propeditor"); + lyr->addWidget(d->editor); + setFocusProxy(d->editor); + d->objectInfoLabel->setBuddy(d->editor); + setFocusPolicy(WheelFocus); + + connect(d->editor, SIGNAL(propertySetChanged(KoProperty::Set*)), + this, SLOT(slotPropertySetChanged(KoProperty::Set*))); + +// d->iconName = "dummy"; + slotPropertySetChanged(0); +} + +KexiPropertyEditorView::~KexiPropertyEditorView() +{ + delete d; +} + +QSize KexiPropertyEditorView::sizeHint() const +{ + return QSize(200,200);//m_editor->sizeHint(); +} + +QSize KexiPropertyEditorView::minimumSizeHint() const +{ + return QSize(200,200);//m_editor->sizeHint(); +} + +/*void KexiPropertyEditorView::setGeometry ( const QRect &r ) +{ + QWidget::setGeometry(r); +} + +void KexiPropertyEditorView::resize ( int w, int h ) +{ + QWidget::resize( w, h ); +}*/ + +KoProperty::Editor *KexiPropertyEditorView::editor() const +{ + return d->editor; +} + +/*! Updates \a infoLabel widget by reusing properties provided by property set \a set. + Read documentation of KexiPropertyEditorView class for information about accepted properties. + If \a set is 0 and \a textToDisplayForNullSet string is not empty, this string is displayed + (without icon or any other additional part). + If \a set is 0 and \a textToDisplayForNullSet string is empty, the \a infoLabel widget becomes + hidden. +*/ +void KexiPropertyEditorView::updateInfoLabelForPropertySet(KexiObjectInfoLabel *infoLabel, + KoProperty::Set* set, const QString& textToDisplayForNullSet) +{ + QString className, iconName, objectName; + if (set) { + if (set->contains("this:classString")) + className = (*set)["this:classString"].value().toString(); + if (set->contains("this:iconName")) + iconName = (*set)["this:iconName"].value().toString(); + const bool useCaptionAsObjectName = set->contains("this:useCaptionAsObjectName") + && (*set)["this:useCaptionAsObjectName"].value().toBool(); + if (set->contains(useCaptionAsObjectName ? "caption" : "name")) + objectName = (*set)[useCaptionAsObjectName ? "caption" : "name"].value().toString(); + } + if (!set || objectName.isEmpty()) { + objectName = textToDisplayForNullSet; + className = QString::null; + iconName = QString::null; + } + + if (className.isEmpty() && objectName.isEmpty()) + infoLabel->hide(); + else + infoLabel->show(); + + if (infoLabel->objectClassName() == className + && infoLabel->objectClassIcon() == iconName + && infoLabel->objectName() == objectName) + return; + + infoLabel->setObjectClassIcon(iconName); + infoLabel->setObjectClassName(className); + infoLabel->setObjectName(objectName); +} + +void KexiPropertyEditorView::slotPropertySetChanged(KoProperty::Set* set) +{ + //update information about selected object + updateInfoLabelForPropertySet(d->objectInfoLabel, set); + d->editor->setEnabled(set); +} + +#include "kexipropertyeditorview.moc" diff --git a/kexi/widget/kexipropertyeditorview.h b/kexi/widget/kexipropertyeditorview.h new file mode 100644 index 00000000..77dab6c8 --- /dev/null +++ b/kexi/widget/kexipropertyeditorview.h @@ -0,0 +1,117 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr> + Copyright (C) 2004-2006 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIPROPERTYEDITORVIEW_H +#define KEXIPROPERTYEDITORVIEW_H + +//#include "kexiviewbase.h" +#include <qwidget.h> + +class QLabel; +class KexiMainWindow; + +namespace KoProperty { + class Editor; + class Set; +} + +//! @short Helper class displaying small icon with class name and object name +/*! The info label is displayed in a form: + <i>[ObjectClassIcon] ClassName "ObjectName"</i> + + The <i>ObjectClassIcon</i> is optional. If "ClassName" is empty, the information + is displayed as: + <i>[ObjectClassIcon] ObjectName</i> + + Example uses: + - [button_icon] Button "quit" + - [label_icon] Label "welcome" +*/ +class KEXIEXTWIDGETS_EXPORT KexiObjectInfoLabel : public QWidget +{ + public: + KexiObjectInfoLabel(QWidget* parent, const char* name = 0); + ~KexiObjectInfoLabel(); + + void setObjectClassIcon(const QString& name); + QString objectClassIcon() const { return m_classIcon; } + void setObjectClassName(const QString& name); + QString objectClassName() const { return m_className; } + void setObjectName(const QString& name); + QString objectName() const { return m_objectName; } + void setBuddy( QWidget * buddy ); + protected: + void updateName(); + + QString m_className; + QString m_classIcon, m_objectName; + QLabel *m_objectIconLabel, *m_objectNameLabel; +}; + +//! @short The container (acts as a dock window) for KexiPropertyEditor. +/*! The widget displays KexiObjectInfoLabel on its top, to show user what + object the properties belong to. Read KexiObjectInfoLabel documentation for + the description what information is displayed. + + There are properties obtained from KexiMainWindow's current property set + that help to customize displaying this information: + - "this:classString property" of type string describes object's class name + - "this:iconName" property of type string describes class name + - "name" or "caption" property of type string describes object's name + - "this:useCaptionAsObjectName" property of type boolean forces displaying "caption" + property instead of "name" - this can be usable when we know that "caption" properties + are available for a given type of objects (this is the case for Table Designer fields) +*/ +class KEXIEXTWIDGETS_EXPORT KexiPropertyEditorView : public QWidget +{ + Q_OBJECT + + public: + KexiPropertyEditorView(KexiMainWindow *mainWin, QWidget* parent); + virtual ~KexiPropertyEditorView(); + + /*! Helper function. Updates \a infoLabel widget by reusing properties provided + by property set \a set. + Read documentation of KexiPropertyEditorView class for information about accepted properties. + If \a set is 0 and \a textToDisplayForNullSet string is not empty, this string is displayed + (without icon or any other additional part). + If \a set is 0 and \a textToDisplayForNullSet string is empty, the \a infoLabel widget becomes + hidden. */ + static void updateInfoLabelForPropertySet( + KexiObjectInfoLabel *infoLabel, KoProperty::Set* set, + const QString& textToDisplayForNullSet = QString::null); + + virtual QSize sizeHint() const; + virtual QSize minimumSizeHint() const; + KoProperty::Editor *editor() const; + +// public slots: +// virtual void setGeometry( const QRect &r ); +// virtual void resize( int w, int h ); + + protected slots: + void slotPropertySetChanged(KoProperty::Set* ); + + protected: + class Private; + Private *d; +}; + +#endif diff --git a/kexi/widget/kexiquerydesignersqleditor.cpp b/kexi/widget/kexiquerydesignersqleditor.cpp new file mode 100644 index 00000000..3ff57989 --- /dev/null +++ b/kexi/widget/kexiquerydesignersqleditor.cpp @@ -0,0 +1,35 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Lucijan Busch <lucijan@gmx.at> + Copyright (C) 2004-2005 Jaroslaw Staniek <js@iidea.pl> + Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "kexiquerydesignersqleditor.h" + +KexiQueryDesignerSQLEditor::KexiQueryDesignerSQLEditor( + KexiMainWindow *mainWin, QWidget *parent, const char *name) + : KexiEditor(mainWin, parent, name) +{ + setHighlightMode("sql"); +} + +KexiQueryDesignerSQLEditor::~KexiQueryDesignerSQLEditor() +{ +} + +#include "kexiquerydesignersqleditor.moc" diff --git a/kexi/widget/kexiquerydesignersqleditor.h b/kexi/widget/kexiquerydesignersqleditor.h new file mode 100644 index 00000000..a34dbc7a --- /dev/null +++ b/kexi/widget/kexiquerydesignersqleditor.h @@ -0,0 +1,39 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Lucijan Busch <lucijan@gmx.at> + Copyright (C) 2004-2005 Jaroslaw Staniek <js@iidea.pl> + Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIQUERYDESIGNERSQLEDITOR_H +#define KEXIQUERYDESIGNERSQLEDITOR_H + +#include "kexieditor.h" + +//! Text editor for entering query statements. +/*! The KexiQueryDesignerSQLEditor class embeds text editor + for entering query statements. */ +class KEXIEXTWIDGETS_EXPORT KexiQueryDesignerSQLEditor : public KexiEditor +{ + Q_OBJECT + + public: + KexiQueryDesignerSQLEditor(KexiMainWindow *mainWin, QWidget *parent, const char *name = 0); + virtual ~KexiQueryDesignerSQLEditor(); +}; + +#endif diff --git a/kexi/widget/kexiqueryparameters.cpp b/kexi/widget/kexiqueryparameters.cpp new file mode 100644 index 00000000..449c265c --- /dev/null +++ b/kexi/widget/kexiqueryparameters.cpp @@ -0,0 +1,139 @@ +/* This file is part of the KDE project + Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl> + + 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 Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "kexiqueryparameters.h" + +#include <kdebug.h> +#include <klocale.h> +#include <kinputdialog.h> +#include <knumvalidator.h> + +#include <kexidb/queryschemaparameter.h> +#include <kexidb/utils.h> +#include "utils/kexidatetimeformatter.h" + +//static +QValueList<QVariant> KexiQueryParameters::getParameters(QWidget *parent, + const KexiDB::Driver &driver, KexiDB::QuerySchema& querySchema, bool &ok) +{ + Q_UNUSED(driver); + ok = false; + const KexiDB::QuerySchemaParameterList params( querySchema.parameters() ); + QValueList<QVariant> values; + const QString caption( i18n("Enter Query Parameter Value", "Enter Parameter Value") ); + foreach(KexiDB::QuerySchemaParameterListConstIterator, it, params) { + switch ((*it).type) { + case KexiDB::Field::Byte: + case KexiDB::Field::ShortInteger: + case KexiDB::Field::Integer: + case KexiDB::Field::BigInteger: { +//! @todo problem for ranges in case of BigInteger - will disappear when we remove use of KInputDialog + int minValue, maxValue; +//! @todo add support for unsigned parameter here + KexiDB::getLimitsForType((*it).type, minValue, maxValue); + const int result = KInputDialog::getInteger( + caption, (*it).message, 0, minValue, maxValue, 1/*step*/, 10/*base*/, &ok, parent); + if (!ok) + return QValueList<QVariant>(); //cancelled + values.append(result); + break; + } + case KexiDB::Field::Boolean: { + QStringList list; + list << i18n("Boolean True - Yes", "Yes") << i18n("Boolean False - No", "No"); + const QString result = KInputDialog::getItem( + caption, (*it).message, list, 0/*current*/, false /*!editable*/, &ok, parent); + if (!ok || result.isEmpty()) + return QValueList<QVariant>(); //cancelled + values.append( QVariant( result==list.first(), 1 ) ); + break; + } + case KexiDB::Field::Date: { + KexiDateFormatter df; + const QString result = KInputDialog::getText( + caption, (*it).message, QString::null, &ok, parent, 0/*name*/, +//! @todo add validator + 0/*validator*/, df.inputMask() ); + if (!ok) + return QValueList<QVariant>(); //cancelled + values.append( df.stringToDate(result) ); + break; + } + case KexiDB::Field::DateTime: { + KexiDateFormatter df; + KexiTimeFormatter tf; + const QString result = KInputDialog::getText( + caption, (*it).message, QString::null, &ok, parent, 0/*name*/, +//! @todo add validator + 0/*validator*/, dateTimeInputMask(df, tf) ); + if (!ok) + return QValueList<QVariant>(); //cancelled + values.append( stringToDateTime(df, tf, result) ); + break; + } + case KexiDB::Field::Time: { + KexiTimeFormatter tf; + const QString result = KInputDialog::getText( + caption, (*it).message, QString::null, &ok, parent, 0/*name*/, +//! @todo add validator + 0/*validator*/, tf.inputMask() ); + if (!ok) + return QValueList<QVariant>(); //cancelled + values.append( tf.stringToTime(result) ); + break; + } + case KexiDB::Field::Float: + case KexiDB::Field::Double: { + // KInputDialog::getDouble() does not work well, use getText and double validator + KDoubleValidator validator(0); + const QString textResult = KInputDialog::getText( caption, (*it).message, QString::null, &ok, + parent, 0, &validator); + if (!ok || textResult.isEmpty()) + return QValueList<QVariant>(); //cancelled +//! @todo this value will be still rounded: consider storing them as a decimal type +//! (e.g. using a special Q_LLONG+decimalplace class) + const double result = textResult.toDouble(&ok); //this is also good for float (to avoid rounding) + if (!ok) + return QValueList<QVariant>(); + values.append( result ); + break; + } + case KexiDB::Field::Text: + case KexiDB::Field::LongText: { + const QString result = KInputDialog::getText( + caption, (*it).message, QString::null, &ok, parent); + if (!ok) + return QValueList<QVariant>(); //cancelled + values.append( result ); + break; + } + case KexiDB::Field::BLOB: { +//! @todo BLOB input unsupported + values.append( QByteArray() ); + } + default: + kexiwarn << "KexiQueryParameters::getParameters() unsupported type " << KexiDB::Field::typeName((*it).type) + << " for parameter \"" << (*it).message << "\" - aborting query execution!" << endl; + return QValueList<QVariant>(); + } + } + ok = true; + return values; +} + diff --git a/kexi/widget/kexiqueryparameters.h b/kexi/widget/kexiqueryparameters.h new file mode 100644 index 00000000..40251b71 --- /dev/null +++ b/kexi/widget/kexiqueryparameters.h @@ -0,0 +1,44 @@ +/* This file is part of the KDE project + Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl> + + 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 Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIQUERYPARAMETERS_H +#define KEXIQUERYPARAMETERS_H + +#include <kexidb/queryschema.h> + +//! @short Utilities providing GUI for getting query parameters +class KEXIEXTWIDGETS_EXPORT KexiQueryParameters +{ + public: + /*! Asks for query parameters using KInputDialog, one dialog per query parameter + (see @ref KexiDB::QuerySchema::parameters()). The type of each dialog depends + on the type of query parameter. + \return list of values obtained from the user + \a ok is set to true on success and to false on failure. */ + //! @todo do not use KInputDialog - switch to more powerful custom dialog + //! @todo offer option to display one dialog (form) with all the parameters + //! @todo support more types (using validators) + //! @todo support defaults + //! @todo support validation rules, e.g. min/max value, unsigned + //! @todo support Enum type (list of strings, need support for keys and user-visible strings) + static QValueList<QVariant> getParameters(QWidget *parent, const KexiDB::Driver &driver, + KexiDB::QuerySchema& querySchema, bool &ok); +}; + +#endif // KEXIDBCONNECTIONWIDGET_H diff --git a/kexi/widget/kexiscrollview.cpp b/kexi/widget/kexiscrollview.cpp new file mode 100644 index 00000000..1cf47378 --- /dev/null +++ b/kexi/widget/kexiscrollview.cpp @@ -0,0 +1,407 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr> + Copyright (C) 2004 Jaroslaw Staniek <js@iidea.pl> + + 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 Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ +#include "kexiscrollview.h" + +#include <qcursor.h> +#include <qobjectlist.h> +#include <qpainter.h> +#include <qpixmap.h> + +#include <kdebug.h> +#include <kstaticdeleter.h> +#include <klocale.h> + +#include <utils/kexirecordnavigator.h> +#include <core/kexi.h> +#include <kexiutils/utils.h> + +//! @internal +class KexiScrollViewData +{ + public: + QPixmap horizontalOuterAreaPixmapBuffer; + QPixmap verticalOuterAreaPixmapBuffer; +}; + +// @todo warning: not reentrant! +static KStaticDeleter<KexiScrollViewData> KexiScrollView_data_deleter; +KexiScrollViewData* KexiScrollView_data = 0; + +KexiScrollView::KexiScrollView(QWidget *parent, bool preview) + : QScrollView(parent, "kexiscrollview", WStaticContents) + , m_widget(0) + , m_helpFont(font()) + , m_preview(preview) + , m_scrollViewNavPanel(0) +{ + setFrameStyle(QFrame::WinPanel | QFrame::Sunken); + viewport()->setPaletteBackgroundColor(colorGroup().mid()); + QColor fc = palette().active().foreground(), + bc = viewport()->paletteBackgroundColor(); + m_helpColor = KexiUtils::blendedColors(fc, bc, 1, 2); +// m_helpColor = QColor((fc.red()+bc.red()*2)/3, (fc.green()+bc.green()*2)/3, +// (fc.blue()+bc.blue()*2)/3); + m_helpFont.setPointSize( m_helpFont.pointSize() * 3 ); + + setFocusPolicy(WheelFocus); + + //initial resize mode is always manual; + //will be changed on show(), if needed + setResizePolicy(Manual); + + viewport()->setMouseTracking(true); + m_resizing = false; + m_enableResizing = true; + m_snapToGrid = false; + m_gridSize = 0; + m_outerAreaVisible = true; + + connect(&m_delayedResize, SIGNAL(timeout()), this, SLOT(refreshContentsSize())); + m_smodeSet = false; + if (m_preview) { + refreshContentsSizeLater(true, true); +//! @todo allow to hide navigator + updateScrollBars(); + m_scrollViewNavPanel = new KexiRecordNavigator(this, leftMargin(), "nav"); + m_scrollViewNavPanel->setSizePolicy(QSizePolicy::Minimum,QSizePolicy::Preferred); + } +} + +KexiScrollView::~KexiScrollView() +{ +} + +void +KexiScrollView::setWidget(QWidget *w) +{ + addChild(w); + m_widget = w; +} + +void +KexiScrollView::setRecordNavigatorVisible(bool visible) +{ + if(/*m_scrollViewNavPanel->isVisible() &&*/ !visible) + m_scrollViewNavPanel->hide(); + else if(visible) { + m_scrollViewNavPanel->show(); + updateNavPanelGeometry(); + } +} + +void +KexiScrollView::setSnapToGrid(bool enable, int gridSize) +{ + m_snapToGrid = enable; + if(enable) { + m_gridSize = gridSize; + } +} + +void +KexiScrollView::refreshContentsSizeLater(bool horizontal, bool vertical) +{ + Q_UNUSED( horizontal ); + Q_UNUSED( vertical ); + + if (!m_smodeSet) { + m_smodeSet = true; + m_vsmode = vScrollBarMode(); + m_hsmode = hScrollBarMode(); + } +// if (vertical) + setVScrollBarMode(QScrollView::AlwaysOff); + //if (horizontal) + setHScrollBarMode(QScrollView::AlwaysOff); + updateScrollBars(); + m_delayedResize.start( 100, true ); +} + +void +KexiScrollView::refreshContentsSize() +{ + if(!m_widget) + return; + if (m_preview) { + resizeContents(m_widget->width(), m_widget->height()); +// kdDebug() << "KexiScrollView::refreshContentsSize(): ( " + // << m_widget->width() <<", "<< m_widget->height() << endl; + setVScrollBarMode(m_vsmode); + setHScrollBarMode(m_hsmode); + m_smodeSet = false; + updateScrollBars(); + } + else { + // Ensure there is always space to resize Form + int w = contentsWidth(), h = contentsHeight(); + bool change = false; + const int delta_x = QMAX( (KexiScrollView_data ? + KexiScrollView_data->verticalOuterAreaPixmapBuffer.width() : 0), 300); + const int delta_y = QMAX( (KexiScrollView_data ? + KexiScrollView_data->horizontalOuterAreaPixmapBuffer.height() : 0), 300); + if((m_widget->width() + delta_x * 2 / 3) > w) { + w = m_widget->width() + delta_x; + change = true; + } + else if((w - m_widget->width()) > delta_x) { + w = m_widget->width() + delta_x; + change = true; + } + if((m_widget->height() + delta_y * 2 / 3) > h) { + h = m_widget->height() + delta_y; + change = true; + } + else if((h - m_widget->height()) > delta_y) { + h = m_widget->height() + delta_y; + change = true; + } + if (change) { + repaint(); + viewport()->repaint(); + repaintContents(); + updateContents(0, 0, 2000,2000); + clipper()->repaint(); + + resizeContents(w, h); + } +// kdDebug() << "KexiScrollView::refreshContentsSize(): ( " + // << contentsWidth() <<", "<< contentsHeight() << endl; + updateScrollBars(); + setVScrollBarMode(Auto); + setHScrollBarMode(Auto); + } + updateContents(); + updateScrollBars(); +} + +void +KexiScrollView::updateNavPanelGeometry() +{ + if (m_scrollViewNavPanel) + m_scrollViewNavPanel->updateGeometry(leftMargin()); +} + +void +KexiScrollView::contentsMousePressEvent(QMouseEvent *ev) +{ + if(!m_widget) + return; + + QRect r3(0, 0, m_widget->width() + 4, m_widget->height() + 4); + if(!r3.contains(ev->pos())) // clicked outside form + //m_form->resetSelection(); + emit outerAreaClicked(); + + if(!m_enableResizing) + return; + + QRect r(m_widget->width(), 0, 4, m_widget->height() + 4); // right limit + QRect r2(0, m_widget->height(), m_widget->width() + 4, 4); // bottom limit + if(r.contains(ev->pos()) || r2.contains(ev->pos())) + { + m_resizing = true; + emit resizingStarted(); + } +} + +void +KexiScrollView::contentsMouseReleaseEvent(QMouseEvent *) +{ + if(m_resizing) { + m_resizing = false; + emit resizingEnded(); + } + + unsetCursor(); +} + +void +KexiScrollView::contentsMouseMoveEvent(QMouseEvent *ev) +{ + if(!m_widget || !m_enableResizing) + return; + + if(m_resizing) // resize widget + { + int tmpx = ev->x(), tmpy = ev->y(); + const int exceeds_x = (tmpx - contentsX() + 5) - clipper()->width(); + const int exceeds_y = (tmpy - contentsY() + 5) - clipper()->height(); + if (exceeds_x > 0) + tmpx -= exceeds_x; + if (exceeds_y > 0) + tmpy -= exceeds_y; + if ((tmpx - contentsX()) < 0) + tmpx = contentsX(); + if ((tmpy - contentsY()) < 0) + tmpy = contentsY(); + + // we look for the max widget right() (or bottom()), which would be the limit for form resizing (not to hide widgets) + QObjectList *list = m_widget->queryList("QWidget", 0, true, false /* not recursive*/); + for(QObject *o = list->first(); o; o = list->next()) + { + QWidget *w = (QWidget*)o; + tmpx = QMAX(tmpx, (w->geometry().right() + 10)); + tmpy = QMAX(tmpy, (w->geometry().bottom() + 10)); + } + delete list; + + int neww = -1, newh; + if(cursor().shape() == QCursor::SizeHorCursor) + { + if(m_snapToGrid) + neww = int( float(tmpx) / float(m_gridSize) + 0.5 ) * m_gridSize; + else + neww = tmpx; + newh = m_widget->height(); + } + else if(cursor().shape() == QCursor::SizeVerCursor) + { + neww = m_widget->width(); + if(m_snapToGrid) + newh = int( float(tmpy) / float(m_gridSize) + 0.5 ) * m_gridSize; + else + newh = tmpy; + } + else if(cursor().shape() == QCursor::SizeFDiagCursor) + { + if(m_snapToGrid) { + neww = int( float(tmpx) / float(m_gridSize) + 0.5 ) * m_gridSize; + newh = int( float(tmpy) / float(m_gridSize) + 0.5 ) * m_gridSize; + } else { + neww = tmpx; + newh = tmpy; + } + } + //needs update? + if (neww!=-1 && m_widget->size() != QSize(neww, newh)) { + m_widget->resize( neww, newh ); + refreshContentsSize(); + updateContents(); + } + } + else // update mouse cursor + { + QPoint p = ev->pos(); + QRect r(m_widget->width(), 0, 4, m_widget->height()); // right + QRect r2(0, m_widget->height(), m_widget->width(), 4); // bottom + QRect r3(m_widget->width(), m_widget->height(), 4, 4); // bottom-right corner + + if(r.contains(p)) + setCursor(QCursor::SizeHorCursor); + else if(r2.contains(p)) + setCursor(QCursor::SizeVerCursor); + else if(r3.contains(p)) + setCursor(QCursor::SizeFDiagCursor); + else + unsetCursor(); + } +} + +void +KexiScrollView::setupPixmapBuffer(QPixmap& pixmap, const QString& text, int lines) +{ + Q_UNUSED( lines ); + + QFontMetrics fm(m_helpFont); + const int flags = Qt::AlignCenter|Qt::AlignTop; + QRect rect(fm.boundingRect(0,0,1000,1000,flags,text)); + const int txtw = rect.width(), txth = rect.height();//fm.width(text), txth = fm.height()*lines; + pixmap = QPixmap(txtw, txth); + if (!pixmap.isNull()) { + //create pixmap once + pixmap.fill( viewport()->paletteBackgroundColor() ); + QPainter pb(&pixmap, this); + pb.setPen(m_helpColor); + pb.setFont(m_helpFont); + pb.drawText(0, 0, txtw, txth, Qt::AlignCenter|Qt::AlignTop, text); + } +} + +void +KexiScrollView::drawContents( QPainter * p, int clipx, int clipy, int clipw, int cliph ) +{ + QScrollView::drawContents(p, clipx, clipy, clipw, cliph); + if (m_widget) { + if(m_preview || !m_outerAreaVisible) + return; + + //draw right and bottom borders + const int wx = childX(m_widget); + const int wy = childY(m_widget); + p->setPen(palette().active().foreground()); + p->drawLine(wx+m_widget->width(), wy, wx+m_widget->width(), wy+m_widget->height()); + p->drawLine(wx, wy+m_widget->height(), wx+m_widget->width(), wy+m_widget->height()); +//kdDebug() << "KexiScrollView::drawContents() " << wy+m_widget->height() << endl; + + if (!KexiScrollView_data) { + KexiScrollView_data_deleter.setObject( KexiScrollView_data, new KexiScrollViewData() ); + + //create flicker-less buffer + setupPixmapBuffer( KexiScrollView_data->horizontalOuterAreaPixmapBuffer, i18n("Outer Area"), 1 ); + setupPixmapBuffer( KexiScrollView_data->verticalOuterAreaPixmapBuffer, i18n("Outer\nArea"), 2 ); + } + if (!KexiScrollView_data->horizontalOuterAreaPixmapBuffer.isNull() + && !KexiScrollView_data->verticalOuterAreaPixmapBuffer.isNull() + && !m_delayedResize.isActive() /* only draw text if there's not pending delayed resize*/) + { + if (m_widget->height()>(KexiScrollView_data->verticalOuterAreaPixmapBuffer.height()+20)) { + p->drawPixmap( + QMAX( m_widget->width(), KexiScrollView_data->verticalOuterAreaPixmapBuffer.width() + 20 ) + 20, + QMAX( (m_widget->height() - KexiScrollView_data->verticalOuterAreaPixmapBuffer.height())/2, 20 ), + KexiScrollView_data->verticalOuterAreaPixmapBuffer + ); + } + p->drawPixmap( + QMAX( (m_widget->width() - KexiScrollView_data->horizontalOuterAreaPixmapBuffer.width())/2, 20 ), + QMAX( m_widget->height(), KexiScrollView_data->horizontalOuterAreaPixmapBuffer.height() + 20 ) + 20, + KexiScrollView_data->horizontalOuterAreaPixmapBuffer + ); + } + } +} + +void +KexiScrollView::leaveEvent( QEvent *e ) +{ + QWidget::leaveEvent(e); + m_widget->update(); //update form elements on too fast mouse move +} + +void +KexiScrollView::setHBarGeometry( QScrollBar & hbar, int x, int y, int w, int h ) +{ +/*todo*/ +// kdDebug(44021)<<"KexiScrollView::setHBarGeometry"<<endl; + if (m_scrollViewNavPanel && m_scrollViewNavPanel->isVisible()) { + m_scrollViewNavPanel->setHBarGeometry( hbar, x, y, w, h ); + } + else { + hbar.setGeometry( x, y, w, h ); + } +} + +KexiRecordNavigator* +KexiScrollView::recordNavigator() const +{ + return m_scrollViewNavPanel; +} + +#include "kexiscrollview.moc" + diff --git a/kexi/widget/kexiscrollview.h b/kexi/widget/kexiscrollview.h new file mode 100644 index 00000000..c313f2d7 --- /dev/null +++ b/kexi/widget/kexiscrollview.h @@ -0,0 +1,93 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr> + Copyright (C) 2004 Jaroslaw Staniek <js@iidea.pl> + + 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 Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXISCROLLVIEW_H +#define KEXISCROLLVIEW_H + +#include <qscrollview.h> +#include <qtimer.h> + +class QColor; +class QFont; +class KexiRecordNavigator; + +//! The scrollview which includes KexiDBForm +/*! It allows to resize its m_widget, following snapToGrid setting. + Its contents is resized so the widget can always be resized. */ +class KEXIEXTWIDGETS_EXPORT KexiScrollView : public QScrollView +{ + Q_OBJECT + + public: + KexiScrollView(QWidget *parent, bool preview); + virtual ~KexiScrollView(); + + void setWidget(QWidget *w); + void setSnapToGrid(bool enable, int gridSize=10); + + void setResizingEnabled(bool enabled) { m_enableResizing = enabled; } + void setRecordNavigatorVisible(bool visible); + + void setOuterAreaIndicatorVisible(bool visible) { m_outerAreaVisible = visible; } + + void refreshContentsSizeLater(bool horizontal, bool vertical); + void updateNavPanelGeometry(); + + KexiRecordNavigator* recordNavigator() const; + + inline bool preview() const { return m_preview; } + + public slots: + /*! Make sure there is a 300px margin around the form contents to allow resizing. */ + virtual void refreshContentsSize(); + + signals: + void outerAreaClicked(); + void resizingStarted(); + void resizingEnded(); + + protected: + virtual void contentsMousePressEvent(QMouseEvent * ev); + virtual void contentsMouseReleaseEvent(QMouseEvent * ev); + virtual void contentsMouseMoveEvent(QMouseEvent * ev); + virtual void drawContents( QPainter * p, int clipx, int clipy, int clipw, int cliph ); + virtual void leaveEvent( QEvent *e ); + virtual void setHBarGeometry( QScrollBar & hbar, int x, int y, int w, int h ); + void setupPixmapBuffer(QPixmap& pixmap, const QString& text, int lines); + + bool m_resizing; + bool m_enableResizing; + QWidget *m_widget; + + int m_gridSize; + QFont m_helpFont; + QColor m_helpColor; + QTimer m_delayedResize; + //! for refreshContentsSizeLater() + QScrollView::ScrollBarMode m_vsmode, m_hsmode; + bool m_snapToGrid : 1; + bool m_preview : 1; + bool m_smodeSet : 1; + bool m_outerAreaVisible : 1; + KexiRecordNavigator* m_scrollViewNavPanel; +}; + +#endif + diff --git a/kexi/widget/kexisectionheader.cpp b/kexi/widget/kexisectionheader.cpp new file mode 100644 index 00000000..42c5031e --- /dev/null +++ b/kexi/widget/kexisectionheader.cpp @@ -0,0 +1,162 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Jaroslaw Staniek <js@iidea.pl> + + 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 Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "kexisectionheader.h" +#include "kexiviewbase.h" +#include <kexiutils/utils.h> + +#include <qlabel.h> +#include <qlayout.h> +#include <qhbox.h> +#include <qtooltip.h> + +#include <kiconloader.h> +#include <kpushbutton.h> + +class KexiSectionHeader::BoxLayout : public QBoxLayout +{ + public: + BoxLayout( KexiSectionHeader* parent, Direction d, int margin = 0, + int spacing = -1, const char * name = 0 ); + virtual void addItem( QLayoutItem * item ); + QGuardedPtr<KexiViewBase> view; +}; + +//========================== + +//! @internal +class KexiSectionHeaderPrivate +{ + public: + KexiSectionHeaderPrivate() + { + } + + Qt::Orientation orientation; + QLabel *lbl; + KexiSectionHeader::BoxLayout *lyr; + QHBox *lbl_b; +}; + +//========================== + +KexiSectionHeader::KexiSectionHeader(const QString &caption, Orientation o, QWidget* parent ) + : QWidget(parent, "KexiSectionHeader") + , d( new KexiSectionHeaderPrivate() ) +{ + d->orientation = o; + d->lyr = new BoxLayout( this, d->orientation==Vertical ? QBoxLayout::TopToBottom : QBoxLayout::LeftToRight ); + d->lyr->setAutoAdd(true); + d->lbl_b = new QHBox(this); + d->lbl = new QLabel(QString(" ")+caption, d->lbl_b); + d->lbl->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); + d->lbl->setFocusPolicy(StrongFocus); + d->lbl->installEventFilter(this); + installEventFilter(this); + setCaption(caption); +} + +KexiSectionHeader::~KexiSectionHeader() +{ + delete d; +} + +void KexiSectionHeader::addButton(const QString& icon, const QString& toolTip, + const QObject * receiver, const char * member) +{ + KPushButton *btn = new KPushButton(d->lbl_b); + btn->setFlat(true); + btn->setFocusPolicy(NoFocus); + btn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); + if (receiver && member) { + connect(btn, SIGNAL(clicked()), receiver, member); + } + + if (!icon.isEmpty()) { + QIconSet iset = SmallIconSet(icon); + btn->setIconSet( iset ); + QFontMetrics fm(d->lbl->font()); + btn->setMaximumHeight( QMAX(fm.height(), 16) ); + } + if (!toolTip.isEmpty()) { + QToolTip::add(btn, toolTip); + } +} + +bool KexiSectionHeader::eventFilter( QObject *o, QEvent *e ) +{ + if (o == d->lbl && e->type()==QEvent::MouseButtonRelease) {//|| e->type()==QEvent::FocusOut) {// && o->inherits("QWidget")) { + if (d->lyr->view) + d->lyr->view->setFocus(); +// if (KexiUtils::hasParent( this, static_cast<QWidget*>(o))) { +// d->lbl->setPaletteBackgroundColor( e->type()==QEvent::FocusIn ? red : blue); +// } + } + return QWidget::eventFilter(o,e); +} + +void KexiSectionHeader::slotFocus(bool in) +{ + in = in || focusWidget()==this; + d->lbl->setPaletteBackgroundColor( + in ? palette().active().color(QColorGroup::Highlight) : palette().active().color(QColorGroup::Background) ); + d->lbl->setPaletteForegroundColor( + in ? palette().active().color(QColorGroup::HighlightedText) : palette().active().color(QColorGroup::Foreground) ); +} + +QSize KexiSectionHeader::sizeHint() const +{ + if (!d->lyr->view) + return QWidget::sizeHint(); + QSize s = d->lyr->view->sizeHint(); + return QSize(s.width(), d->lbl->sizeHint().height() + s.height()); +} + +/*void KexiSectionHeader::setFocus() +{ + if (d->lyr->view) + d->lyr->view->setFocus(); + else + QWidget::setFocus(); +}*/ + +//====================== + +KexiSectionHeader::BoxLayout::BoxLayout( KexiSectionHeader* parent, Direction d, int margin, int spacing, const char * name ) + : QBoxLayout(parent, d, margin, spacing, name ) +{ +} + +void KexiSectionHeader::BoxLayout::addItem( QLayoutItem * item ) +{ + QBoxLayout::addItem( item ); + if (item->widget()) { + item->widget()->installEventFilter( mainWidget() ); + if (item->widget()->inherits("KexiViewBase")) { + view = static_cast<KexiViewBase*>(item->widget()); + KexiSectionHeader *sh = static_cast<KexiSectionHeader*>(mainWidget()); + connect(view,SIGNAL(focus(bool)),sh,SLOT(slotFocus(bool))); + sh->d->lbl->setBuddy(item->widget()); + } + } +} + + +#include "kexisectionheader.moc" + diff --git a/kexi/widget/kexisectionheader.h b/kexi/widget/kexisectionheader.h new file mode 100644 index 00000000..b842f6ff --- /dev/null +++ b/kexi/widget/kexisectionheader.h @@ -0,0 +1,54 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Jaroslaw Staniek <js@iidea.pl> + + 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 Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXISECTIONHEADER_H +#define KEXISECTIONHEADER_H + +#include <qwidget.h> + +class KexiSectionHeaderPrivate; + +class KEXIEXTWIDGETS_EXPORT KexiSectionHeader : public QWidget +{ + Q_OBJECT + public: + class BoxLayout; + + KexiSectionHeader(const QString &caption, Orientation o, + QWidget* parent = 0 ); + + virtual ~KexiSectionHeader(); + + void addButton(const QString& icon, const QString& toolTip, + const QObject * receiver, const char * member); + + virtual bool eventFilter( QObject *o, QEvent *e ); + + virtual QSize sizeHint() const; + + public slots: + void slotFocus(bool in); + + protected: + KexiSectionHeaderPrivate *d; + friend class BoxLayout; +}; + +#endif + diff --git a/kexi/widget/kexismalltoolbutton.cpp b/kexi/widget/kexismalltoolbutton.cpp new file mode 100644 index 00000000..085d4847 --- /dev/null +++ b/kexi/widget/kexismalltoolbutton.cpp @@ -0,0 +1,133 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "kexismalltoolbutton.h" + +#include <qtooltip.h> +#include <qwhatsthis.h> +#include <qstyle.h> + +#include <kiconloader.h> +#include <kglobalsettings.h> + +#include <core/kexi.h> + +KexiSmallToolButton::KexiSmallToolButton(QWidget* parent, const QString& text, + const QString& icon, const char* name) + : QToolButton(parent, name) +{ + init(); + update(text, SmallIconSet(icon)); +} + +KexiSmallToolButton::KexiSmallToolButton(QWidget* parent, const QString& text, + const QIconSet& iconSet, const char* name) + : QToolButton(parent, name) +{ + init(); + update(text, iconSet); +} + +KexiSmallToolButton::KexiSmallToolButton(QWidget* parent, KAction* action) + : QToolButton(parent, action->name()) + , m_action(action) +{ + init(); + connect(this, SIGNAL(clicked()), action, SLOT(activate())); + connect(action, SIGNAL(enabled(bool)), this, SLOT(setEnabled(bool))); + updateAction(); +} + +KexiSmallToolButton::~KexiSmallToolButton() +{ +} + +void KexiSmallToolButton::updateAction() +{ + if (!m_action) + return; + update(m_action->text(), m_action->iconSet(KIcon::Small)); + setAccel(m_action->shortcut()); + QToolTip::add(this, m_action->toolTip()); + QWhatsThis::add(this, m_action->whatsThis()); +} + +void KexiSmallToolButton::init() +{ + setPaletteBackgroundColor(palette().active().background()); + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); + QFont f(KGlobalSettings::toolBarFont()); + f.setPixelSize(Kexi::smallFont().pixelSize()); + setFont(f); + setAutoRaise(true); +} + +void KexiSmallToolButton::update(const QString& text, const QIconSet& iconSet, bool tipToo) +{ + int width = 0; + if (text.isEmpty()) { + width = 10; + setUsesTextLabel(false); + } + else { + width += QFontMetrics(font()).width(text+" "); + setUsesTextLabel(true); + setTextPosition(QToolButton::Right); + QToolButton::setTextLabel(text, tipToo); + } + if (!iconSet.isNull()) { + width += IconSize(KIcon::Small); + QToolButton::setIconSet(iconSet); + } + setFixedWidth( width ); +} + +void KexiSmallToolButton::setIconSet( const QIconSet& iconSet ) +{ + update(textLabel(), iconSet); +} + +void KexiSmallToolButton::setIconSet( const QString& icon ) +{ + setIconSet( SmallIconSet(icon) ); +} + +void KexiSmallToolButton::setTextLabel( const QString & newLabel, bool tipToo ) +{ + Q_UNUSED( tipToo ); + + update(newLabel, iconSet()); +} + +void KexiSmallToolButton::drawButton( QPainter *_painter ) +{ + QToolButton::drawButton(_painter); + if (QToolButton::popup()) { + QStyle::SFlags arrowFlags = QStyle::Style_Default; + if (isDown()) + arrowFlags |= QStyle::Style_Down; + if (isEnabled()) + arrowFlags |= QStyle::Style_Enabled; + style().drawPrimitive(QStyle::PE_ArrowDown, _painter, + QRect(width()-7, height()-7, 5, 5), colorGroup(), + arrowFlags, QStyleOption() ); + } +} + +#include "kexismalltoolbutton.moc" diff --git a/kexi/widget/kexismalltoolbutton.h b/kexi/widget/kexismalltoolbutton.h new file mode 100644 index 00000000..59af7cfa --- /dev/null +++ b/kexi/widget/kexismalltoolbutton.h @@ -0,0 +1,59 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXISMALLTOOLBUTTON_H +#define KEXISMALLTOOLBUTTON_H + +#include <qtoolbutton.h> +#include <kaction.h> + +class QIconSet; + +//! A small tool button with icon and optional text +class KEXIEXTWIDGETS_EXPORT KexiSmallToolButton : public QToolButton +{ + Q_OBJECT + + public: + KexiSmallToolButton(QWidget* parent, const QString& text, + const QString& icon = QString::null, const char* name = 0); + + KexiSmallToolButton(QWidget* parent, const QString& text, + const QIconSet& iconSet, const char* name = 0); + + KexiSmallToolButton(QWidget* parent, KAction *action); + + virtual ~KexiSmallToolButton(); + + void updateAction(); + + virtual void setIconSet( const QIconSet& iconSet ); + virtual void setIconSet( const QString& icon ); + virtual void setTextLabel( const QString & newLabel, bool tipToo ); + virtual void setTextLabel( const QString & newLabel ) { setTextLabel(newLabel, false); } + + protected: + void update(const QString& text, const QIconSet& iconSet, bool tipToo = false); + void init(); + virtual void drawButton( QPainter *_painter ); + + QGuardedPtr<KAction> m_action; +}; + +#endif diff --git a/kexi/widget/pixmapcollection.cpp b/kexi/widget/pixmapcollection.cpp new file mode 100644 index 00000000..7f9718d6 --- /dev/null +++ b/kexi/widget/pixmapcollection.cpp @@ -0,0 +1,440 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr> + + 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 Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include <qpixmap.h> +#include <qlayout.h> +#include <qlabel.h> +#include <qstringlist.h> +#include <qtoolbutton.h> +#include <qdom.h> + +#include <kapplication.h> +#include <kiconloader.h> +#include <kfiledialog.h> +#include <kcombobox.h> +#include <kicondialog.h> +#include <klineedit.h> +#include <kicontheme.h> +#include <kpixmapio.h> +#include <kpopupmenu.h> +#include <kdebug.h> +#include <klocale.h> +#include <kmessagebox.h> + +#include "pixmapcollection.h" + +/// Pixmap Collection +PixmapCollection::PixmapCollection(const QString &collectionName, QObject *parent, const char *name) + : QObject(parent, name) +{ + m_name = collectionName; +} + +QString +PixmapCollection::addPixmapPath(const KURL &url) +{ + QString name = url.filename(); + while(m_pixmaps.contains(name)) + { + bool ok; + int num = name.right(1).toInt(&ok, 10); + if(ok) + name = name.left(name.length()-1) + QString::number(num+1); + else + name += "2"; + } + + m_pixmaps.insert(name, qMakePair(url.path(), 0)); + return name; +} + +QString +PixmapCollection::addPixmapName(const QString &icon, int size) +{ + QString name = icon; + while(m_pixmaps.contains(name)) + { + bool ok; + int num = name.right(1).toInt(&ok, 10); + if(ok) + name = name.left(name.length()-1) + QString::number(num+1); + else + name += "2"; + } + + m_pixmaps.insert(name, qMakePair(icon, size)); + return name; +} + +void +PixmapCollection::removePixmap(const QString &name) +{ + m_pixmaps.remove(name); +} + +QPixmap +PixmapCollection::getPixmap(const QString &name) +{ + if(!m_pixmaps.contains(name)) + { + kdDebug() << " The icon " << name << " you requested is not in the collection" << endl; + return QPixmap(); + } + + if(m_pixmaps[name].second != 0) + { + return kapp->iconLoader()->loadIcon(m_pixmaps[name].first, KIcon::NoGroup, m_pixmaps[name].second); + } + else + return QPixmap(m_pixmaps[name].first); +} + +bool +PixmapCollection::contains(const QString &name) +{ + return m_pixmaps.contains(name); +} + +void +PixmapCollection::save(QDomNode parentNode) +{ + if(m_pixmaps.isEmpty()) + return; + + QDomDocument domDoc = parentNode.ownerDocument(); + QDomElement collection = domDoc.createElement("collection"); + parentNode.appendChild(collection); + + PixmapMap::ConstIterator it; + PixmapMap::ConstIterator endIt = m_pixmaps.constEnd(); + for(it = m_pixmaps.constBegin(); it != endIt; ++it) + { + QDomElement item = domDoc.createElement("pixmap"); + collection.appendChild(item); + item.setAttribute("name", it.key()); + if(it.data().second != 0) + item.setAttribute("size", QString::number(it.data().second)); + + QString text = it.data().first; + QDomText textNode = domDoc.createTextNode(text); + item.appendChild(textNode); + } +} + +void +PixmapCollection::load(QDomNode node) +{ + QDomDocument domDoc = node.ownerDocument(); + for(QDomNode n = node.firstChild(); !n.isNull(); n = n.nextSibling()) + { + QDomElement el = n.toElement(); + QPair<QString, int> pair = qMakePair(el.text(), el.attribute("size").toInt()); + m_pixmaps[el.attribute("name")] = pair; + } +} + +//// A dialog to load a KDE icon by its name +LoadIconDialog::LoadIconDialog(QWidget *parent) +: KDialogBase(parent, "loadicon_dialog", true, i18n("Load KDE Icon by Name"), Ok|Cancel, Ok, false) +{ + QFrame *frame = makeMainWidget(); + QGridLayout *l = new QGridLayout(frame, 2, 3, 0, 6); + + // Name input + QLabel *name = new QLabel(i18n("&Name:"), frame); + l->addWidget(name, 0, 0); + name->setAlignment(Qt::AlignRight|Qt::AlignVCenter); + m_nameInput = new KLineEdit("kexi", frame); + l->addWidget(m_nameInput, 0, 1); + name->setBuddy(m_nameInput); + + // Choose size + QLabel *size = new QLabel(i18n("&Size:"), frame); + l->addWidget(size, 1, 0); + size->setAlignment(Qt::AlignRight|Qt::AlignVCenter); + + KComboBox *combo = new KComboBox(frame); + l->addWidget(combo, 1, 1); + size->setBuddy(combo); + QStringList list; + list << i18n("Small") << i18n("Medium") << i18n("Large") << i18n("Huge"); + combo->insertStringList(list); + combo->setCurrentItem(2); + connect(combo, SIGNAL(activated(int)), this, SLOT(changeIconSize(int))); + + + // Icon chooser button + m_button = new KIconButton(frame); + m_button->setIcon("kexi"); + m_button->setIconSize(KIcon::SizeMedium); + l->addMultiCellWidget(m_button, 0, 1, 2, 2); + connect(m_button, SIGNAL(iconChanged(QString)), this, SLOT(updateIconName(QString))); + connect(m_nameInput, SIGNAL(textChanged(const QString &)), this, SLOT(setIcon(const QString &))); +} + +void +LoadIconDialog::updateIconName(QString icon) +{ + m_nameInput->setText(icon); +} + +void +LoadIconDialog::setIcon(const QString &icon) +{ + m_button->setIcon(icon); +} + +void +LoadIconDialog::changeIconSize(int index) +{ + int size = KIcon::SizeMedium; + switch(index) + { + case 0: size = KIcon::SizeSmall; break; + //case 1: size = KIcon::SizeSmallMedium; break; + case 1: size = KIcon::SizeMedium; break; + case 2: size = KIcon::SizeLarge; break; +#if !defined(Q_WS_WIN) && KDE_IS_VERSION(3,1,9) + case 3: size = KIcon::SizeHuge; break; +#endif + default:; + } + + m_button->setIconSize(size); +} + +int LoadIconDialog::iconSize() +{ + return m_button->iconSize(); +} + +QString LoadIconDialog::iconName() +{ + return m_button->icon(); +} + +/// Pixmap Collection Editor Dialog +PixmapCollectionEditor::PixmapCollectionEditor(PixmapCollection *collection, QWidget *parent) +: KDialogBase(parent, "pixcollection_dialog", true, + i18n("Edit Pixmap Collection: %1").arg(collection->collectionName()), Close, Close, false) +{ + m_collection = collection; + QFrame *frame = makeMainWidget(); + QHBoxLayout *l = new QHBoxLayout(frame, 0, 6); + setInitialSize(QSize(400, 200), true); + + //// Setup the icon toolbar ///////////////// + QVBoxLayout *vlayout = new QVBoxLayout(l, 3); + QToolButton *newItemPath = new QToolButton(frame); + newItemPath->setIconSet(BarIconSet("fileopen")); + newItemPath->setTextLabel(i18n("&Add File"), true); + vlayout->addWidget(newItemPath); + m_buttons.insert(BNewItemPath, newItemPath); + connect(newItemPath, SIGNAL(clicked()), this, SLOT(newItemByPath())); + + QToolButton *newItemName = new QToolButton(frame); + newItemName->setIconSet(BarIconSet("icons")); + newItemName->setTextLabel(i18n("&Add an Icon"), true); + vlayout->addWidget(newItemName); + m_buttons.insert(BNewItemName, newItemName); + connect(newItemName, SIGNAL(clicked()), this, SLOT(newItemByName())); + + QToolButton *delItem = new QToolButton(frame); + delItem->setIconSet(BarIconSet("edit_remove")); + delItem->setTextLabel(i18n("&Remove Selected Item"), true); + vlayout->addWidget(delItem); + m_buttons.insert(BDelItem, delItem); + connect(delItem, SIGNAL(clicked()), this, SLOT(removeItem())); + vlayout->addStretch(); + + // Setup the iconView + m_iconView = new KIconView(frame, "pixcollection_iconView"); + m_iconView->resize(100,100); + m_iconView->setArrangement(QIconView::LeftToRight); + m_iconView->setAutoArrange(true); + m_iconView->setMode(KIconView::Select); + l->addWidget(m_iconView); + connect(m_iconView, SIGNAL(contextMenuRequested(QIconViewItem*, const QPoint&)), this, SLOT(displayMenu(QIconViewItem*, const QPoint&))); + connect(m_iconView, SIGNAL(itemRenamed(QIconViewItem*, const QString &)), this, SLOT(renameCollectionItem(QIconViewItem*, const QString&))); + + PixmapMap::ConstIterator it; + PixmapMap::ConstIterator endIt = collection->m_pixmaps.end(); + for(it = collection->m_pixmaps.constBegin(); it != endIt; ++it) + createIconViewItem(it.key()); +} + +void +PixmapCollectionEditor::newItemByName() +{ + LoadIconDialog d(parentWidget()); + if(d.exec()== QDialog::Accepted) + { + if(d.iconName().isEmpty()) + return; + + QString name = m_collection->addPixmapName(d.iconName(), d.iconSize()); + createIconViewItem(name); + } +} + +void +PixmapCollectionEditor::newItemByPath() +{ + KURL url = KFileDialog::getImageOpenURL("::kexi", parentWidget()); + if(url.isEmpty()) + return; + QString name = m_collection->addPixmapPath(url); + createIconViewItem(name); +} + +void +PixmapCollectionEditor::removeItem() +{ + QIconViewItem *item = m_iconView->currentItem(); + if( !item ) + return; + + int confirm = KMessageBox::questionYesNo(parentWidget(), QString("<qt>")+ + i18n("Do you want to remove item \"%1\" from collection \"%2\"?") + .arg(item->text()).arg(m_collection->collectionName()) + "</qt>"); + if(confirm == KMessageBox::No) + return; + + m_collection->removePixmap(item->text()); + delete item; +} + +void +PixmapCollectionEditor::renameItem() +{ + if(m_iconView->currentItem()) + m_iconView->currentItem()->rename(); +} + +void +PixmapCollectionEditor::createIconViewItem(const QString &name) +{ + PixmapIconViewItem *item = new PixmapIconViewItem(m_iconView, name, getPixmap(name)); + item->setRenameEnabled(true); +} + +QPixmap +PixmapCollectionEditor::getPixmap(const QString &name) +{ + QPixmap pixmap = m_collection->getPixmap(name); + if((pixmap.width() <= 48) && (pixmap.height() <= 48)) + return pixmap; + + KPixmapIO io; + QImage image = io.convertToImage(pixmap); + pixmap = io.convertToPixmap(image.scale(48, 48, QImage::ScaleMin)); + return pixmap; +} + +void +PixmapCollectionEditor::renameCollectionItem(QIconViewItem *it, const QString &name) +{ + PixmapIconViewItem *item = static_cast<PixmapIconViewItem*>(it); + if(!m_collection->m_pixmaps.contains(item->name())) + return; + + // We just rename the collection item + QPair<QString, int> pair = m_collection->m_pixmaps[item->name()]; + m_collection->m_pixmaps.remove(item->name()); + m_collection->m_pixmaps[name] = pair; + item->setName(name); +} + +void +PixmapCollectionEditor::displayMenu(QIconViewItem *it, const QPoint &p) +{ + if(!it) return; + KPopupMenu *menu = new KPopupMenu(); + menu->insertItem(SmallIconSet("edit"), i18n("Rename Item"), this, SLOT(renameItem())); + menu->insertItem(SmallIconSet("remove"), i18n("Remove Item"), this, SLOT(removeItem())); + menu->exec(p); +} + +//// A Dialog to choose a pixmap from the PixmapCollection +PixmapCollectionChooser::PixmapCollectionChooser(PixmapCollection *collection, const QString &selectedItem, QWidget *parent) +: KDialogBase(parent, "pixchoose_dialog", true, i18n("Select Pixmap From %1").arg(collection->collectionName()), + User1|Ok|Cancel, Ok, false, KGuiItem(i18n("Edit Collection..."))) +{ + m_collection = collection; + setInitialSize(QSize(400, 200), true); + + m_iconView = new KIconView(this, "pixchooser_iconView"); + setMainWidget(m_iconView); + m_iconView->setArrangement(QIconView::LeftToRight); + m_iconView->setAutoArrange(true); + m_iconView->setMode(KIconView::Select); + + PixmapMap::ConstIterator it; + PixmapMap::ConstIterator endIt = collection->m_pixmaps.constEnd(); + for(it = collection->m_pixmaps.constBegin(); it != endIt; ++it) + new PixmapIconViewItem(m_iconView, it.key(), getPixmap(it.key())); + + QIconViewItem *item = m_iconView->findItem(selectedItem, Qt::ExactMatch); + if(item && !selectedItem.isEmpty()) + m_iconView->setCurrentItem(item); +} + +QPixmap +PixmapCollectionChooser::pixmap() +{ + if(! m_iconView->currentItem()) + return QPixmap(); + QString name = m_iconView->currentItem()->text(); + return m_collection->getPixmap(name); +} + +QString +PixmapCollectionChooser::pixmapName() +{ + return m_iconView->currentItem() ? m_iconView->currentItem()->text() : QString(""); +} + +QPixmap +PixmapCollectionChooser::getPixmap(const QString &name) +{ + QPixmap pixmap = m_collection->getPixmap(name); + if((pixmap.width() <= 48) && (pixmap.height() <= 48)) + return pixmap; + + // We scale the pixmap down to 48x48 to fit in the iconView + KPixmapIO io; + QImage image = io.convertToImage(pixmap); + pixmap = io.convertToPixmap(image.scale(48, 48, QImage::ScaleMin)); + return pixmap; +} + +void +PixmapCollectionChooser::slotUser1() +{ + PixmapCollectionEditor dialog(m_collection, parentWidget()); + dialog.exec(); + + m_iconView->clear(); + PixmapMap::ConstIterator it; + PixmapMap::ConstIterator endIt = m_collection->m_pixmaps.constEnd(); + for(it = m_collection->m_pixmaps.constBegin(); it != endIt; ++it) + new PixmapIconViewItem(m_iconView, it.key(), getPixmap(it.key())); +} + +#include "pixmapcollection.moc" diff --git a/kexi/widget/pixmapcollection.h b/kexi/widget/pixmapcollection.h new file mode 100644 index 00000000..a46a2043 --- /dev/null +++ b/kexi/widget/pixmapcollection.h @@ -0,0 +1,162 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Cedric Pasteur <cedric.pasteur@free.fr> + + 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 Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KFORMDESIGNERPIXMAPCOLLECTION_H +#define KFORMDESIGNERPIXMAPCOLLECTION_H + +#include <qobject.h> +#include <qmap.h> +#include <qpair.h> +#include <qintdict.h> +#include <qtoolbutton.h> + +#include <kicontheme.h> +#include <kdialogbase.h> +#include <kiconview.h> +#include <kurl.h> + +class QPixmap; +class KIconView; +class KIconButton; +class KLineEdit; +class QDomNode; + +typedef QMap<QString, QPair<QString, int> > PixmapMap; + +//! A class that store pixmaps (by path or by name for KDE icons) +class KEXIEXTWIDGETS_EXPORT PixmapCollection : public QObject +{ + Q_OBJECT + + public: + PixmapCollection(const QString &collectionName, QObject *parent = 0, const char *name = 0); + ~PixmapCollection() {;} + + QString addPixmapPath(const KURL &url); + QString addPixmapName(const QString &name, int size = KIcon::SizeMedium); + void removePixmap(const QString &name); + + bool contains(const QString &name); + QPixmap getPixmap(const QString &name); + + void save(QDomNode parentNode); + void load(QDomNode node); + + QString collectionName() {return m_name; } + + signals: + void itemRenamed(const QString &oldName, const QString &newName); + void itemRemoved(const QString &name); + + protected: + QString m_name; + PixmapMap m_pixmaps; + + friend class PixmapCollectionEditor; + friend class PixmapCollectionChooser; +}; + +//! A dialog to edit the contents of a PixmapCollection +class KEXIEXTWIDGETS_EXPORT PixmapCollectionEditor : public KDialogBase +{ + Q_OBJECT + + public: + PixmapCollectionEditor(PixmapCollection *collection, QWidget *parent = 0); + ~PixmapCollectionEditor() {;} + + protected: + QPixmap getPixmap(const QString &name); + void createIconViewItem(const QString &name); + + protected slots: + void newItemByPath(); + void newItemByName(); + void removeItem(); + void renameItem(); + void renameCollectionItem(QIconViewItem *item, const QString &name); + void displayMenu(QIconViewItem *item, const QPoint &p); + + private: + enum { BNewItemPath = 101, BNewItemName, BDelItem}; + KIconView *m_iconView; + QIntDict<QToolButton> m_buttons; + PixmapCollection *m_collection; +}; + +//! A dialog to choose an icon in a PixmapCollection +class KEXIEXTWIDGETS_EXPORT PixmapCollectionChooser : public KDialogBase +{ + Q_OBJECT + + public: + PixmapCollectionChooser(PixmapCollection *collection, const QString &selectedItem, QWidget *parent = 0); + ~PixmapCollectionChooser() {;} + + QPixmap pixmap(); + QString pixmapName(); + + protected: + QPixmap getPixmap(const QString &name); + protected slots: + virtual void slotUser1(); + + private: + PixmapCollection *m_collection; + KIconView *m_iconView; +}; + +//! A simple dialog to choose a KDE icon +class KEXIEXTWIDGETS_EXPORT LoadIconDialog : public KDialogBase +{ + Q_OBJECT + + public: + LoadIconDialog(QWidget *parent = 0); + ~LoadIconDialog() {;} + + int iconSize(); + QString iconName(); + + protected slots: + void changeIconSize(int); + void updateIconName(QString); + void setIcon(const QString &); + + private: + KLineEdit *m_nameInput; + KIconButton *m_button; +}; + +//! A Special KIconViewItem that holds the name of its associated pixmap (to allow renaming) +class KEXIEXTWIDGETS_EXPORT PixmapIconViewItem : public KIconViewItem +{ + public: + PixmapIconViewItem(KIconView *parent, const QString &text, const QPixmap &icon) + : KIconViewItem(parent, text, icon) { m_name = text; } + ~PixmapIconViewItem() {;} + + void setName(const QString &name) { m_name = name; } + QString name() { return m_name;} + + private: + QString m_name; +}; + +#endif diff --git a/kexi/widget/relations/Makefile.am b/kexi/widget/relations/Makefile.am new file mode 100644 index 00000000..f09e939a --- /dev/null +++ b/kexi/widget/relations/Makefile.am @@ -0,0 +1,38 @@ +include $(top_srcdir)/kexi/Makefile.global + +lib_LTLIBRARIES = libkexirelationsview.la + +libkexirelationsview_la_SOURCES = kexirelationview.cpp kexirelationviewconnection.cpp \ + kexirelationviewtable.cpp kexirelationwidget.cpp + +libkexirelationsview_la_LDFLAGS = $(all_libraries) $(VER_INFO) -Wnounresolved +libkexirelationsview_la_LIBADD = ../../core/libkexicore.la + +libkexirelationsview_la_METASOURCES = AUTO + +SUBDIRS = . + +# kde_appsdir Where your application's menu entry (.desktop) should go to. +# kde_icondir Where your icon should go to - better use KDE_ICON. +# kde_sounddir Where your sounds should go to. +# kde_htmldir Where your docs should go to. (contains lang subdirs) +# kde_datadir Where you install application data. (Use a subdir) +# kde_locale Where translation files should go to. (contains lang subdirs) +# kde_cgidir Where cgi-bin executables should go to. +# kde_confdir Where config files should go to (system-wide ones with default values). +# kde_mimedir Where mimetypes .desktop files should go to. +# kde_servicesdir Where services .desktop files should go to. +# kde_servicetypesdir Where servicetypes .desktop files should go to. +# kde_toolbardir Where general toolbar icons should go to (deprecated, use KDE_ICON). +# kde_wallpaperdir Where general wallpapers should go to. +# kde_templatesdir Where templates for the "New" menu (Konqueror/KDesktop) should go to. +# kde_bindir Where executables should go to. Use bin_PROGRAMS or bin_SCRIPTS. +# kde_libdir Where shared libraries should go to. Use lib_LTLIBRARIES. +# kde_moduledir Where modules (e.g. parts) should go to. Use kde_module_LTLIBRARIES. +# kde_styledir Where Qt/KDE widget styles should go to (new in KDE 3). +# kde_designerdir Where Qt Designer plugins should go to (new in KDE 3). + +# set the include path for X, qt and KDE +INCLUDES= -I$(top_srcdir)/kexi $(LIB_KEXI_KMDI_INCLUDES) \ + -I$(top_srcdir)/kexi/core $(all_includes) + diff --git a/kexi/widget/relations/kexirelationview.cpp b/kexi/widget/relations/kexirelationview.cpp new file mode 100644 index 00000000..9d68a755 --- /dev/null +++ b/kexi/widget/relations/kexirelationview.cpp @@ -0,0 +1,639 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Lucijan Busch <lucijan@gmx.at> + Copyright (C) 2003 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include <kdebug.h> + +#include <qstringlist.h> +#include <qlayout.h> +#include <qlabel.h> +#include <qheader.h> +#include <qevent.h> +#include <qpainter.h> +#include <qstyle.h> +#include <qlineedit.h> +#include <qpopupmenu.h> + +#include <klocale.h> +#include <kaction.h> +#include <kpopupmenu.h> +#include <kglobalsettings.h> +#include <kmessagebox.h> + +#include <kexidb/tableschema.h> +#include <kexidb/indexschema.h> +#include <kexidb/utils.h> + +#include "kexirelationview.h" +#include "kexirelationviewtable.h" +#include "kexirelationviewconnection.h" +#include <kexi.h> + +KexiRelationView::KexiRelationView(QWidget *parent, const char *name) + : QScrollView(parent, name, WStaticContents) +{ +// m_relation=relation; +// m_relation->incUsageCount(); + m_selectedConnection = 0; + m_readOnly=false; + m_focusedTableView = 0; + setFrameStyle(QFrame::WinPanel | QFrame::Sunken); + +// connect(relation, SIGNAL(relationListUpdated(QObject *)), this, SLOT(slotListUpdate(QObject *))); + + viewport()->setPaletteBackgroundColor(colorGroup().mid()); + setFocusPolicy(WheelFocus); + setResizePolicy(Manual); +/*MOVED TO KexiRelationDialog + //actions + m_tableQueryPopup = new KPopupMenu(this, "m_popup"); + m_tableQueryPopup->insertTitle(i18n("Table")); + m_connectionPopup = new KPopupMenu(this, "m_connectionPopup"); + m_connectionPopup->insertTitle(i18n("Relation")); + m_areaPopup = new KPopupMenu(this, "m_areaPopup"); + + plugSharedAction("edit_delete", i18n("Hide Table"), m_tableQueryPopup); + plugSharedAction("edit_delete",m_connectionPopup); + plugSharedAction("edit_delete",this, SLOT(removeSelectedObject())); +*/ +#if 0 + m_removeSelectedTableQueryAction = new KAction(i18n("&Hide Selected Table/Query"), "editdelete", "", + this, SLOT(removeSelectedTableQuery()), parent->actionCollection(), "relationsview_removeSelectedTableQuery"); + m_removeSelectedConnectionAction = new KAction(i18n("&Remove Selected Relationship"), "button_cancel", "", + this, SLOT(removeSelectedConnection()), parent->actionCollection(), "relationsview_removeSelectedConnection"); + m_openSelectedTableQueryAction = new KAction(i18n("&Open Selected Table/Query"), "", "", + this, SLOT(openSelectedTableQuery()), 0/*parent->actionCollection()*/, "relationsview_openSelectedTableQuery"); +#endif + +// invalidateActions(); + +#if 0 + + + m_popup = new KPopupMenu(this, "m_popup"); + m_openSelectedTableQueryAction->plug( m_popup ); + m_removeSelectedTableQueryAction->plug( m_popup ); + m_removeSelectedConnectionAction->plug( m_popup ); + + invalidateActions(); +#endif + + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding, true); +} + +KexiRelationView::~KexiRelationView() +{ +} + +/*KexiRelationViewTableContainer* +KexiRelationView::containerForTable(KexiDB::TableSchema* tableSchema) +{ + if (!tableSchema) + return 0; + for (TablesDictIterator it(m_tables); it.current(); ++it) { + if (it.current()->schema()->table()==tableSchema) + return it.current(); + } + return 0; +}*/ + +KexiRelationViewTableContainer * +KexiRelationView::tableContainer(KexiDB::TableSchema *t) const +{ + return t ? m_tables.find(t->name()) : 0; +} + +KexiRelationViewTableContainer* +KexiRelationView::addTableContainer(KexiDB::TableSchema *t, const QRect &rect) +{ + if(!t) + return 0; + + kdDebug() << "KexiRelationView::addTable(): " << t->name() << ", " << viewport() << endl; + + KexiRelationViewTableContainer* c = tableContainer(t); + if (c) { + kdWarning() << "KexiRelationView::addTable(): table already added" << endl; + return c; + } + + c = new KexiRelationViewTableContainer(this, +/*! @todo what about query? */ + new KexiDB::TableOrQuerySchema(t) + ); + connect(c, SIGNAL(endDrag()), this, SLOT(slotTableViewEndDrag())); + connect(c, SIGNAL(gotFocus()), this, SLOT(slotTableViewGotFocus())); +// connect(c, SIGNAL(headerContextMenuRequest(const QPoint&)), +// this, SLOT(tableHeaderContextMenuRequest(const QPoint&))); + connect(c, SIGNAL(contextMenuRequest(const QPoint&)), + this, SIGNAL(tableContextMenuRequest(const QPoint&))); + + addChild(c, 100,100); + if (rect.isValid()) {//predefined size + QSize finalSize = c->size().expandedTo( c->sizeHint() ); + QRect r = rect; + r.setSize( finalSize + QSize(0,10) ); + moveChild( c, rect.left(), rect.top() ); + //we're doing this instead of setGeometry(rect) + //because the geomenty might be saved on other system with bigger fonts :) + c->resize(c->sizeHint()); +// c->setGeometry(r); +//TODO + +// moveChild( c, rect.left(), rect.top() ); // setGeometry(rect); +// c->resize( finalSize ); +// c->updateGeometry(); + } + c->show(); + updateGeometry(); + if (!rect.isValid()) { + c->updateGeometry(); + c->resize(c->sizeHint()); + } + int x, y; + + if(m_tables.count() > 0) + { + int place = -10; + QDictIterator<KexiRelationViewTableContainer> it(m_tables); + for(; it.current(); ++it) + { + int right = (*it)->x() + (*it)->width(); + if(right > place) + place = right; + } + + x = place + 30; + } + else + { + x = 5; + } + + y = 5; + QPoint p = viewportToContents(QPoint(x, y)); + recalculateSize(p.x() + c->width(), p.y() + c->height()); + if (!rect.isValid()) { + moveChild(c, x, y); + } + + m_tables.insert(t->name(), c); + + connect(c, SIGNAL(moved(KexiRelationViewTableContainer *)), this, + SLOT(containerMoved(KexiRelationViewTableContainer *))); + + if (hasFocus()) //ok? + c->setFocus(); + + return c; +} + +void +KexiRelationView::addConnection(const SourceConnection& _conn) +{ + SourceConnection conn = _conn; + kdDebug() << "KexiRelationView::addConnection()" << endl; + + KexiRelationViewTableContainer *master = m_tables[conn.masterTable]; + KexiRelationViewTableContainer *details = m_tables[conn.detailsTable]; + if (!master || !details) + return; + +/*! @todo what about query? */ + KexiDB::TableSchema *masterTable = master->schema()->table(); +/*! @todo what about query? */ + KexiDB::TableSchema *detailsTable = details->schema()->table(); + if (!masterTable || !detailsTable) + return; + + // ok, but we need to know where is the 'master' and where is the 'details' side: + KexiDB::Field *masterFld = masterTable->field(conn.masterField); + KexiDB::Field *detailsFld = detailsTable->field(conn.detailsField); + if (!masterFld || !detailsFld) + return; + + if (!masterFld->isUniqueKey()) { + if (detailsFld->isUniqueKey()) { + //SWAP: + KexiDB::Field *tmpFld = masterFld; + masterFld = detailsFld; + detailsFld = tmpFld; + KexiDB::TableSchema *tmpTable = masterTable; + masterTable = detailsTable; + detailsTable = tmpTable; + KexiRelationViewTableContainer *tmp = master; + master = details; + details = tmp; + QString tmp_masterTable = conn.masterTable; + conn.masterTable = conn.detailsTable; + conn.detailsTable = tmp_masterTable; + QString tmp_masterField = conn.masterField; + conn.masterField = conn.detailsField; + conn.detailsField = tmp_masterField; + } + } + +// kdDebug() << "KexiRelationView::addConnection(): finalSRC = " << m_tables[conn.srcTable] << endl; + + KexiRelationViewConnection *connView = new KexiRelationViewConnection(master, details, conn, this); + m_connectionViews.append(connView); + updateContents(connView->connectionRect()); + +/*js: will be moved up to relation/query part as this is only visual class + KexiDB::TableSchema *mtable = m_conn->tableSchema(conn.srcTable); + KexiDB::TableSchema *ftable = m_conn->tableSchema(conn.rcvTable); + KexiDB::IndexSchema *forign = new KexiDB::IndexSchema(ftable); + + forign->addField(mtable->field(conn.srcField)); + new KexiDB::Reference(forign, mtable->primaryKey()); +*/ +#if 0 + if(!interactive) + { + kdDebug() << "KexiRelationView::addConnection: adding self" << endl; + RelationList l = m_relation->projectRelations(); + l.append(conn); + m_relation->updateRelationList(this, l); + } +#endif +} + +void +KexiRelationView::drawContents(QPainter *p, int cx, int cy, int cw, int ch) +{ + KexiRelationViewConnection *cview; +// p->translate(0, (double)contentsY()); + + QRect clipping(cx, cy, cw, ch); + for(cview = m_connectionViews.first(); cview; cview = m_connectionViews.next()) + { + if(clipping.intersects(cview->oldRect() | cview->connectionRect())) + cview->drawConnection(p); + } +} + +void +KexiRelationView::slotTableScrolling(const QString& table) +{ + KexiRelationViewTableContainer *c = m_tables[table]; + + if(c) + containerMoved(c); +} + +void +KexiRelationView::containerMoved(KexiRelationViewTableContainer *c) +{ + KexiRelationViewConnection *cview; + QRect r; + for (ConnectionListIterator it(m_connectionViews); ((cview=it.current())); ++it) { +//! @todo optimize + if(cview->masterTable() == c || cview->detailsTable() == c + || cview->connectionRect().intersects(r)) + { + r |= cview->oldRect(); + kdDebug() << r << endl; + r |= cview->connectionRect(); + kdDebug() << r << endl; + } +// updateContents(cview->oldRect()); +// updateContents(cview->connectionRect()); +// } + } +//! @todo optimize! +//didn't work well: updateContents(r); + updateContents(); + +// QRect w(c->x() - 5, c->y() - 5, c->width() + 5, c->height() + 5); +// updateContents(w); + + QPoint p = viewportToContents(QPoint(c->x(), c->y())); + recalculateSize(p.x() + c->width(), p.y() + c->height()); + + emit tablePositionChanged(c); +} + +void +KexiRelationView::setReadOnly(bool b) +{ + m_readOnly=b; +//TODO +// invalidateActions(); +/* TableList::Iterator it, end( m_tables.end() ); + for ( it=m_tables.begin(); it != end; ++it) + { +// (*it)->setReadOnly(b); +#ifndef Q_WS_WIN + #warning readonly needed +#endif + }*/ +} + +void +KexiRelationView::slotListUpdate(QObject *) +{ +#if 0 + if(s != this) + { + m_connectionViews.clear(); + RelationList rl = m_relation->projectRelations(); + if(!rl.isEmpty()) + { + RelationList::ConstIterator it, end( rl.constEnd() ); + for( it = rl.begin(); it != end; ++it) + { + addConnection((*it), true); + } + } + } + + updateContents(); +#endif +} + +void +KexiRelationView::contentsMousePressEvent(QMouseEvent *ev) +{ + KexiRelationViewConnection *cview; + for(cview = m_connectionViews.first(); cview; cview = m_connectionViews.next()) + { + if(!cview->matchesPoint(ev->pos(), 3)) + continue; + clearSelection(); + setFocus(); + cview->setSelected(true); + updateContents(cview->connectionRect()); + m_selectedConnection = cview; + emit connectionViewGotFocus(); +// invalidateActions(); + + if(ev->button() == RightButton) {//show popup + kdDebug() << "KexiRelationView::contentsMousePressEvent(): context" << endl; +// QPopupMenu m; +// m_removeSelectedTableQueryAction->plug( &m ); +// m_removeSelectedConnectionAction->plug( &m ); + emit connectionContextMenuRequest( ev->globalPos() ); +// executePopup( ev->globalPos() ); + } + return; + } + //connection not found + clearSelection(); +// invalidateActions(); + if(ev->button() == RightButton) {//show popup on view background area +// QPopupMenu m; +// m_removeSelectedConnectionAction->plug( &m ); + emit emptyAreaContextMenuRequest( ev->globalPos() ); +// executePopup(ev->globalPos()); + } + else { + emit emptyAreaGotFocus(); + } + setFocus(); +// QScrollView::contentsMousePressEvent(ev); +} + +void KexiRelationView::clearSelection() +{ + if (m_focusedTableView) { + m_focusedTableView->unsetFocus(); + m_focusedTableView = 0; +// setFocus(); +// invalidateActions(); + } + if (m_selectedConnection) { + m_selectedConnection->setSelected(false); + updateContents(m_selectedConnection->connectionRect()); + m_selectedConnection = 0; +// invalidateActions(); + } +} + +void +KexiRelationView::keyPressEvent(QKeyEvent *ev) +{ + kdDebug() << "KexiRelationView::keyPressEvent()" << endl; + + if (ev->key()==KGlobalSettings::contextMenuKey()) { + if (m_selectedConnection) { + emit connectionContextMenuRequest( + mapToGlobal(m_selectedConnection->connectionRect().center()) ); + } +// m_popup->exec( mapToGlobal( m_focusedTableView ? m_focusedTableView->pos() + m_focusedTableView->rect().center() : rect().center() ) ); +// executePopup(); + } + else { + if(ev->key() == Key_Delete) + removeSelectedObject(); + } +} + +void +KexiRelationView::recalculateSize(int width, int height) +{ + kdDebug() << "recalculateSize(" << width << ", " << height << ")" << endl; + int newW = contentsWidth(), newH = contentsHeight(); + kdDebug() << "contentsSize(" << newW << ", " << newH << ")" << endl; + + if(newW < width) + newW = width; + + if(newH < height) + newH = height; + + resizeContents(newW, newH); +} + +/*! Resizes contents to size exactly enough to fit tableViews. + Executed on every tableView's drop event. +*/ +void +KexiRelationView::stretchExpandSize() +{ + int max_x=-1, max_y=-1; + QDictIterator<KexiRelationViewTableContainer> it(m_tables); + for (;it.current(); ++it) { + if (it.current()->right()>max_x) + max_x = it.current()->right(); + if (it.current()->bottom()>max_y) + max_y = it.current()->bottom(); + } + QPoint p = viewportToContents(QPoint(max_x, max_y) + QPoint(3,3)); //3 pixels margin + resizeContents(p.x(), p.y()); +} + +void KexiRelationView::slotTableViewEndDrag() +{ + kdDebug() << "END DRAG!" <<endl; + stretchExpandSize(); + +} + +void +KexiRelationView::removeSelectedObject() +{ + if (m_selectedConnection) { + removeConnection(m_selectedConnection); + +#if 0 + RelationList l = m_relation->projectRelations(); + RelationList nl; + for(RelationList::Iterator it = l.begin(); it != l.end(); ++it) + { + if((*it).srcTable == m_selectedConnection->connection().srcTable + && (*it).rcvTable == m_selectedConnection->connection().rcvTable + && (*it).srcField == m_selectedConnection->connection().srcField + && (*it).rcvField == m_selectedConnection->connection().rcvField) + { + kdDebug() << "KexiRelationView::removeSelectedConnection(): matching found!" << endl; +// l.remove(it); + } + else + { + nl.append(*it); + } + } + + kdDebug() << "KexiRelationView::removeSelectedConnection(): d2" << endl; + m_relation->updateRelationList(this, nl); + kdDebug() << "KexiRelationView::removeSelectedConnection(): d3" << endl; +#endif + delete m_selectedConnection; + m_selectedConnection = 0; +// invalidateActions(); + } + else if (m_focusedTableView) { + KexiRelationViewTableContainer *tmp = m_focusedTableView; + m_focusedTableView = 0; + hideTable(tmp); + } +} + +void +KexiRelationView::hideTable(KexiRelationViewTableContainer* tableView) +{ +/*! @todo what about query? */ + KexiDB::TableSchema *ts = tableView->schema()->table(); + //for all connections: find and remove all connected with this table + QPtrListIterator<KexiRelationViewConnection> it(m_connectionViews); + for (;it.current();) { + if (it.current()->masterTable() == tableView + || it.current()->detailsTable() == tableView) + { + //remove this + removeConnection(it.current()); + } + else { + ++it; + } + } + m_tables.take(tableView->schema()->name()); + delete tableView; + emit tableHidden( *ts ); +} + +void +KexiRelationView::hideAllTablesExcept( KexiDB::TableSchema::List* tables ) +{ +//! @todo what about queries? + for (TablesDictIterator it(m_tables); it.current();) { + KexiDB::TableSchema *table = it.current()->schema()->table(); + if (!table || tables->findRef( table )!=-1) { + ++it; + continue; + } + hideTable(it.current()); + } +} + +void +KexiRelationView::removeConnection(KexiRelationViewConnection *conn) +{ + emit aboutConnectionRemove(conn); + m_connectionViews.remove(conn); + updateContents(conn->connectionRect()); + kdDebug() << "KexiRelationView::removeConnection()" << endl; +} + +void KexiRelationView::slotTableViewGotFocus() +{ + if (m_focusedTableView == sender()) + return; + kdDebug() << "GOT FOCUS!" <<endl; + clearSelection(); +// if (m_focusedTableView) +// m_focusedTableView->unsetFocus(); + m_focusedTableView = (KexiRelationViewTableContainer*)sender(); +// invalidateActions(); + emit tableViewGotFocus(); +} + +QSize KexiRelationView::sizeHint() const +{ + return QSize(QScrollView::sizeHint());//.width(), 600); +} + +void KexiRelationView::clear() +{ + removeAllConnections(); + m_tables.setAutoDelete(true); + m_tables.clear(); + m_tables.setAutoDelete(false); + updateContents(); +} + +void KexiRelationView::removeAllConnections() +{ + clearSelection(); //sanity + m_connectionViews.setAutoDelete(true); + m_connectionViews.clear(); + m_connectionViews.setAutoDelete(false); + updateContents(); +} + +/* + +void KexiRelationView::tableHeaderContextMenuRequest(const QPoint& pos) +{ + if (m_focusedTableView != sender()) + return; + kdDebug() << "HEADER CTXT MENU!" <<endl; + invalidateActions(); + m_tableQueryPopup->exec(pos); +} + +//! Invalidates all actions availability +void KexiRelationView::invalidateActions() +{ + setAvailable("edit_delete", m_selectedConnection || m_focusedTableView); +} + +void KexiRelationView::executePopup( QPoint pos ) +{ + if (pos==QPoint(-1,-1)) { + pos = mapToGlobal( m_focusedTableView ? m_focusedTableView->pos() + m_focusedTableView->rect().center() : rect().center() ); + } + if (m_focusedTableView) + m_tableQueryPopup->exec(pos); + else if (m_selectedConnection) + m_connectionPopup->exec(pos); +} +*/ + +#include "kexirelationview.moc" diff --git a/kexi/widget/relations/kexirelationview.h b/kexi/widget/relations/kexirelationview.h new file mode 100644 index 00000000..2de6620d --- /dev/null +++ b/kexi/widget/relations/kexirelationview.h @@ -0,0 +1,167 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Lucijan Busch <lucijan@gmx.at> + Copyright (C) 2003-2004, 2006 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIRELATIONVIEW_H +#define KEXIRELATIONVIEW_H + +#include <qguardedptr.h> +#include <qscrollview.h> +#include <qptrlist.h> +#include <qdict.h> + +#include <kexidb/tableschema.h> + +#include "kexirelationviewconnection.h" + +class QFrame; + +class KexiRelationViewTable; +class KexiRelationViewTableContainer; +class KAction; +class KPopupMenu; + +namespace KexiDB +{ + class Reference; + class Connection; +} + +typedef QDict<KexiRelationViewTableContainer> TablesDict; +typedef QDictIterator<KexiRelationViewTableContainer> TablesDictIterator; +typedef QPtrList<KexiRelationViewConnection> ConnectionList; +typedef QPtrListIterator<KexiRelationViewConnection> ConnectionListIterator; + +struct SourceConnection +{ + QString masterTable; + QString detailsTable; + QString masterField; + QString detailsField; +}; + +/*! @short provides a view for displaying relations between database tables. + + It is currently used for two purposes: + - displaying global database relations + - displaying relations defined for a database query + + The class is for displaying only - retrieving data and updating data on the backend side is implemented + in KexiRelationWidget, and more specifically in: Kexi Relation Part and Kexi Query Part. +*/ +class KEXIRELATIONSVIEW_EXPORT KexiRelationView : public QScrollView +{ + Q_OBJECT + + public: + KexiRelationView(QWidget *parent, const char *name=0); + virtual ~KexiRelationView(); + + //! \return a dictionary of added tables + TablesDict* tables() { return &m_tables; } + + /*! Adds a table \a t to the area. This changes only visual representation. + If \a rect is valid, table widget geometry will be initialized. + \return added table container or 0 on failure. + */ + KexiRelationViewTableContainer* addTableContainer(KexiDB::TableSchema *t, + const QRect &rect = QRect()); + + /*! \return table container for table \a t. */ + KexiRelationViewTableContainer * tableContainer(KexiDB::TableSchema *t) const; + + //! Adds a connection \a con to the area. This changes only visual representation. + void addConnection(const SourceConnection& _conn /*, bool interactive=true*/); + + void setReadOnly(bool); + + inline KexiRelationViewConnection* selectedConnection() const { return m_selectedConnection; } + + inline KexiRelationViewTableContainer* focusedTableView() const { return m_focusedTableView; } + + virtual QSize sizeHint() const; + + const ConnectionList* connections() const { return &m_connectionViews; } + +// KexiRelationViewTableContainer* containerForTable(KexiDB::TableSchema* tableSchema); + + signals: + void tableContextMenuRequest( const QPoint& pos ); + void connectionContextMenuRequest( const QPoint& pos ); + void emptyAreaContextMenuRequest( const QPoint& pos ); + void tableViewGotFocus(); + void connectionViewGotFocus(); + void emptyAreaGotFocus(); + void tableHidden(KexiDB::TableSchema& t); + void tablePositionChanged(KexiRelationViewTableContainer*); + void aboutConnectionRemove(KexiRelationViewConnection*); + + public slots: + //! Clears current selection - table/query or connection + void clearSelection(); + + /*! Removes all tables and connections from the view. + Does not emit signals like tableHidden(). */ + void clear(); + + /*! Removes all coonections from the view. */ + void removeAllConnections(); + + /*! Hides all tables except \a tables. */ + void hideAllTablesExcept( KexiDB::TableSchema::List* tables ); + + void slotTableScrolling(const QString&); + + //! removes selected table or connection + void removeSelectedObject(); + + + protected slots: + void containerMoved(KexiRelationViewTableContainer *c); + void slotListUpdate(QObject *s); + void slotTableViewEndDrag(); + void slotTableViewGotFocus(); + + protected: +// /*! executes popup menu at \a pos, or, +// if \a pos not specified: at center of selected table view (if any selected), +// or at center point of the relations view. */ +// void executePopup( QPoint pos = QPoint(-1,-1) ); + + void drawContents(QPainter *p, int cx, int cy, int cw, int ch); + void contentsMousePressEvent(QMouseEvent *ev); + virtual void keyPressEvent(QKeyEvent *ev); + + void recalculateSize(int width, int height); + void stretchExpandSize(); +// void invalidateActions(); +// void clearTableSelection(); +// void clearConnSelection(); + + void hideTable(KexiRelationViewTableContainer* tableView); + void removeConnection(KexiRelationViewConnection *conn); + + TablesDict m_tables; + bool m_readOnly; + ConnectionList m_connectionViews; + KexiRelationViewConnection* m_selectedConnection; + QGuardedPtr<KexiRelationViewTableContainer> m_focusedTableView; +}; + +#endif diff --git a/kexi/widget/relations/kexirelationviewconnection.cpp b/kexi/widget/relations/kexirelationviewconnection.cpp new file mode 100644 index 00000000..2c27de87 --- /dev/null +++ b/kexi/widget/relations/kexirelationviewconnection.cpp @@ -0,0 +1,298 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Lucijan Busch <lucijan@kde.org> + Copyright (C) 2004-2005 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include <qpainter.h> +#include <qpixmap.h> +#include <qcolor.h> +#include <qapplication.h> +#include <qpointarray.h> + +#include <kdebug.h> + +#include <math.h> + +#include "kexirelationview.h" +#include "kexirelationviewtable.h" +#include "kexirelationviewconnection.h" +#include <kexidb/tableschema.h> +#include <kexidb/utils.h> +#include <core/kexi.h> + +//#include "r1.xpm" +//#include "rn.xpm" + +KexiRelationViewConnection::KexiRelationViewConnection( + KexiRelationViewTableContainer *masterTbl, KexiRelationViewTableContainer *detailsTbl, + SourceConnection &c, KexiRelationView *parent) +{ + m_parent = parent; +// kdDebug() << "KexiRelationViewConnection::KexiRelationViewConnection()" << endl; + + m_masterTable = masterTbl; + if(!masterTbl || !detailsTbl) + { + kdDebug() << "KexiRelationViewConnection::KexiRelationViewConnection(): expect sig11" << endl; + kdDebug() << "KexiRelationViewConnection::KexiRelationViewConnection()" << masterTbl << endl; + kdDebug() << "KexiRelationViewConnection::KexiRelationViewConnection()" << detailsTbl << endl; + } + + m_detailsTable = detailsTbl; + m_masterField = c.masterField; + m_detailsField = c.detailsField; + + m_selected = false; +} + +KexiRelationViewConnection::~KexiRelationViewConnection() +{ +} + +void +KexiRelationViewConnection::drawConnection(QPainter *p) +{ + p->setPen(m_parent->palette().active().foreground()); + int sx = m_masterTable->x() + m_masterTable->width() + m_parent->contentsX(); + int sy = m_masterTable->globalY(m_masterField); + int rx = m_detailsTable->x() + m_parent->contentsX(); + int ry = m_detailsTable->globalY(m_detailsField); + + QFont f( Kexi::smallFont( m_parent ) ); + QFontMetrics fm(f); + int side1x=0, side1y=sy - fm.height(), + sideNx=0, sideNy=ry - fm.height(); +//! @todo details char can be also just a '1' for some cases + QChar sideNChar(0x221E); //infinity char + uint sideNCharWidth = 2+2+ fm.width( sideNChar ); + QChar side1Char('1'); + uint side1CharWidth = 2+2+ fm.width( side1Char ); + p->setBrush(p->pen().color()); + + if(m_masterTable->x() < m_detailsTable->x()) + { + //det. side + p->drawLine(rx - sideNCharWidth, ry, rx, ry); + QPointArray pa(3); + pa.setPoint(0, rx - 4, ry - 3); + pa.setPoint(1, rx - 4, ry + 3); + pa.setPoint(2, rx - 1, ry); + p->drawPolygon(pa, true); + + //master side + p->drawLine(sx, sy - 1, sx + side1CharWidth -1, sy - 1); + p->drawLine(sx, sy, sx + side1CharWidth -1, sy); + p->drawLine(sx, sy + 1, sx + side1CharWidth -1, sy + 1); + + side1x = sx; +// side1y = sy - 7; + + sideNx = rx - sideNCharWidth - 1; +// sideNy = ry - 6; + + QPen pen(p->pen()); + if(m_selected) + { + QPen pen(p->pen()); + pen.setWidth(2); + p->setPen(pen); + } + + p->drawLine(sx + side1CharWidth, sy, rx - sideNCharWidth, ry); + + if(m_selected) + { + QPen pen(p->pen()); + pen.setWidth(1); + p->setPen(pen); + } + + } + else + { + int lx = rx + m_detailsTable->width(); + int rx = sx - m_masterTable->width(); + + //det. side + p->drawLine(lx, ry, lx + sideNCharWidth, ry); + QPointArray pa(3); + pa.setPoint(0, lx + 3, ry - 3); + pa.setPoint(1, lx + 3, ry + 3); + pa.setPoint(2, lx, ry); + p->drawPolygon(pa, true); + +// p->drawLine(lx, ry, lx + 8, ry); +// p->drawPoint(lx + 1, ry - 1); +// p->drawPoint(lx + 1, ry + 1); +// p->drawLine(lx + 2, ry - 2, lx + 2, ry + 2); + + //master side + p->drawLine(rx - side1CharWidth +1, sy - 1, rx, sy - 1); + p->drawLine(rx - side1CharWidth +1, sy + 1, rx, sy + 1); + p->drawLine(rx - side1CharWidth +1, sy, rx, sy); + + side1x = rx - side1CharWidth; +// side1y = sy - 7; + + sideNx = lx + 1; +// sideNy = ry - 6; + + if(m_selected) + { + QPen pen(p->pen()); + pen.setWidth(2); + p->setPen(pen); + } + + p->drawLine(lx + sideNCharWidth, ry, rx - side1CharWidth, sy); + + if(m_selected) + { + QPen pen(p->pen()); + pen.setWidth(1); + p->setPen(pen); + } + } + + p->drawText(side1x, side1y, side1CharWidth, fm.height(), Qt::AlignCenter, side1Char); + p->drawText(sideNx, sideNy, sideNCharWidth, fm.height(), Qt::AlignCenter, sideNChar); + //p->drawRect(QRect(connectionRect().topLeft(), QSize(50,50))); +// p->drawPixmap(side1, QPixmap(r1_xpm)); +// p->drawPixmap(sideN, QPixmap(rn_xpm)); +} + +const QRect +KexiRelationViewConnection::connectionRect() +{ + int sx = m_masterTable->x() + m_parent->contentsX(); + int rx = m_detailsTable->x() + m_parent->contentsX(); + int ry = m_detailsTable->globalY(m_detailsField); + int sy = m_masterTable->globalY(m_masterField); + + int width, leftX, rightX; + + if(sx < rx) + { + leftX = sx; + rightX = rx; + width = m_masterTable->width(); + } + else + { + leftX = rx; + rightX = sx; + width = m_detailsTable->width(); + } + + + int dx = QABS((leftX + width) - rightX); + int dy = QABS(sy - ry) + 2; + + int top = QMIN(sy, ry); + int left = leftX + width; + + +// return QRect(sx - 1, sy - 1, (rx + m_detailsTable->width()) - sx + 1, ry - sy + 1); + QRect rect(left - 150, top - 150, dx + 150, dy + 150); +// kdDebug() << "KexiRelationViewConnection::connectionRect():" << m_oldRect << "," << rect << endl; + + m_oldRect = rect; + + return rect; +} + +bool +KexiRelationViewConnection::matchesPoint(const QPoint &p, int tolerance) +{ + QRect we = connectionRect(); + + if(!we.contains(p)) + return false; + + /** get our coordinats + * you know what i mean the x1, y1 is the top point + * and the x2, y2 is the bottom point + * (quite tirvial :) although that was the entrace to the magic + * gate... + */ + + int sx = m_masterTable->x() + m_masterTable->width(); + int sy = m_masterTable->globalY(m_masterField); + int rx = m_detailsTable->x(); + int ry = m_detailsTable->globalY(m_detailsField); + + int x1 = sx + 8; + int y1 = sy; + int x2 = rx - 8; + int y2 = ry; + + if(sx > rx) + { + x1 = m_detailsTable->x() + m_detailsTable->width(); + x2 = m_masterTable->x(); + y2 = sy; + y1 = ry; + } + + /* + here we call pythagoras (the greek math geek :p) + see: http://w1.480.telia.com/%7Eu48019406/geekporn.gif if you don't know + how these people have got sex :) + */ + float mx = x2-x1; + float my = y2-y1; + float mag = sqrt(mx * mx + my * my); + float u = (((p.x() - x1)*(x2 - x1))+((p.y() - y1)*(y2 - y1)))/(mag * mag); + kdDebug() << "KexiRelationViewConnection::matchesPoint(): u: " << u << endl; + + float iX = x1 + u * (x2 - x1); + float iY = y1 + u * (y2 - y1); + kdDebug() << "KexiRelationViewConnection::matchesPoint(): px: " << p.x() << endl; + kdDebug() << "KexiRelationViewConnection::matchesPoint(): py: " << p.y() << endl; + kdDebug() << "KexiRelationViewConnection::matchesPoint(): ix: " << iX << endl; + kdDebug() << "KexiRelationViewConnection::matchesPoint(): iy: " << iY << endl; + + float dX = iX - p.x(); + float dY = iY - p.y(); + + kdDebug() << "KexiRelationViewConnection::matchesPoint(): dx: " << dX << endl; + kdDebug() << "KexiRelationViewConnection::matchesPoint(): dy: " << dY << endl; + + float distance = sqrt(dX * dX + dY * dY); + kdDebug() << "KexiRelationViewConnection::matchesPoint(): distance: " << distance << endl; + + if(distance <= tolerance) + return true; + + return false; +} + +QString +KexiRelationViewConnection::toString() const +{ + QString str; +/*! @todo what about query? */ + if (m_masterTable && m_masterTable->schema()->table()) { + str += (QString(m_masterTable->schema()->name()) + "." + m_masterField); + } + if (m_detailsTable && m_detailsTable->schema()->table()) { + str += " - "; + str += (QString(m_detailsTable->schema()->name()) + "." + m_detailsField); + } + return str; +} diff --git a/kexi/widget/relations/kexirelationviewconnection.h b/kexi/widget/relations/kexirelationviewconnection.h new file mode 100644 index 00000000..699fdf4f --- /dev/null +++ b/kexi/widget/relations/kexirelationviewconnection.h @@ -0,0 +1,75 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Lucijan Busch <lucijan@kde.org> + Copyright (C) 2004 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIRELATIONVIEWCONNECTION_H +#define KEXIRELATIONVIEWCONNECTION_H + +#include <qstring.h> +#include <qguardedptr.h> + +class QPainter; +class KexiRelationViewTableContainer; +class KexiRelationView; + +class KEXIRELATIONSVIEW_EXPORT KexiRelationViewConnection +{ + public: + + KexiRelationViewConnection(KexiRelationViewTableContainer *masterTbl, + KexiRelationViewTableContainer *detailsTbl, struct SourceConnection &s, KexiRelationView *parent); + ~KexiRelationViewConnection(); + + + /* + C++PROGRAMMIERER bestehen darauf, da�der Elefant eine Klasse sei, + und somit schlie�ich seine Fang-Methoden selbst mitzubringen habe. + + http://www.c-plusplus.de ;) + */ + void drawConnection(QPainter *p); + + bool selected() { return m_selected; } + void setSelected(bool s) { m_selected = s; } + + const QRect connectionRect(); + const QRect oldRect() const { return m_oldRect; } + + KexiRelationViewTableContainer *masterTable() { return m_masterTable; } + KexiRelationViewTableContainer *detailsTable() { return m_detailsTable; } + QString masterField() const { return m_masterField; } + QString detailsField() const { return m_detailsField; } + + + bool matchesPoint(const QPoint &p, int tolerance=3); +// SourceConnection connection() { return m_conn; } + + QString toString() const; + + private: + QGuardedPtr<KexiRelationViewTableContainer> m_masterTable; + QGuardedPtr<KexiRelationViewTableContainer> m_detailsTable; + QString m_masterField; + QString m_detailsField; + QRect m_oldRect; + bool m_selected; + QGuardedPtr<KexiRelationView> m_parent; +}; + +#endif diff --git a/kexi/widget/relations/kexirelationviewtable.cpp b/kexi/widget/relations/kexirelationviewtable.cpp new file mode 100644 index 00000000..97beaa87 --- /dev/null +++ b/kexi/widget/relations/kexirelationviewtable.cpp @@ -0,0 +1,429 @@ +/* This file is part of the KDE project + Copyright (C) 2002, 2003 Lucijan Busch <lucijan@gmx.at> + Copyright (C) 2003-2007 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include <stdlib.h> + +#include <qheader.h> +#include <qlayout.h> +#include <qlabel.h> +#include <qpushbutton.h> +#include <qcursor.h> +#include <qpoint.h> +#include <qapplication.h> +#include <qbitmap.h> +#include <qstyle.h> + +#include <kdebug.h> +#include <kiconloader.h> +#include <kdeversion.h> +#include <kconfig.h> +#include <kglobalsettings.h> + +#include <kexidb/tableschema.h> +#include <kexidb/utils.h> +#include <kexidragobjects.h> +#include "kexirelationviewtable.h" +#include "kexirelationview.h" + +KexiRelationViewTableContainer::KexiRelationViewTableContainer( + KexiRelationView *parent, KexiDB::TableOrQuerySchema *schema) + : QFrame(parent,"KexiRelationViewTableContainer" ) +// , m_table(t) + , m_parent(parent) +// , m_mousePressed(false) +{ + +// setFixedSize(100, 150); +//js: resize(100, 150); + //setMouseTracking(true); + + setFrameStyle( QFrame::WinPanel | QFrame::Raised ); + + QVBoxLayout *lyr = new QVBoxLayout(this,4,1); //js: using Q*BoxLayout is a good idea + + m_tableHeader = new KexiRelationViewTableContainerHeader(schema->name(), this); + + m_tableHeader->unsetFocus(); + m_tableHeader->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed)); + lyr->addWidget(m_tableHeader); + connect(m_tableHeader,SIGNAL(moved()),this,SLOT(moved())); + connect(m_tableHeader, SIGNAL(endDrag()), this, SIGNAL(endDrag())); + + m_tableView = new KexiRelationViewTable(schema, parent, this, "KexiRelationViewTable"); + //m_tableHeader->setFocusProxy( m_tableView ); + m_tableView->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum)); + + m_tableView->setMaximumSize( m_tableView->sizeHint() ); + +// m_tableView->resize( m_tableView->sizeHint() ); + lyr->addWidget(m_tableView, 0); + connect(m_tableView, SIGNAL(tableScrolling()), this, SLOT(moved())); + connect(m_tableView, SIGNAL(contextMenu(KListView*, QListViewItem*, const QPoint&)), + this, SLOT(slotContextMenu(KListView*, QListViewItem*, const QPoint&))); +} + +KexiRelationViewTableContainer::~KexiRelationViewTableContainer() +{ +} + +KexiDB::TableOrQuerySchema* KexiRelationViewTableContainer::schema() const +{ + return m_tableView->schema(); +} + +void KexiRelationViewTableContainer::slotContextMenu(KListView *, QListViewItem *, const QPoint &p) +{ +// m_parent->executePopup(p); + emit contextMenuRequest( p ); +} + +void KexiRelationViewTableContainer::moved() { +// kdDebug()<<"finally emitting moved"<<endl; + emit moved(this); +} + +int KexiRelationViewTableContainer::globalY(const QString &field) +{ +// kdDebug() << "KexiRelationViewTableContainer::globalY()" << endl; +// QPoint o = mapFromGlobal(QPoint(0, (m_tableView->globalY(field))/*+m_parent->contentsY()*/)); + + QPoint o(0, (m_tableView->globalY(field)) + m_parent->contentsY()); +// kdDebug() << "KexiRelationViewTableContainer::globalY() db2" << endl; + return m_parent->viewport()->mapFromGlobal(o).y(); +} + +#if 0//js +QSize KexiRelationViewTableContainer::sizeHint() +{ +#ifdef Q_WS_WIN + QSize s = m_tableView->sizeHint() + + QSize( 2 * 5 , m_tableHeader->height() + 2 * 5 ); +#else + QSize s = m_tableView->sizeHint(); + s.setWidth(s.width() + 4); + s.setHeight(m_tableHeader->height() + s.height()); +#endif + return s; +} +#endif + +void KexiRelationViewTableContainer::setFocus() +{ + kdDebug() << "SET FOCUS" << endl; + //select 1st: + if (m_tableView->firstChild()) { + if (!m_tableView->selectedItems().first()) + m_tableView->setSelected( m_tableView->firstChild(), true ); + } + m_tableHeader->setFocus(); + m_tableView->setFocus(); +/* QPalette p = qApp->palette(); + p.setColor( QPalette::Active, QColorGroup::Highlight, KGlobalSettings::highlightColor() ); + p.setColor( QPalette::Active, QColorGroup::HighlightedText, KGlobalSettings::highlightedTextColor() ); + m_tableView->setPalette(p);*/ + + raise(); + repaint(); + emit gotFocus(); +} + +void KexiRelationViewTableContainer::unsetFocus() +{ + kdDebug() << "UNSET FOCUS" << endl; +// if (m_tableView->selectedItem()) //unselect item if was selected +// m_tableView->setSelected(m_tableView->selectedItem(), false); +// m_tableView->clearSelection(); + m_tableHeader->unsetFocus(); + + m_tableView->clearSelection(); + +// m_tableView->unsetPalette(); +/* QPalette p = m_tableView->palette(); +// p.setColor( QPalette::Active, QColorGroup::Highlight, KGlobalSettings::highlightColor() ); +// p.setColor( QPalette::Active, QColorGroup::HighlightedText, KGlobalSettings::highlightedTextColor() ); + p.setColor( QPalette::Active, QColorGroup::Highlight, p.color(QPalette::Active, QColorGroup::Background ) ); +// p.setColor( QPalette::Active, QColorGroup::Highlight, gray ); + p.setColor( QPalette::Active, QColorGroup::HighlightedText, p.color(QPalette::Active, QColorGroup::Foreground ) ); +// p.setColor( QPalette::Active, QColorGroup::Highlight, green ); +// p.setColor( QPalette::Active, QColorGroup::HighlightedText, blue ); + m_tableView->setPalette(p);*/ + + clearFocus(); + repaint(); +} + + +//END KexiRelationViewTableContainer + +//============================================================================ +//BEGIN KexiRelatoinViewTableContainerHeader + +KexiRelationViewTableContainerHeader::KexiRelationViewTableContainerHeader( + const QString& text,QWidget *parent) + :QLabel(text,parent),m_dragging(false) +{ + setMargin(1); + m_activeBG = KGlobalSettings::activeTitleColor(); + m_activeFG = KGlobalSettings::activeTextColor(); + m_inactiveBG = KGlobalSettings::inactiveTitleColor(); + m_inactiveFG = KGlobalSettings::inactiveTextColor(); + + installEventFilter(this); +} + +KexiRelationViewTableContainerHeader::~KexiRelationViewTableContainerHeader() +{ +} + +void KexiRelationViewTableContainerHeader::setFocus() +{ + setPaletteBackgroundColor(m_activeBG); + setPaletteForegroundColor(m_activeFG); +} + +void KexiRelationViewTableContainerHeader::unsetFocus() +{ + setPaletteBackgroundColor(m_inactiveBG); + setPaletteForegroundColor(m_inactiveFG); +} + +bool KexiRelationViewTableContainerHeader::eventFilter(QObject *, QEvent *ev) +{ + if (ev->type()==QEvent::MouseMove) + { + if (m_dragging && static_cast<QMouseEvent*>(ev)->state()==Qt::LeftButton) { + int diffX,diffY; + diffX=static_cast<QMouseEvent*>(ev)->globalPos().x()-m_grabX; + diffY=static_cast<QMouseEvent*>(ev)->globalPos().y()-m_grabY; + if ((abs(diffX)>2) || (abs(diffY)>2)) + { + QPoint newPos=parentWidget()->pos()+QPoint(diffX,diffY); +//correct the x position + if (newPos.x()<0) { + m_offsetX+=newPos.x(); + newPos.setX(0); + } + else if (m_offsetX<0) { + m_offsetX+=newPos.x(); + if (m_offsetX>0) { + newPos.setX(m_offsetX); + m_offsetX=0; + } + else newPos.setX(0); + } +//correct the y position + if (newPos.y()<0) { + m_offsetY+=newPos.y(); + newPos.setY(0); + } + else + if (m_offsetY<0) { + m_offsetY+=newPos.y(); + if (m_offsetY>0) { + newPos.setY(m_offsetY); + m_offsetY=0; + } + else newPos.setY(0); + } +//move and update helpers + + parentWidget()->move(newPos); + m_grabX=static_cast<QMouseEvent*>(ev)->globalPos().x(); + m_grabY=static_cast<QMouseEvent*>(ev)->globalPos().y(); +// kdDebug()<<"HEADER:emitting moved"<<endl; + emit moved(); + } + return true; + } + } + return false; +} + +void KexiRelationViewTableContainerHeader::mousePressEvent(QMouseEvent *ev) { + kdDebug()<<"KexiRelationViewTableContainerHeader::Mouse Press Event"<<endl; + parentWidget()->setFocus(); + ev->accept(); + if (ev->button()==Qt::LeftButton) { + m_dragging=true; + m_grabX=ev->globalPos().x(); + m_grabY=ev->globalPos().y(); + m_offsetX=0; + m_offsetY=0; + setCursor(Qt::SizeAllCursor); + return; + } + if (ev->button()==Qt::RightButton) { + emit static_cast<KexiRelationViewTableContainer*>(parentWidget()) + ->contextMenuRequest(ev->globalPos()); + } +// QLabel::mousePressEvent(ev); +} + +void KexiRelationViewTableContainerHeader::mouseReleaseEvent(QMouseEvent *ev) { + kdDebug()<<"KexiRelationViewTableContainerHeader::Mouse Release Event"<<endl; + if (m_dragging && ev->button() & Qt::LeftButton) { + setCursor(Qt::ArrowCursor); + m_dragging=false; + emit endDrag(); + } + ev->accept(); +} + +//END KexiRelatoinViewTableContainerHeader + + +//===================================================================================== + +KexiRelationViewTable::KexiRelationViewTable(KexiDB::TableOrQuerySchema* tableOrQuerySchema, + KexiRelationView *view, QWidget *parent, const char *name) + : KexiFieldListView(parent, name, KexiFieldListView::ShowAsterisk) + , m_view(view) +{ + setSchema(tableOrQuerySchema); + header()->hide(); + + connect(this, SIGNAL(dropped(QDropEvent *, QListViewItem *)), this, SLOT(slotDropped(QDropEvent *))); + connect(this, SIGNAL(contentsMoving(int, int)), this, SLOT(slotContentsMoving(int,int))); +} + +KexiRelationViewTable::~KexiRelationViewTable() +{ +} + +QSize KexiRelationViewTable::sizeHint() const +{ + QFontMetrics fm(fontMetrics()); + +// kdDebug() << schema()->name() << " cw=" << columnWidth(0) + fm.width("i") +// << ", " << fm.width(schema()->name()+" ") << endl; + + int maxWidth = -1; + const int iconWidth = IconSize(KIcon::Small) + fm.width("i")+20; + for (QListViewItem *item = firstChild(); item; item = item->nextSibling()) + maxWidth = QMAX(maxWidth, iconWidth + fm.width(item->text(0))); + + const uint rowCount = QMIN( 8, childCount() ); + + QSize s( + QMAX( maxWidth, fm.width(schema()->name()+" ")), + rowCount*firstChild()->totalHeight() + 4 ); + return s; +} + +#if 0 +void KexiRelationViewTable::setReadOnly(bool b) +{ + setAcceptDrops(!b); + viewport()->setAcceptDrops(!b); +} +#endif + +int +KexiRelationViewTable::globalY(const QString &item) +{ + QListViewItem *i = findItem(item, 0); + if (!i) + return -1; + int y = itemRect(i).y() + (itemRect(i).height() / 2); + if (contentsY() > itemPos(i)) + y = 0; + else if (y == 0) + y = height(); + return mapToGlobal(QPoint(0, y)).y(); +} + +bool +KexiRelationViewTable::acceptDrag(QDropEvent *ev) const +{ +// kdDebug() << "KexiRelationViewTable::acceptDrag()" << endl; + QListViewItem *receiver = itemAt(ev->pos() - QPoint(0,contentsY())); + if (!receiver || !KexiFieldDrag::canDecodeSingle(ev)) + return false; + QString sourceMimeType; + QString srcTable; + QString srcField; + if (!KexiFieldDrag::decodeSingle(ev,sourceMimeType,srcTable,srcField)) + return false; + if (sourceMimeType!="kexi/table" && sourceMimeType=="kexi/query") + return false; + QString f = receiver->text(0).stripWhiteSpace(); + if (!srcField.stripWhiteSpace().startsWith("*") && !f.startsWith("*") && ev->source() != (QWidget*)this) + return true; + + return false; +} + +void +KexiRelationViewTable::slotDropped(QDropEvent *ev) +{ + QListViewItem *recever = itemAt(ev->pos() - QPoint(0,contentsY())); + if (!recever || !KexiFieldDrag::canDecodeSingle(ev)) { + ev->ignore(); + return; + } + QString sourceMimeType; + QString srcTable; + QString srcField; + if (!KexiFieldDrag::decodeSingle(ev,sourceMimeType,srcTable,srcField)) + return; + if (sourceMimeType!="kexi/table" && sourceMimeType=="kexi/query") + return; +// kdDebug() << "KexiRelationViewTable::slotDropped() srcfield: " << srcField << endl; + + QString rcvField = recever->text(0); + + SourceConnection s; + s.masterTable = srcTable; + s.detailsTable = schema()->name(); + s.masterField = srcField; + s.detailsField = rcvField; + + m_view->addConnection(s); + + kdDebug() << "KexiRelationViewTable::slotDropped() " << srcTable << ":" << srcField << " " + << schema()->name() << ":" << rcvField << endl; + ev->accept(); +} + +void +KexiRelationViewTable::slotContentsMoving(int,int) +{ + emit tableScrolling(); +} + +void KexiRelationViewTable::contentsMousePressEvent(QMouseEvent *ev) +{ + parentWidget()->setFocus(); + setFocus(); + KListView::contentsMousePressEvent(ev); +// if (ev->button()==Qt::RightButton) +// static_cast<KexiRelationView*>(parentWidget())->executePopup(ev->pos()); +} + +QRect KexiRelationViewTable::drawItemHighlighter(QPainter *painter, QListViewItem *item) +{ + if (painter) { + style().drawPrimitive(QStyle::PE_FocusRect, painter, itemRect(item), colorGroup(), + QStyle::Style_FocusAtBorder); + } + return itemRect(item); +} + +#include "kexirelationviewtable.moc" diff --git a/kexi/widget/relations/kexirelationviewtable.h b/kexi/widget/relations/kexirelationviewtable.h new file mode 100644 index 00000000..cc90e16d --- /dev/null +++ b/kexi/widget/relations/kexirelationviewtable.h @@ -0,0 +1,157 @@ +/* This file is part of the KDE project + Copyright (C) 2002, 2003 Lucijan Busch <lucijan@gmx.at> + Copyright (C) 2003-2007 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIRELATIONVIEWTABLE_H +#define KEXIRELATIONVIEWTABLE_H + +#include <qframe.h> +#include <qstringlist.h> +#include <qlabel.h> +#include <klistview.h> + +#include <widget/kexifieldlistview.h> + +class KexiRelationView; +class KexiRelationViewTable; +class KexiRelationViewTableContainerHeader; + +namespace KexiDB +{ + class TableOrQuerySchema; +} + +class KEXIRELATIONSVIEW_EXPORT KexiRelationViewTableContainer : public QFrame +{ + Q_OBJECT + + public: +// KexiRelationViewTableContainer(KexiRelationView *parent, KexiDB::TableSchema *t); + KexiRelationViewTableContainer( + KexiRelationView *parent, KexiDB::TableOrQuerySchema *schema); + + virtual ~KexiRelationViewTableContainer(); + + int globalY(const QString &field); +// KexiDB::TableSchema *table(); + + KexiRelationViewTable* tableView() const { return m_tableView; } + KexiDB::TableOrQuerySchema* schema() const; + + int right() { return x() + width() - 1; } + int bottom() { return y() + height() - 1; } + + signals: + void moved(KexiRelationViewTableContainer *); + void endDrag(); + void gotFocus(); + void contextMenuRequest(const QPoint& pos); + + public slots: + virtual void setFocus(); + virtual void unsetFocus(); + + protected slots: + void moved(); + void slotContextMenu(KListView *lv, QListViewItem *i, const QPoint& p); + + protected: +// KexiDB::TableSchema *m_table; + KexiRelationViewTableContainerHeader *m_tableHeader; + KexiRelationViewTable *m_tableView; + KexiRelationView *m_parent; + + friend class KexiRelationViewTableContainerHeader; +}; + +/* +class KEXIRELATIONSVIEW_EXPORT KexiRelationViewTableItem : public KListViewItem +{ + public: + KexiRelationViewTableItem(QListView *parent, QListViewItem *after, + QString key, QString field); + virtual void paintFocus ( QPainter * p, const QColorGroup & cg, const QRect & r ); +};*/ + + +class KEXIRELATIONSVIEW_EXPORT KexiRelationViewTable : public KexiFieldListView +{ + Q_OBJECT + + public: + KexiRelationViewTable(KexiDB::TableOrQuerySchema* tableOrQuerySchema, + KexiRelationView *view, QWidget *parent, const char *name = 0); +// KexiRelationViewTable(QWidget *parent, KexiRelationView *view, KexiDB::TableSchema *t, const char *name=0); + virtual ~KexiRelationViewTable(); + +// KexiDB::TableSchema *table() const { return m_table; }; + int globalY(const QString &item); +// void setReadOnly(bool); + virtual QSize sizeHint() const; + + signals: + void tableScrolling(); + + protected slots: + void slotDropped(QDropEvent *e); + void slotContentsMoving(int, int); +// void slotItemDoubleClicked( QListViewItem *i, const QPoint &, int ); + + protected: + virtual void contentsMousePressEvent( QMouseEvent * e ); + virtual bool acceptDrag(QDropEvent *e) const; +//moved virtual QDragObject *dragObject(); + virtual QRect drawItemHighlighter(QPainter *painter, QListViewItem *item); + + private: +// QStringList m_fieldList; +// KexiDB::TableSchema *m_table; + KexiRelationView *m_view; +// QPixmap m_keyIcon, m_noIcon; +}; + +class KEXIRELATIONSVIEW_EXPORT KexiRelationViewTableContainerHeader : public QLabel +{ + Q_OBJECT + public: + KexiRelationViewTableContainerHeader(const QString& text,QWidget *parent); + virtual ~KexiRelationViewTableContainerHeader(); + + virtual void setFocus(); + virtual void unsetFocus(); + + signals: + void moved(); + void endDrag(); + + protected: + bool eventFilter(QObject *obj, QEvent *ev); + void mousePressEvent(QMouseEvent *ev); + void mouseReleaseEvent(QMouseEvent *ev); + + bool m_dragging; + int m_grabX; + int m_grabY; + int m_offsetX; + int m_offsetY; + + QColor m_activeBG, m_activeFG, m_inactiveBG, m_inactiveFG; +}; + +#endif diff --git a/kexi/widget/relations/kexirelationwidget.cpp b/kexi/widget/relations/kexirelationwidget.cpp new file mode 100644 index 00000000..14ec4ce0 --- /dev/null +++ b/kexi/widget/relations/kexirelationwidget.cpp @@ -0,0 +1,425 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Lucijan Busch <lucijan@gmx.at> + Copyright (C) 2003 Joseph Wenninger<jowenn@kde.org> + Copyright (C) 2003-2004 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "kexirelationwidget.h" + +#include <qlayout.h> +#include <qlistbox.h> +#include <qpushbutton.h> +#include <qtimer.h> + +#include <kcombobox.h> +#include <klocale.h> +#include <kdebug.h> +#include <kiconloader.h> +#include <kpushbutton.h> + +#include <kexidb/connection.h> +#include <kexidb/utils.h> + +#include <kexiproject.h> +#include <keximainwindow.h> +#include "kexirelationview.h" +#include "kexirelationviewtable.h" +#include "kexirelationviewconnection.h" + +KexiRelationWidget::KexiRelationWidget(KexiMainWindow *win, QWidget *parent, + const char *name) + : KexiViewBase(win, parent, name) + , m_win(win) +{ + m_conn = m_win->project()->dbConnection(); + + QHBoxLayout *hlyr = new QHBoxLayout(0); + QGridLayout *g = new QGridLayout(this); + g->addLayout( hlyr, 0, 0 ); + + m_tableCombo = new KComboBox(this, "tables_combo"); + m_tableCombo->setMinimumWidth(QFontMetrics(font()).width("w")*20); + QLabel *lbl = new QLabel(m_tableCombo, i18n("Table")+": ", this); + lbl->setIndent(3); + m_tableCombo->setInsertionPolicy(QComboBox::NoInsertion); + hlyr->addWidget(lbl); + hlyr->addWidget(m_tableCombo); + m_tableCombo->setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred)); + fillTablesCombo(); + + m_btnAdd = new KPushButton(i18n("&Add"), this); + hlyr->addWidget(m_btnAdd); + hlyr->addStretch(1); + connect(m_btnAdd, SIGNAL(clicked()), this, SLOT(slotAddTable())); + + m_relationView = new KexiRelationView(this, "relation_view"); + setViewWidget(m_relationView); + g->addWidget(m_relationView, 1, 0); + //m_relationView->setFocus(); + + //actions + m_tableQueryPopup = new KPopupMenu(this, "m_popup"); + m_tableQueryPopupTitleID = m_tableQueryPopup->insertTitle(SmallIcon("table"), ""); + connect(m_tableQueryPopup, SIGNAL(aboutToShow()), this, SLOT(aboutToShowPopupMenu())); + + m_connectionPopup = new KPopupMenu(this, "m_connectionPopup"); + m_connectionPopupTitleID = m_connectionPopup->insertTitle(""); + connect(m_connectionPopup, SIGNAL(aboutToShow()), this, SLOT(aboutToShowPopupMenu())); + + m_areaPopup = new KPopupMenu(this, "m_areaPopup"); + + m_openSelectedTableAction = new KAction(i18n("&Open Table"), SmallIcon("fileopen"), KShortcut(), + this, SLOT(openSelectedTable()), this, "relationsview_openTable"); + m_openSelectedTableAction->plug( m_tableQueryPopup ); + m_designSelectedTableAction = new KAction(i18n("&Design Table"), SmallIcon("edit"), KShortcut(), + this, SLOT(designSelectedTable()), this, "relationsview_designTable"); + m_designSelectedTableAction->plug( m_tableQueryPopup ); + m_tableQueryPopup->insertSeparator(); + + KAction* hide_action = plugSharedAction("edit_delete", i18n("&Hide Table"), m_tableQueryPopup); + hide_action->setIconSet(QIconSet()); + + plugSharedAction("edit_delete",m_connectionPopup); + plugSharedAction("edit_delete",this, SLOT(removeSelectedObject())); + + connect(m_relationView, SIGNAL(tableViewGotFocus()), + this, SLOT(tableViewGotFocus())); + connect(m_relationView, SIGNAL(connectionViewGotFocus()), + this, SLOT(connectionViewGotFocus())); + connect(m_relationView, SIGNAL(emptyAreaGotFocus()), + this, SLOT(emptyAreaGotFocus())); + connect(m_relationView, SIGNAL(tableContextMenuRequest( const QPoint& )), + this, SLOT(tableContextMenuRequest( const QPoint& ))); + connect(m_relationView, SIGNAL(connectionContextMenuRequest( const QPoint& )), + this, SLOT(connectionContextMenuRequest( const QPoint& ))); + connect(m_relationView, SIGNAL(tableHidden(KexiDB::TableSchema&)), + this, SLOT(slotTableHidden(KexiDB::TableSchema&))); + connect(m_relationView, SIGNAL(tablePositionChanged(KexiRelationViewTableContainer*)), + this, SIGNAL(tablePositionChanged(KexiRelationViewTableContainer*))); + connect(m_relationView, SIGNAL(aboutConnectionRemove(KexiRelationViewConnection*)), + this, SIGNAL(aboutConnectionRemove(KexiRelationViewConnection*))); + +#if 0 + if(!embedd) + { +/*todo setContextHelp(i18n("Relations"), i18n("To create a relationship simply drag the source field onto the target field. " + "An arrowhead is used to show which table is the parent (master) and which table is the child (slave) in the relationship."));*/ + } +#endif +// else +//js: while embedding means read-only? m_relationView->setReadOnly(true); + +#ifdef TESTING_KexiRelationWidget + for (int i=0;i<(int)m_db->tableNames().count();i++) + QTimer::singleShot(100,this,SLOT(slotAddTable())); +#endif + + invalidateActions(); +} + +KexiRelationWidget::~KexiRelationWidget() +{ +} + +TablesDict* KexiRelationWidget::tables() const +{ + return m_relationView->tables(); +} + +KexiRelationViewTableContainer* KexiRelationWidget::table(const QString& name) const +{ + return m_relationView->tables()->find( name ); +} + +const ConnectionList* KexiRelationWidget::connections() const +{ + return m_relationView->connections(); +} + +void +KexiRelationWidget::slotAddTable() +{ + if (m_tableCombo->currentItem()==-1) + return; + QString tname = m_tableCombo->text(m_tableCombo->currentItem()); + KexiDB::TableSchema *t = m_conn->tableSchema(tname); + addTable(t); +} + +void +KexiRelationWidget::addTable(KexiDB::TableSchema *t, const QRect &rect) +{ + if (!t) + return; + if (!m_relationView->tableContainer(t)) { + KexiRelationViewTableContainer *c = m_relationView->addTableContainer(t, rect); + kdDebug() << "KexiRelationWidget::slotAddTable(): adding table " << t->name() << endl; + if (!c) + return; + connect(c->tableView(), SIGNAL(doubleClicked(QListViewItem*,const QPoint&,int)), + this, SLOT(slotTableFieldDoubleClicked(QListViewItem*,const QPoint&,int))); + } + + const QString tname = t->name().lower(); + const int count = m_tableCombo->count(); + int i = 0; + for (; i < count; i++ ) { + if (m_tableCombo->text(i).lower() == tname ) + break; + } + if (i<count) { + int oi = m_tableCombo->currentItem(); + kdDebug()<<"KexiRelationWidget::slotAddTable(): removing a table from the combo box"<<endl; + m_tableCombo->removeItem(i); + if (m_tableCombo->count()>0) { + if (oi>=m_tableCombo->count()) { + oi=m_tableCombo->count()-1; + } + m_tableCombo->setCurrentItem(oi); + } + else { + m_tableCombo->setEnabled(false); + m_btnAdd->setEnabled(false); + } + } + emit tableAdded(*t); +} + +void +KexiRelationWidget::addConnection(const SourceConnection& conn) +{ + m_relationView->addConnection(conn); +} + +void +KexiRelationWidget::addTable(const QString& t) +{ + for(int i=0; i < m_tableCombo->count(); i++) + { + if(m_tableCombo->text(i) == t) + { + m_tableCombo->setCurrentItem(i); + slotAddTable(); + } + } +} + +void KexiRelationWidget::tableViewGotFocus() +{ +// if (m_relationView->focusedTableView == sender()) +// return; +// kdDebug() << "GOT FOCUS!" <<endl; +// clearSelection(); +// if (m_focusedTableView) +// m_focusedTableView->unsetFocus(); +// m_focusedTableView = (KexiRelationViewTableContainer*)sender(); + invalidateActions(); +} + +void KexiRelationWidget::connectionViewGotFocus() +{ + invalidateActions(); +} + +void KexiRelationWidget::emptyAreaGotFocus() +{ + invalidateActions(); +} + +void KexiRelationWidget::tableContextMenuRequest(const QPoint& pos) +{ + invalidateActions(); + executePopup( pos ); +} + +void KexiRelationWidget::connectionContextMenuRequest(const QPoint& pos) +{ + invalidateActions(); + executePopup( pos ); +// m_connectionPopup->exec(pos); +} + +void KexiRelationWidget::emptyAreaContextMenuRequest( const QPoint& /*pos*/ ) +{ + invalidateActions(); + //TODO +} + +void KexiRelationWidget::invalidateActions() +{ + setAvailable("edit_delete", m_relationView->selectedConnection() || m_relationView->focusedTableView()); +} + +void KexiRelationWidget::executePopup( QPoint pos ) +{ + if (pos==QPoint(-1,-1)) { + pos = mapToGlobal( + m_relationView->focusedTableView() ? m_relationView->focusedTableView()->pos() + m_relationView->focusedTableView()->rect().center() : rect().center() ); + } + if (m_relationView->focusedTableView()) + m_tableQueryPopup->exec(pos); + else if (m_relationView->selectedConnection()) + m_connectionPopup->exec(pos); +} + +void KexiRelationWidget::removeSelectedObject() +{ + m_relationView->removeSelectedObject(); +} + +void KexiRelationWidget::openSelectedTable() +{ +/*! @todo what about query? */ + if (!m_relationView->focusedTableView() || !m_relationView->focusedTableView()->schema()->table()) + return; + bool openingCancelled; + m_win->openObject("kexi/table", m_relationView->focusedTableView()->schema()->name(), + Kexi::DataViewMode, openingCancelled); +} + +void KexiRelationWidget::designSelectedTable() +{ +/*! @todo what about query? */ + if (!m_relationView->focusedTableView() || !m_relationView->focusedTableView()->schema()->table()) + return; + bool openingCancelled; + m_win->openObject("kexi/table", m_relationView->focusedTableView()->schema()->name(), + Kexi::DesignViewMode, openingCancelled); +} + +QSize KexiRelationWidget::sizeHint() const +{ + return m_relationView->sizeHint(); +} + +void KexiRelationWidget::slotTableHidden(KexiDB::TableSchema &table) +{ + const QString &t = table.name().lower(); + int i; + for (i=0; i<m_tableCombo->count() && t > m_tableCombo->text(i).lower(); i++) + ; + m_tableCombo->insertItem(table.name(), i); + if (!m_tableCombo->isEnabled()) { + m_tableCombo->setCurrentItem(0); + m_tableCombo->setEnabled(true); + m_btnAdd->setEnabled(true); + } + + emit tableHidden(table); +} + +void KexiRelationWidget::aboutToShowPopupMenu() +{ +/*! @todo what about query? */ + if (m_relationView->focusedTableView() && m_relationView->focusedTableView()->schema()->table()) { + m_tableQueryPopup->changeTitle(m_tableQueryPopupTitleID, SmallIcon("table"), + QString(m_relationView->focusedTableView()->schema()->name()) + " : " + i18n("Table")); + } + else if (m_relationView->selectedConnection()) { + m_connectionPopup->changeTitle( m_connectionPopupTitleID, + m_relationView->selectedConnection()->toString() + " : " + i18n("Relationship") ); + } +} + +void +KexiRelationWidget::slotTableFieldDoubleClicked(QListViewItem *i,const QPoint&,int) +{ + if (!sender()->isA("KexiRelationViewTable")) + return; + const KexiRelationViewTable* t = static_cast<const KexiRelationViewTable*>(sender()); + const QStringList selectedFieldNames( t->selectedFieldNames() ); + if (selectedFieldNames.count()==1) + emit tableFieldDoubleClicked( t->schema()->table(), selectedFieldNames.first() ); +} + +void +KexiRelationWidget::clear() +{ + m_relationView->clear(); + fillTablesCombo(); +} + +/*! Removes all coonections from the view. */ +void KexiRelationWidget::removeAllConnections() +{ + m_relationView->removeAllConnections(); +} + +void +KexiRelationWidget::fillTablesCombo() +{ + m_tableCombo->clear(); + QStringList tmp = m_conn->tableNames(); + tmp.sort(); + m_tableCombo->insertStringList(tmp); +} + +void +KexiRelationWidget::objectCreated(const QCString &mime, const QCString& name) +{ + if (mime=="kexi/table" || mime=="kexi/query") { +//! @todo query? + m_tableCombo->insertItem(QString(name)); + m_tableCombo->listBox()->sort(); + } +} + +void +KexiRelationWidget::objectDeleted(const QCString &mime, const QCString& name) +{ + if (mime=="kexi/table" || mime=="kexi/query") { + QString strName(name); + for (int i=0; i<m_tableCombo->count(); i++) { +//! @todo query? + if (m_tableCombo->text(i)==strName) { + m_tableCombo->removeItem(i); + if (m_tableCombo->currentItem()==i) { + if (i==(m_tableCombo->count()-1)) + m_tableCombo->setCurrentItem(i-1); + else + m_tableCombo->setCurrentItem(i); + } + break; + } + } + } +} + +void +KexiRelationWidget::objectRenamed(const QCString &mime, const QCString& name, const QCString& newName) +{ + if (mime=="kexi/table" || mime=="kexi/query") { + QString strName(name); + for (int i=0; i<m_tableCombo->count(); i++) { +//! @todo query? + if (m_tableCombo->text(i)==strName) { + m_tableCombo->changeItem(QString(newName), i); + m_tableCombo->listBox()->sort(); + break; + } + } + } +} + +void +KexiRelationWidget::hideAllTablesExcept( KexiDB::TableSchema::List* tables ) +{ + m_relationView->hideAllTablesExcept(tables); +} + +#include "kexirelationwidget.moc" diff --git a/kexi/widget/relations/kexirelationwidget.h b/kexi/widget/relations/kexirelationwidget.h new file mode 100644 index 00000000..3beb0b34 --- /dev/null +++ b/kexi/widget/relations/kexirelationwidget.h @@ -0,0 +1,137 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Lucijan Busch <lucijan@gmx.at> + Copyright (C) 2003-2004 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIRELATIONWIDGET_H +#define KEXIRELATIONWIDGET_H + +//#include <qwidget.h> +//#include "kexiactionproxy.h" +#include "kexiviewbase.h" +#include "kexirelationview.h" + +class KComboBox; +class KPushButton; +class KPopupMenu; +class KAction; +class QListViewItem; + +class KexiMainWindow; + +namespace KexiDB +{ + class Connection; + class TableSchema; + class Reference; +} + +class KEXIRELATIONSVIEW_EXPORT KexiRelationWidget : public KexiViewBase +{ + Q_OBJECT + + public: + KexiRelationWidget(KexiMainWindow *win, QWidget *parent, const char *name=0); + virtual ~KexiRelationWidget(); + + //! \return a dictionary of added tables + TablesDict* tables() const; + KexiRelationViewTableContainer* table(const QString& name) const; + const ConnectionList* connections() const; + +// KexiRelationView *relationView() const { return m_relationView; } + void addTable(const QString& t); + +// void openTable(KexiDB::TableSchema* table, bool designMode); + + virtual QSize sizeHint() const; + + /*! Used to add newly created object information to the combo box. */ + void objectCreated(const QCString &mime, const QCString& name); + void objectDeleted(const QCString &mime, const QCString& name); + void objectRenamed(const QCString &mime, const QCString& name, const QCString& newName); + + signals: + void tableAdded(KexiDB::TableSchema& t); + void tableHidden(KexiDB::TableSchema& t); + void tablePositionChanged(KexiRelationViewTableContainer*); + void aboutConnectionRemove(KexiRelationViewConnection*); + void tableFieldDoubleClicked( KexiDB::TableSchema* table, const QString& fieldName ); + + public slots: + /*! Adds a table \a t to the area. This changes only visual representation. + If \a rect is valid, table widget rgeometry will be initialized. + */ + void addTable(KexiDB::TableSchema *t, const QRect &rect = QRect()); + + //! Adds a connection \a con to the area. This changes only visual representation. + void addConnection(const SourceConnection& conn); + + void removeSelectedObject(); + + /*! Removes all tables and coonections from the widget. */ + void clear(); + + /*! Removes all coonections from the view. */ + void removeAllConnections(); + + /*! Hides all tables except \a tables. */ + void hideAllTablesExcept( KexiDB::TableSchema::List* tables ); + + protected slots: + void slotAddTable(); + void tableViewGotFocus(); + void connectionViewGotFocus(); + void emptyAreaGotFocus(); + void tableContextMenuRequest(const QPoint& pos); + void connectionContextMenuRequest(const QPoint& pos); + void emptyAreaContextMenuRequest( const QPoint& pos ); + void openSelectedTable(); + void designSelectedTable(); + void slotTableHidden(KexiDB::TableSchema &table); + void aboutToShowPopupMenu(); + void slotTableFieldDoubleClicked(QListViewItem *i,const QPoint&,int); + + protected: + /*! executes popup menu at \a pos, or, + if \a pos not specified: at center of selected table view (if any selected), + or at center point of the relations view. */ + void executePopup( QPoint pos = QPoint(-1,-1) ); + + //! Invalidates all actions availability. + void invalidateActions(); + + //! Fills table's combo box with all available table names. + void fillTablesCombo(); + + private: + KexiMainWindow *m_win; + KComboBox *m_tableCombo; + KPushButton *m_btnAdd; + KexiRelationView *m_relationView; + KexiDB::Connection *m_conn; + + KPopupMenu *m_tableQueryPopup //over table/query + , *m_connectionPopup //over connection + , *m_areaPopup; //over outer area + KAction *m_openSelectedTableAction, *m_designSelectedTableAction; + + int m_tableQueryPopupTitleID, m_connectionPopupTitleID; +}; + +#endif diff --git a/kexi/widget/relations/r1.xpm b/kexi/widget/relations/r1.xpm new file mode 100644 index 00000000..11b8dc66 --- /dev/null +++ b/kexi/widget/relations/r1.xpm @@ -0,0 +1,10 @@ +/* XPM */ +static const char * r1_xpm[] = { +"2 5 2 1", +" c None", +". c #000000", +" .", +"..", +" .", +" .", +" ."}; diff --git a/kexi/widget/relations/rn.xpm b/kexi/widget/relations/rn.xpm new file mode 100644 index 00000000..d721e5f5 --- /dev/null +++ b/kexi/widget/relations/rn.xpm @@ -0,0 +1,9 @@ +/* XPM */ +static const char * rn_xpm[] = { +"7 4 2 1", +" c None", +". c #000000", +" .. .. ", +". . .", +". . .", +" .. .. "}; diff --git a/kexi/widget/tableview/Makefile.am b/kexi/widget/tableview/Makefile.am new file mode 100644 index 00000000..69bb400d --- /dev/null +++ b/kexi/widget/tableview/Makefile.am @@ -0,0 +1,49 @@ +include $(top_srcdir)/kexi/Makefile.global + +lib_LTLIBRARIES = libkexidatatable.la +libkexidatatable_la_SOURCES = kexidataawareobjectiface.cpp \ + kexitableview.cpp kexitableview_p.cpp kexidatatableview.cpp \ + kexicelleditorfactory.cpp kexitableedit.cpp \ + kexitableviewheader.cpp kexitableitem.cpp kexitableviewdata.cpp \ + kexidatetableedit.cpp kexitimetableedit.cpp kexidatetimetableedit.cpp \ + kexiinputtableedit.cpp kexiblobtableedit.cpp kexibooltableedit.cpp \ + kexicomboboxbase.cpp kexicomboboxtableedit.cpp kexicomboboxpopup.cpp \ + kexidataawarepropertyset.cpp kexitextformatter.cpp + +noinst_HEADERS = kexitableview_p.h + +libkexidatatable_la_LDFLAGS = $(all_libraries) $(VER_INFO) -Wnounresolved +libkexidatatable_la_LIBADD = $(top_builddir)/kexi/core/libkexicore.la $(top_builddir)/kexi/widget/utils/libkexiguiutils.la \ + $(top_builddir)/lib/koproperty/libkoproperty.la $(LIB_KDEUI) + +#TODO: remove libkexicore link when kexiutils arrive + +SUBDIRS = . + + +# kde_appsdir Where your application's menu entry (.desktop) should go to. +# kde_icondir Where your icon should go to - better use KDE_ICON. +# kde_sounddir Where your sounds should go to. +# kde_htmldir Where your docs should go to. (contains lang subdirs) +# kde_datadir Where you install application data. (Use a subdir) +# kde_locale Where translation files should go to. (contains lang subdirs) +# kde_cgidir Where cgi-bin executables should go to. +# kde_confdir Where config files should go to (system-wide ones with default values). +# kde_mimedir Where mimetypes .desktop files should go to. +# kde_servicesdir Where services .desktop files should go to. +# kde_servicetypesdir Where servicetypes .desktop files should go to. +# kde_toolbardir Where general toolbar icons should go to (deprecated, use KDE_ICON). +# kde_wallpaperdir Where general wallpapers should go to. +# kde_templatesdir Where templates for the "New" menu (Konqueror/KDesktop) should go to. +# kde_bindir Where executables should go to. Use bin_PROGRAMS or bin_SCRIPTS. +# kde_libdir Where shared libraries should go to. Use lib_LTLIBRARIES. +# kde_moduledir Where modules (e.g. parts) should go to. Use kde_module_LTLIBRARIES. +# kde_styledir Where Qt/KDE widget styles should go to (new in KDE 3). +# kde_designerdir Where Qt Designer plugins should go to (new in KDE 3). + +# set the include path for X, qt and KDE +INCLUDES= -I$(top_srcdir)/kexi -I$(top_srcdir)/kexi/core -I$(top_srcdir)/kexi/kexidb \ + -I$(top_srcdir)/lib -I$(top_srcdir)/lib/kofficecore -I$(top_srcdir)/lib/koproperty/ $(all_includes) + +METASOURCES = AUTO + diff --git a/kexi/widget/tableview/autonumber.png b/kexi/widget/tableview/autonumber.png Binary files differnew file mode 100644 index 00000000..23fff353 --- /dev/null +++ b/kexi/widget/tableview/autonumber.png diff --git a/kexi/widget/tableview/kexiblobtableedit.cpp b/kexi/widget/tableview/kexiblobtableedit.cpp new file mode 100644 index 00000000..db0799a4 --- /dev/null +++ b/kexi/widget/tableview/kexiblobtableedit.cpp @@ -0,0 +1,595 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Peter Simonsson <psn@linux.se> + Copyright (C) 2004, 2006 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "kexiblobtableedit.h" + +#include <stdlib.h> + +#include <qdatastream.h> +#include <qfile.h> +#include <qpopupmenu.h> +#include <qtextedit.h> +#include <qlayout.h> +#include <qstatusbar.h> +#include <qlabel.h> +#include <qpixmap.h> +#include <qimage.h> +#include <qpainter.h> +#include <qtooltip.h> +#include <qapplication.h> +#include <qclipboard.h> +#include <qbuffer.h> + +#include <kdebug.h> +#include <ktempfile.h> +#include <kmimetype.h> +#include <kmimemagic.h> +#include <kuserprofile.h> +#include <kservice.h> +#include <kprocess.h> +#include <kopenwith.h> +#include <kurl.h> +#include <karrowbutton.h> +#include <klocale.h> +#include <kfiledialog.h> +#include <kio/job.h> +#include <kglobal.h> +#include <kiconloader.h> +#include <kpopupmenu.h> +#include <kstdaccel.h> + +#include <kexiutils/utils.h> +#include <widget/utils/kexidropdownbutton.h> +#include <widget/utils/kexicontextmenuutils.h> + +//! @internal +class KexiBlobTableEdit::Private +{ +public: + Private() + : popup(0) + , readOnly(false) + , setValueInternalEnabled(true) + { + } + + QByteArray value; + KexiDropDownButton *button; + QSize totalSize; + KexiImageContextMenu *popup; + bool readOnly : 1; //!< cached for slotUpdateActionsAvailabilityRequested() + bool setValueInternalEnabled : 1; //!< used to disable KexiBlobTableEdit::setValueInternal() +}; + +//====================================================== + +KexiBlobTableEdit::KexiBlobTableEdit(KexiTableViewColumn &column, QWidget *parent) + : KexiTableEdit(column, parent) + , d ( new Private() ) +{ + setName("KexiBlobTableEdit"); +// m_proc = 0; +// m_content = 0; + m_hasFocusableWidget = false; + d->button = new KexiDropDownButton( parentWidget() /*usually a viewport*/ ); + d->button->hide(); + QToolTip::add(d->button, i18n("Click to show available actions for this cell")); + + d->popup = new KexiImageContextMenu(this); + d->popup->installEventFilter(this); + if (column.columnInfo) + KexiImageContextMenu::updateTitle( d->popup, column.columnInfo->captionOrAliasOrName(), +//! @todo pixmaplabel icon is hardcoded... + "pixmaplabel" ); + d->button->setPopup( d->popup ); + + //force edit requested to start editing... (this will call slotUpdateActionsAvailabilityRequested()) + //connect(d->popup, SIGNAL(aboutToShow()), this, SIGNAL(editRequested())); + + connect(d->popup, SIGNAL(updateActionsAvailabilityRequested(bool&, bool&)), + this, SLOT(slotUpdateActionsAvailabilityRequested(bool&, bool&))); + + connect(d->popup, SIGNAL(insertFromFileRequested(const KURL&)), + this, SLOT(handleInsertFromFileAction(const KURL&))); + connect(d->popup, SIGNAL(saveAsRequested(const QString&)), + this, SLOT(handleSaveAsAction(const QString&))); + connect(d->popup, SIGNAL(cutRequested()), + this, SLOT(handleCutAction())); + connect(d->popup, SIGNAL(copyRequested()), + this, SLOT(handleCopyAction())); + connect(d->popup, SIGNAL(pasteRequested()), + this, SLOT(handlePasteAction())); + connect(d->popup, SIGNAL(clearRequested()), + this, SLOT(clear())); + connect(d->popup, SIGNAL(showPropertiesRequested()), + this, SLOT(handleShowPropertiesAction())); +} + +KexiBlobTableEdit::~KexiBlobTableEdit() +{ + delete d; +#if 0 + kdDebug() << "KexiBlobTableEdit: Cleaning up..." << endl; + if (m_tempFile) { + m_tempFile->unlink(); + //todo + } + delete m_proc; + m_proc = 0; + kdDebug() << "KexiBlobTableEdit: Ready." << endl; +#endif +} + +//! initializes this editor with \a add value +void KexiBlobTableEdit::setValueInternal(const QVariant& add, bool removeOld) +{ + if (!d->setValueInternalEnabled) + return; + if (removeOld) + d->value = add.toByteArray(); + else //do not add "m_origValue" to "add" as this is QByteArray + d->value = m_origValue.toByteArray(); + +#if 0 //todo? + QByteArray val = m_origValue.toByteArray(); + kdDebug() << "KexiBlobTableEdit: Size of BLOB: " << val.size() << endl; + m_tempFile = new KTempFile(); + m_tempFile->setAutoDelete(true); + kdDebug() << "KexiBlobTableEdit: Creating temporary file: " << m_tempFile->name() << endl; + m_tempFile->dataStream()->writeRawBytes(val.data(), val.size()); + m_tempFile->close(); + delete m_tempFile; + m_tempFile = 0; + + KMimeMagicResult* mmr = KMimeMagic::self()->findFileType(m_tempFile->name()); + kdDebug() << "KexiBlobTableEdit: Mimetype = " << mmr->mimeType() << endl; + + setViewWidget( new QWidget(this) ); +#endif +} + +bool KexiBlobTableEdit::valueIsNull() +{ +//TODO + d->value.size(); + return d->value.isEmpty(); +} + +bool KexiBlobTableEdit::valueIsEmpty() +{ +//TODO + return d->value.isEmpty(); +} + +QVariant +KexiBlobTableEdit::value() +{ + return d->value; +#if 0 + //todo +// ok = true; + + if(m_content && m_content->isModified()) + { + return QVariant(m_content->text()); + } + QByteArray value; + QFile f( m_tempFile->name() ); + f.open(IO_ReadOnly); + QDataStream stream(&f); + char* data = (char*) malloc(f.size()); + value.resize(f.size()); + stream.readRawBytes(data, f.size()); + value.duplicate(data, f.size()); + free(data); + kdDebug() << "KexiBlobTableEdit: Size of BLOB: " << value.size() << endl; + return QVariant(value); +#endif +} + +void KexiBlobTableEdit::paintFocusBorders( QPainter *p, QVariant &, int x, int y, int w, int h ) +{ +// d->currentEditorWidth = w; + if (!d->readOnly && w > d->button->width()) + w -= d->button->width(); + p->drawRect(x, y, w, h); +} + +void +KexiBlobTableEdit::setupContents( QPainter *p, bool focused, const QVariant& val, + QString &txt, int &align, int &x, int &y_offset, int &w, int &h ) +{ + Q_UNUSED(focused); + Q_UNUSED(txt); + Q_UNUSED(align); + +//! @todo optimize: load to m_pixmap, downsize + QPixmap pixmap; + x = 0; + w -= 1; //a place for border + h -= 1; //a place for border + if (p && val.canCast(QVariant::ByteArray) && pixmap.loadFromData(val.toByteArray())) { + KexiUtils::drawPixmap( *p, 0/*lineWidth*/, QRect(x, y_offset, w, h), + pixmap, Qt::AlignCenter, true/*scaledContents*/, true/*keepAspectRatio*/); + } +} + +bool KexiBlobTableEdit::cursorAtStart() +{ + return true; +} + +bool KexiBlobTableEdit::cursorAtEnd() +{ + return true; +} + +void KexiBlobTableEdit::handleInsertFromFileAction(const KURL& url) +{ + if (isReadOnly()) + return; + + QString fileName( url.isLocalFile() ? url.path() : url.prettyURL() ); + + //! @todo download the file if remote, then set fileName properly + QFile f(fileName); + if (!f.open(IO_ReadOnly)) { + //! @todo err msg + return; + } + QByteArray ba = f.readAll(); + if (f.status()!=IO_Ok) { + //! @todo err msg + f.close(); + return; + } + f.close(); +// m_valueMimeType = KImageIO::mimeType( fileName ); + setValueInternal( ba, true ); + signalEditRequested(); + //emit acceptRequested(); +} + +void KexiBlobTableEdit::handleAboutToSaveAsAction(QString& origFilename, QString& fileExtension, bool& dataIsEmpty) +{ + Q_UNUSED(origFilename); + Q_UNUSED(fileExtension); + dataIsEmpty = valueIsEmpty(); +//! @todo no fname stored for now +} + +void KexiBlobTableEdit::handleSaveAsAction(const QString& fileName) +{ + QFile f(fileName); + if (!f.open(IO_WriteOnly)) { + //! @todo err msg + return; + } + f.writeBlock( d->value ); + if (f.status()!=IO_Ok) { + //! @todo err msg + f.close(); + return; + } + f.close(); +} + +void KexiBlobTableEdit::handleCutAction() +{ + if (isReadOnly()) + return; + handleCopyAction(); + clear(); +} + +void KexiBlobTableEdit::handleCopyAction() +{ + executeCopyAction(d->value); +} + +void KexiBlobTableEdit::executeCopyAction(const QByteArray& data) +{ + QPixmap pixmap; + if (!pixmap.loadFromData(data)) + return; + qApp->clipboard()->setPixmap(pixmap, QClipboard::Clipboard); +} + +void KexiBlobTableEdit::handlePasteAction() +{ + if (isReadOnly()) + return; + QPixmap pm( qApp->clipboard()->pixmap(QClipboard::Clipboard) ); + QByteArray ba; + QBuffer buffer( ba ); + buffer.open( IO_WriteOnly ); + if (pm.save( &buffer, "PNG" )) {// write pixmap into ba in PNG format + setValueInternal( ba, true ); + } + else { + setValueInternal( QByteArray(), true ); + } + signalEditRequested(); + //emit acceptRequested(); + repaintRelatedCell(); +} + +void KexiBlobTableEdit::clear() +{ + setValueInternal( QByteArray(), true ); + signalEditRequested(); + //emit acceptRequested(); + repaintRelatedCell(); +} + +void KexiBlobTableEdit::handleShowPropertiesAction() +{ + //! @todo +} + +void KexiBlobTableEdit::showFocus( const QRect& r, bool readOnly ) +{ + d->readOnly = readOnly; //cache for slotUpdateActionsAvailabilityRequested() +// d->button->move( pos().x()+ width(), pos().y() ); + updateFocus( r ); +// d->button->setEnabled(!readOnly); + if (d->readOnly) + d->button->hide(); + else + d->button->show(); +} + +void KexiBlobTableEdit::resize(int w, int h) +{ + d->totalSize = QSize(w,h); + const int addWidth = d->readOnly ? 0 : d->button->width(); + QWidget::resize(w - addWidth, h); + if (!d->readOnly) + d->button->resize( h, h ); + m_rightMarginWhenFocused = m_rightMargin + addWidth; + QRect r( pos().x(), pos().y(), w+1, h+1 ); + r.moveBy(m_scrollView->contentsX(),m_scrollView->contentsY()); + updateFocus( r ); +//todo if (d->popup) { +//todo d->popup->updateSize(); +//todo } +} + +void KexiBlobTableEdit::updateFocus( const QRect& r ) +{ + if (!d->readOnly) { + if (d->button->width() > r.width()) + moveChild(d->button, r.right() + 1, r.top()); + else + moveChild(d->button, r.right() - d->button->width(), r.top() ); + } +} + +void KexiBlobTableEdit::hideFocus() +{ + d->button->hide(); +} + +QSize KexiBlobTableEdit::totalSize() const +{ + return d->totalSize; +} + +void KexiBlobTableEdit::slotUpdateActionsAvailabilityRequested(bool& valueIsNull, bool& valueIsReadOnly) +{ + emit editRequested(); + valueIsNull = this->valueIsNull(); + valueIsReadOnly = d->readOnly || isReadOnly(); +} + +void KexiBlobTableEdit::signalEditRequested() +{ + d->setValueInternalEnabled = false; + emit editRequested(); + d->setValueInternalEnabled = true; +} + +bool KexiBlobTableEdit::handleKeyPress( QKeyEvent* ke, bool editorActive ) +{ + Q_UNUSED(editorActive); + + const int k = ke->key(); + KKey kkey(ke); + if (!d->readOnly) { + if ((ke->state()==Qt::NoButton && k==Qt::Key_F4) + || (ke->state()==Qt::AltButton && k==Qt::Key_Down)) { + d->button->animateClick(); + QMouseEvent me( QEvent::MouseButtonPress, QPoint(2,2), Qt::LeftButton, Qt::NoButton ); + QApplication::sendEvent( d->button, &me ); + } + else if ((ke->state()==NoButton && (k==Qt::Key_F2 || k==Qt::Key_Space || k==Qt::Key_Enter || k==Qt::Key_Return))) { + d->popup->insertFromFile(); + } + else + return false; + } + else + return false; + return true; +} + +bool KexiBlobTableEdit::handleDoubleClick() +{ + d->popup->insertFromFile(); + return true; +} + +void KexiBlobTableEdit::handleCopyAction(const QVariant& value, const QVariant& visibleValue) +{ + Q_UNUSED(visibleValue); + executeCopyAction(value.toByteArray()); +} + +void KexiBlobTableEdit::handleAction(const QString& actionName) +{ + if (actionName=="edit_paste") { + d->popup->paste(); + } + else if (actionName=="edit_cut") { + emit editRequested(); + d->popup->cut(); + } +} + +bool KexiBlobTableEdit::eventFilter( QObject *o, QEvent *e ) +{ + if (o == d->popup && e->type()==QEvent::KeyPress) { + QKeyEvent* ke = static_cast<QKeyEvent*>(e); + const int state = ke->state(); + const int k = ke->key(); + if ( (state==Qt::NoButton && (k==Qt::Key_Tab || k==Qt::Key_Left || k==Qt::Key_Right)) + || (state==Qt::ShiftButton && k==Qt::Key_Backtab) + ) + { + d->popup->hide(); + QApplication::sendEvent( this, ke ); //re-send to move cursor + return true; + } + } + return false; +} + +KEXI_CELLEDITOR_FACTORY_ITEM_IMPL(KexiBlobEditorFactoryItem, KexiBlobTableEdit) + +//======================= +// KexiKIconTableEdit class is temporarily here: + +//! @internal +class KexiKIconTableEdit::Private +{ +public: + Private() + : pixmapCache(17, 17, false) + { + } + //! We've no editor widget that would store current value, so we do this here + QVariant currentValue; + + QCache<QPixmap> pixmapCache; +}; + +KexiKIconTableEdit::KexiKIconTableEdit(KexiTableViewColumn &column, QWidget *parent) + : KexiTableEdit(column, parent) + , d( new Private() ) +{ + setName("KexiKIconTableEdit"); + init(); +} + +KexiKIconTableEdit::~KexiKIconTableEdit() +{ + delete d; +} + +void KexiKIconTableEdit::init() +{ + m_hasFocusableWidget = false; + d->pixmapCache.setAutoDelete(true); +} + +void KexiKIconTableEdit::setValueInternal(const QVariant& /*add*/, bool /*removeOld*/) +{ + d->currentValue = m_origValue; +} + +bool KexiKIconTableEdit::valueIsNull() +{ + return d->currentValue.isNull(); +} + +bool KexiKIconTableEdit::valueIsEmpty() +{ + return d->currentValue.isNull(); +} + +QVariant KexiKIconTableEdit::value() +{ + return d->currentValue; +} + +void KexiKIconTableEdit::clear() +{ + d->currentValue = QVariant(); +} + +bool KexiKIconTableEdit::cursorAtStart() +{ + return true; +} + +bool KexiKIconTableEdit::cursorAtEnd() +{ + return true; +} + +void KexiKIconTableEdit::setupContents( QPainter *p, bool /*focused*/, const QVariant& val, + QString &/*txt*/, int &/*align*/, int &/*x*/, int &y_offset, int &w, int &h ) +{ + Q_UNUSED( y_offset ); + +#if 0 +#ifdef Q_WS_WIN + y_offset = -1; +#else + y_offset = 0; +#endif + int s = QMAX(h - 5, 12); + s = QMIN( h-3, s ); + s = QMIN( w-3, s );//avoid too large box + QRect r( QMAX( w/2 - s/2, 0 ) , h/2 - s/2 /*- 1*/, s, s); + p->setPen(QPen(colorGroup().text(), 1)); + p->drawRect(r); + if (val.asBool()) { + p->drawLine(r.x(), r.y(), r.right(), r.bottom()); + p->drawLine(r.x(), r.bottom(), r.right(), r.y()); + } +#endif + + QString key = val.toString(); + QPixmap *pix = 0; + if (!key.isEmpty() && !(pix = d->pixmapCache[ key ])) { + //cache pixmap + QPixmap pm = KGlobal::iconLoader()->loadIcon( key, KIcon::Small, + 0, KIcon::DefaultState, 0L, true/*canReturnNull*/ ); + if (!pm.isNull()) { + pix = new QPixmap(pm); + d->pixmapCache.insert(key, pix); + } + } + + if (p && pix) { + p->drawPixmap( (w-pix->width())/2, (h-pix->height())/2, *pix ); + } +} + +void KexiKIconTableEdit::handleCopyAction(const QVariant& value, const QVariant& visibleValue) +{ + Q_UNUSED(value); + Q_UNUSED(visibleValue); +} + +KEXI_CELLEDITOR_FACTORY_ITEM_IMPL(KexiKIconTableEditorFactoryItem, KexiKIconTableEdit) + +#include "kexiblobtableedit.moc" diff --git a/kexi/widget/tableview/kexiblobtableedit.h b/kexi/widget/tableview/kexiblobtableedit.h new file mode 100644 index 00000000..a44559be --- /dev/null +++ b/kexi/widget/tableview/kexiblobtableedit.h @@ -0,0 +1,170 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Peter Simonsson <psn@linux.se> + Copyright (C) 2004, 2006 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef _KEXIBLOBTABLEEDIT_H_ +#define _KEXIBLOBTABLEEDIT_H_ + +#include <qcstring.h> +#include <qcache.h> + +#include <kurl.h> + +#include "kexitableedit.h" +#include "kexicelleditorfactory.h" + +class KTempFile; +class KProcess; +class QTextEdit; + +class KexiBlobTableEdit : public KexiTableEdit +{ + Q_OBJECT + public: + KexiBlobTableEdit(KexiTableViewColumn &column, QWidget *parent=0); + virtual ~KexiBlobTableEdit(); + + bool valueIsNull(); + bool valueIsEmpty(); + + virtual QVariant value(); + + virtual bool cursorAtStart(); + virtual bool cursorAtEnd(); + + /*! Reimplemented: resizes a view(). */ + virtual void resize(int w, int h); + + virtual void showFocus( const QRect& r, bool readOnly ); + + virtual void hideFocus(); + + /*! \return total size of this editor, including popup button. */ + virtual QSize totalSize() const; + + virtual void paintFocusBorders( QPainter *p, QVariant &, int x, int y, int w, int h ); + + /*! Reimplemented to handle the key events. */ + virtual bool handleKeyPress( QKeyEvent* ke, bool editorActive ); + + /*! Handles double click request coming from the table view. + \return true if it has been consumed. + Reimplemented in KexiBlobTableEdit (to execute "insert file" action. */ + virtual bool handleDoubleClick(); + + /*! Handles action having standard name \a actionName. + Action could be: "edit_cut", "edit_paste", etc. */ + virtual void handleAction(const QString& actionName); + + /*! Handles copy action for value. The \a value is copied to clipboard in format appropriate + for the editor's impementation, e.g. for image cell it can be a pixmap. + \a visibleValue is unused here. Reimplemented after KexiTableEdit. */ + virtual void handleCopyAction(const QVariant& value, const QVariant& visibleValue); + + virtual void setupContents( QPainter *p, bool focused, const QVariant& val, + QString &txt, int &align, int &x, int &y_offset, int &w, int &h ); + + protected slots: + void slotUpdateActionsAvailabilityRequested(bool& valueIsNull, bool& valueIsReadOnly); + + void handleInsertFromFileAction(const KURL& url); + void handleAboutToSaveAsAction(QString& origFilename, QString& fileExtension, bool& dataIsEmpty); + void handleSaveAsAction(const QString& fileName); + void handleCutAction(); + void handleCopyAction(); + void handlePasteAction(); + virtual void clear(); + void handleShowPropertiesAction(); + + protected: + //! initializes this editor with \a add value + virtual void setValueInternal(const QVariant& add, bool removeOld); + + //todo QString openWithDlg(const QString& file); + //todo void execute(const QString& app, const QString& file); + + //! @internal + void updateFocus( const QRect& r ); + + void signalEditRequested(); + + //! @internal + void executeCopyAction(const QByteArray& data); + + virtual bool eventFilter( QObject *o, QEvent *e ); + + class Private; + Private *d; +//todo KTempFile* m_tempFile; +//todo KProcess* m_proc; +//todo QTextEdit *m_content; +}; + +KEXI_DECLARE_CELLEDITOR_FACTORY_ITEM(KexiBlobEditorFactoryItem) + + +//======================= +//This class is temporarily here: + +/*! @short Cell editor for displaying kde icon (using icon name provided as string). + Read only. +*/ +class KexiKIconTableEdit : public KexiTableEdit +{ + public: + KexiKIconTableEdit(KexiTableViewColumn &column, QWidget *parent=0); + + virtual ~KexiKIconTableEdit(); + + //! \return true if editor's value is null (not empty) + virtual bool valueIsNull(); + + //! \return true if editor's value is empty (not null). + //! Only few field types can accept "EMPTY" property + //! (check this with KexiDB::Field::hasEmptyProperty()), + virtual bool valueIsEmpty(); + + virtual QVariant value(); + + virtual bool cursorAtStart(); + virtual bool cursorAtEnd(); + + virtual void clear(); + + virtual void setupContents( QPainter *p, bool focused, const QVariant& val, + QString &txt, int &align, int &x, int &y_offset, int &w, int &h ); + + /*! Handles copy action for value. Does nothing. + \a visibleValue is unused here. Reimplemented after KexiTableEdit. */ + virtual void handleCopyAction(const QVariant& value, const QVariant& visibleValue); + + protected: + //! initializes this editor with \a add value + virtual void setValueInternal(const QVariant& add, bool removeOld); + + void showHintButton(); + void init(); + + class Private; + Private *d; +}; + +KEXI_DECLARE_CELLEDITOR_FACTORY_ITEM(KexiKIconTableEditorFactoryItem) + +#endif diff --git a/kexi/widget/tableview/kexibooltableedit.cpp b/kexi/widget/tableview/kexibooltableedit.cpp new file mode 100644 index 00000000..7b7bc0ac --- /dev/null +++ b/kexi/widget/tableview/kexibooltableedit.cpp @@ -0,0 +1,180 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "kexibooltableedit.h" + +#include <kexidb/field.h> + +#include <qpainter.h> +#include <qapplication.h> +#include <qclipboard.h> + +#include <kglobal.h> +#include <klocale.h> +#include <kdebug.h> +#include <kglobalsettings.h> + + +KexiBoolTableEdit::KexiBoolTableEdit(KexiTableViewColumn &column, QWidget *parent) + : KexiTableEdit(column, parent) +{ + setName("KexiBoolTableEdit"); + kdDebug() << "KexiBoolTableEdit: m_origValue.typeName()==" << m_origValue.typeName() << endl; + kdDebug() << "KexiBoolTableEdit: type== " << field()->typeName() << endl; + m_hasFocusableWidget = false; + m_acceptEditorAfterDeleteContents = true; + m_usesSelectedTextColor = false; +} + +KexiBoolTableEdit::~KexiBoolTableEdit() +{ +} + +void KexiBoolTableEdit::setValueInternal(const QVariant& /*add*/, bool /*removeOld*/) +{ + m_currentValue = m_origValue; + //nothing to do more... +} + +bool KexiBoolTableEdit::valueIsNull() +{ + return m_currentValue.isNull(); +} + +bool KexiBoolTableEdit::valueIsEmpty() +{ + return m_currentValue.isNull(); +} + +QVariant KexiBoolTableEdit::value() +{ +// ok = true; + return m_currentValue; +} + +void KexiBoolTableEdit::clear() +{ + if (field()->isNotNull()) + m_currentValue = QVariant(false, 0); + else + m_currentValue = QVariant(); +} + +bool KexiBoolTableEdit::cursorAtStart() +{ + return true; +} + +bool KexiBoolTableEdit::cursorAtEnd() +{ + return true; +} + +void KexiBoolTableEdit::setupContents( QPainter *p, bool focused, const QVariant& val, + QString &txt, int &align, int &x, int &y_offset, int &w, int &h ) +{ + Q_UNUSED(focused); + Q_UNUSED(txt); + Q_UNUSED(align); + Q_UNUSED(x); +#ifdef Q_WS_WIN +// x = 1; + y_offset = -1; +#else +// x = 1; + y_offset = 0; +#endif + if (p) { + int s = QMAX(h - 5, 12); + s = QMIN( h-3, s ); + s = QMIN( w-3, s );//avoid too large box + QRect r( QMAX( w/2 - s/2, 0 ) , h/2 - s/2 /*- 1*/, s, s); +//already set ouotside: p->setPen(QPen(colorGroup().text(), 1)); + p->drawRect(r); + if (val.isNull()) { // && !field()->isNotNull()) { + p->drawText( r, Qt::AlignCenter, "?" ); + } + else if (val.toBool()) { + p->drawLine(r.x(), r.y(), r.right(), r.bottom()); + p->drawLine(r.x(), r.bottom(), r.right(), r.y()); + } + } +} + +void KexiBoolTableEdit::clickedOnContents() +{ + if (field()->isNotNull()) + m_currentValue = QVariant( !m_currentValue.toBool(), 0 ); + else { + // null allowed: use the cycle: true -> false -> null + if (m_currentValue.isNull()) + m_currentValue = QVariant( true, 1 ); + else + m_currentValue = m_currentValue.toBool() ? QVariant( false, 1 ) : QVariant(); + } +} + +void KexiBoolTableEdit::handleAction(const QString& actionName) +{ + if (actionName=="edit_paste") { + emit editRequested(); + bool ok; + const int value = qApp->clipboard()->text( QClipboard::Clipboard ).toInt(&ok); + if (ok) { + m_currentValue = (value==0) ? QVariant(false, 0) : QVariant(true, 1); + } + else { + m_currentValue = field()->isNotNull() + ? QVariant(0, false)/*0 instead of NULL - handle case when null is not allowed*/ + : QVariant(); + } + repaintRelatedCell(); + } + else if (actionName=="edit_cut") { + emit editRequested(); +//! @todo handle defaultValue... + m_currentValue = field()->isNotNull() + ? QVariant(0, false)/*0 instead of NULL - handle case when null is not allowed*/ + : QVariant(); + handleCopyAction(m_origValue, QVariant()); + repaintRelatedCell(); + } +} + +void KexiBoolTableEdit::handleCopyAction(const QVariant& value, const QVariant& visibleValue) +{ + Q_UNUSED(visibleValue); + if (value.type()==QVariant::Bool) + qApp->clipboard()->setText(value.toBool() ? "1" : "0"); + else + qApp->clipboard()->setText(QString::null); +} + +int KexiBoolTableEdit::widthForValue( QVariant &val, const QFontMetrics &fm ) +{ + Q_UNUSED(fm); + return val.toPixmap().width(); +} + +//====================================================== + +KEXI_CELLEDITOR_FACTORY_ITEM_IMPL(KexiBoolEditorFactoryItem, KexiBoolTableEdit) + +#include "kexibooltableedit.moc" + diff --git a/kexi/widget/tableview/kexibooltableedit.h b/kexi/widget/tableview/kexibooltableedit.h new file mode 100644 index 00000000..3320a573 --- /dev/null +++ b/kexi/widget/tableview/kexibooltableedit.h @@ -0,0 +1,87 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef KEXIBOOLTABLEEDIT_H +#define KEXIBOOLTABLEEDIT_H + +#include <qvariant.h> + +#include "kexitableedit.h" +#include "kexicelleditorfactory.h" + +/*! @short Cell editor for boolean type. +*/ +class KexiBoolTableEdit : public KexiTableEdit +{ + Q_OBJECT + + public: + KexiBoolTableEdit(KexiTableViewColumn &column, QWidget *parent=0); + + virtual ~KexiBoolTableEdit(); + + //! \return true if editor's value is null (not empty) + virtual bool valueIsNull(); + + //! \return true if editor's value is empty (not null). + //! Only few field types can accept "EMPTY" property + //! (check this with KexiDB::Field::hasEmptyProperty()), + virtual bool valueIsEmpty(); + + virtual QVariant value(); + + virtual bool cursorAtStart(); + virtual bool cursorAtEnd(); + + virtual void clear(); + + virtual void setupContents( QPainter *p, bool focused, const QVariant& val, + QString &txt, int &align, int &x, int &y_offset, int &w, int &h ); + + virtual void clickedOnContents(); + + /*! Handles action having standard name \a actionName. + Action could be: "edit_cut", "edit_paste", etc. */ + virtual void handleAction(const QString& actionName); + + /*! Handles copy action for value. Copies empty string for null, "1" for true, "0" for false. + \a visibleValue is unused here. Reimplemented after KexiTableEdit. */ + virtual void handleCopyAction(const QVariant& value, const QVariant& visibleValue); + + /*! \return width of \a value. Reimplemented after KexiTableEdit. */ + virtual int widthForValue( QVariant &val, const QFontMetrics &fm ); + + protected slots: + + protected: + //! initializes this editor with \a add value + virtual void setValueInternal(const QVariant& add, bool removeOld); + + void showHintButton(); + + //! We've no editor widget that would store current value, so we do this here + QVariant m_currentValue; + + signals: + void hintClicked(); +}; + +KEXI_DECLARE_CELLEDITOR_FACTORY_ITEM(KexiBoolEditorFactoryItem) + +#endif diff --git a/kexi/widget/tableview/kexicelleditorfactory.cpp b/kexi/widget/tableview/kexicelleditorfactory.cpp new file mode 100644 index 00000000..a20eac07 --- /dev/null +++ b/kexi/widget/tableview/kexicelleditorfactory.cpp @@ -0,0 +1,198 @@ +/* This file is part of the KDE project + Copyright (C) 2004-2007 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "kexicelleditorfactory.h" + +#include <qptrdict.h> +#include <qintdict.h> +#include <kstaticdeleter.h> + +#include <kexidb/indexschema.h> +#include <kexidb/tableschema.h> +#include "kexitableviewdata.h" +#include "kexidatetableedit.h" +#include "kexitimetableedit.h" +#include "kexidatetimetableedit.h" +#include "kexitableedit.h" +#include "kexiinputtableedit.h" +#include "kexicomboboxtableedit.h" +#include "kexiblobtableedit.h" +#include "kexibooltableedit.h" + +//============= KexiCellEditorFactoryItem ============ + +KexiCellEditorFactoryItem::KexiCellEditorFactoryItem() +{ +} + +KexiCellEditorFactoryItem::~KexiCellEditorFactoryItem() +{ +} + +//============= KexiCellEditorFactoryPrivate ============ + +//! @internal +class KexiCellEditorFactoryPrivate +{ + public: + KexiCellEditorFactoryPrivate() + : items(101) + , items_by_type(101, false) + { + items.setAutoDelete( true ); + items_by_type.setAutoDelete( false ); + } + ~KexiCellEditorFactoryPrivate() {} + + QString key(uint type, const QString& subType) const + { + QString key = QString::number(type); + if (!subType.isEmpty()) + key += (QString(" ") + subType); + return key; + } + + void registerItem( KexiCellEditorFactoryItem& item, uint type, const QString& subType = QString::null ) + { + if (!items[ &item ]) + items.insert( &item, &item ); + + items_by_type.insert( key(type, subType), &item ); + } + + KexiCellEditorFactoryItem *findItem(uint type, const QString& subType) + { + KexiCellEditorFactoryItem *item = items_by_type[ key(type, subType) ]; + if (item) + return item; + item = items_by_type[ key(type, QString::null) ]; + if (item) + return item; + return items_by_type[ key( KexiDB::Field::InvalidType, QString::null ) ]; + } + + QPtrDict<KexiCellEditorFactoryItem> items; //!< list of editor factory items (for later destroy) + + QDict<KexiCellEditorFactoryItem> items_by_type; //!< editor factory items accessed by a key +}; + +static KStaticDeleter<KexiCellEditorFactoryPrivate> KexiCellEditorFactory_deleter; +static KexiCellEditorFactoryPrivate *KexiCellEditorFactory_static = 0; + +//============= KexiCellEditorFactory ============ + +KexiCellEditorFactory::KexiCellEditorFactory() +{ +} + +KexiCellEditorFactory::~KexiCellEditorFactory() +{ +} + + +// Initializes standard editor cell editor factories +void KexiCellEditorFactory::init() +{ + if (KexiCellEditorFactory_static) + return; + KexiCellEditorFactory_deleter.setObject(KexiCellEditorFactory_static, new KexiCellEditorFactoryPrivate()); + + KexiCellEditorFactory_static->registerItem( *new KexiBlobEditorFactoryItem(), KexiDB::Field::BLOB ); + KexiCellEditorFactory_static->registerItem( *new KexiDateEditorFactoryItem(), KexiDB::Field::Date ); + KexiCellEditorFactory_static->registerItem( *new KexiTimeEditorFactoryItem(), KexiDB::Field::Time ); + KexiCellEditorFactory_static->registerItem( *new KexiDateTimeEditorFactoryItem(), KexiDB::Field::DateTime ); + KexiCellEditorFactory_static->registerItem( *new KexiComboBoxEditorFactoryItem(), KexiDB::Field::Enum ); + KexiCellEditorFactory_static->registerItem( *new KexiBoolEditorFactoryItem(), KexiDB::Field::Boolean ); + KexiCellEditorFactory_static->registerItem( *new KexiKIconTableEditorFactoryItem(), KexiDB::Field::Text, "KIcon" ); + //default type + KexiCellEditorFactory_static->registerItem( *new KexiInputEditorFactoryItem(), KexiDB::Field::InvalidType ); +} + +void KexiCellEditorFactory::registerItem( KexiCellEditorFactoryItem& item, uint type, const QString& subType ) +{ + init(); + KexiCellEditorFactory_static->registerItem( item, type, subType ); +} + +static bool hasEnumType( const KexiTableViewColumn &column ) +{ + /*not db-aware case*/ + if (column.relatedData()) + return true; + /*db-aware case*/ + if (!column.field() || !column.field()->table()) + return false; + KexiDB::LookupFieldSchema *lookupFieldSchema = column.field()->table()->lookupFieldSchema( *column.field() ); + if (!lookupFieldSchema) + return false; + if (lookupFieldSchema->rowSource().name().isEmpty()) + return false; + return true; +} + +KexiTableEdit* KexiCellEditorFactory::createEditor(KexiTableViewColumn &column, QWidget* parent) +{ + init(); + KexiDB::Field *realField; + if (column.visibleLookupColumnInfo) { + realField = column.visibleLookupColumnInfo->field; + } + else { + realField = column.field(); + } + + KexiCellEditorFactoryItem *item = 0; + + if (hasEnumType(column)) { + //--we need to create combo box because of relationship: + item = KexiCellEditorFactory::item( KexiDB::Field::Enum ); + } + else { + item = KexiCellEditorFactory::item( realField->type(), realField->subType() ); + } + +#if 0 //js: TODO LATER + //--check if we need to create combo box because of relationship: + //WARNING: it's assumed that indices are one-field long + KexiDB::TableSchema *table = f.table(); + if (table) { + //find index that contain this field + KexiDB::IndexSchema::ListIterator it = table->indicesIterator(); + for (;it.current();++it) { + KexiDB::IndexSchema *idx = it.current(); + if (idx->fields()->findRef(&f)!=-1) { + //find details-side rel. for this index + KexiDB::Relationship *rel = idx->detailsRelationships()->first(); + if (rel) { + + } + } + } + } +#endif + + return item->createEditor(column, parent); +} + +KexiCellEditorFactoryItem* KexiCellEditorFactory::item( uint type, const QString& subType ) +{ + init(); + return KexiCellEditorFactory_static->findItem(type, subType); +} + diff --git a/kexi/widget/tableview/kexicelleditorfactory.h b/kexi/widget/tableview/kexicelleditorfactory.h new file mode 100644 index 00000000..1b68cb8d --- /dev/null +++ b/kexi/widget/tableview/kexicelleditorfactory.h @@ -0,0 +1,79 @@ +/* This file is part of the KDE project + Copyright (C) 2004-2007 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXICELLEDITORFACTORY_H +#define KEXICELLEDITORFACTORY_H + +#include <qvariant.h> +#include <qwidget.h> + +#include <kexidb/field.h> + +class KexiCellEditorFactoryItem; +class KexiTableEdit; +class KexiTableViewColumn; + +//! A singleton class providing access to cell editor factories +class KEXIDATATABLE_EXPORT KexiCellEditorFactory +{ + public: + KexiCellEditorFactory(); + virtual ~KexiCellEditorFactory(); + + /*! Registers factory item for \a type and (optional) \a subType. + \a subType is usually obtained (e.g. in KexiTableView) from KexiDB::Field::subType(). + Passing KexiDB::Field::Invalid as type will set default item, + i.e. the one that will be used when no other item is defined for given data type. + You can register the same \a item many times for different types and subtypes. + Once registered, \a item object will be owned by the factory, so you shouldn't + care about deleting it. */ + static void registerItem( KexiCellEditorFactoryItem& item, uint type, + const QString& subType = QString::null ); + + /*! \return item for \a type and (optional) \a subType. + If no item found, the one with empty subtype is tried. + If still no item found, the default is tried. Eventually, may return NULL. */ + static KexiCellEditorFactoryItem* item( uint type, const QString& subType = QString::null ); + +// static KexiTableEdit* createEditor(KexiDB::Field &f, QScrollView* parent = 0); + /*! Creates a new editor for \a column. If \a parent is of QScrollView, the new editor + will be created inside parent->viewport() instead. */ + static KexiTableEdit* createEditor(KexiTableViewColumn &column, QWidget* parent = 0); + + protected: + static void init(); +}; + +//! A base class for implementing cell editor factories +class KEXIDATATABLE_EXPORT KexiCellEditorFactoryItem +{ + public: + KexiCellEditorFactoryItem(); + virtual ~KexiCellEditorFactoryItem(); + QString className() { return m_className; } + + protected: +// virtual KexiTableEdit* createEditor(KexiDB::Field &f, QScrollView* parent = 0) = 0; + virtual KexiTableEdit* createEditor(KexiTableViewColumn &column, QWidget* parent = 0) = 0; + + QString m_className; + friend class KexiCellEditorFactory; +}; + +#endif diff --git a/kexi/widget/tableview/kexicomboboxbase.cpp b/kexi/widget/tableview/kexicomboboxbase.cpp new file mode 100644 index 00000000..2d6d52ac --- /dev/null +++ b/kexi/widget/tableview/kexicomboboxbase.cpp @@ -0,0 +1,597 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Peter Simonsson <psn@linux.se> + Copyright (C) 2003-2007 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <qlayout.h> +#include <qstyle.h> +#include <qwindowsstyle.h> +#include <qpainter.h> + +#include "kexicomboboxbase.h" +#include <widget/utils/kexicomboboxdropdownbutton.h> +#include "kexicomboboxpopup.h" +#include "kexitableview.h" +#include "kexitableitem.h" +#include "kexi.h" + +#include <klineedit.h> + +KexiComboBoxBase::KexiComboBoxBase() +{ + m_internalEditorValueChanged = false; //user has text or other value inside editor + m_slotInternalEditorValueChanged_enabled = true; + m_mouseBtnPressedWhenPopupVisible = false; + m_insideCreatePopup = false; + m_setValueOrTextInInternalEditor_enabled = true; + m_updatePopupSelectionOnShow = true; + m_moveCursorToEndInInternalEditor_enabled = true; + m_selectAllInInternalEditor_enabled = true; + m_setValueInInternalEditor_enabled = true; + m_setVisibleValueOnSetValueInternal = false; +} + +KexiComboBoxBase::~KexiComboBoxBase() +{ +} + +KexiDB::LookupFieldSchema *KexiComboBoxBase::lookupFieldSchema() const +{ + if (field() && field()->table()) { + KexiDB::LookupFieldSchema *lookupFieldSchema = field()->table()->lookupFieldSchema( *field() ); + if (lookupFieldSchema && !lookupFieldSchema->rowSource().name().isEmpty()) + return lookupFieldSchema; + } + return 0; +} + +int KexiComboBoxBase::rowToHighlightForLookupTable() const +{ + if (!popup()) + return -1;//err + KexiDB::LookupFieldSchema *lookupFieldSchema = this->lookupFieldSchema(); + if (!lookupFieldSchema) + return -1; + if (lookupFieldSchema->boundColumn()==-1) + return -1; //err + bool ok; + const int rowUid = origValue().toInt(); +//! @todo for now we're assuming the id is INTEGER + KexiTableViewData *tvData = popup()->tableView()->data(); + const int boundColumn = lookupFieldSchema->boundColumn(); + KexiTableViewData::Iterator it(tvData->iterator()); + int row=0; + for (;it.current();++it, row++) + { + if (it.current()->at(boundColumn).toInt(&ok) == rowUid && ok || !ok) + break; + } + if (!ok || !it.current()) //item not found: highlight 1st row, if available + return -1; + return row; +} + +void KexiComboBoxBase::setValueInternal(const QVariant& add_, bool removeOld) +{ + Q_UNUSED(removeOld); + m_mouseBtnPressedWhenPopupVisible = false; + m_updatePopupSelectionOnShow = true; + QString add(add_.toString()); + if (add.isEmpty()) { + KexiTableViewData *relData = column() ? column()->relatedData() : 0; + QVariant valueToSet; + bool hasValueToSet = true; + int rowToHighlight = -1; + KexiDB::LookupFieldSchema *lookupFieldSchema = this->lookupFieldSchema(); + if (lookupFieldSchema) { + //use 'lookup field' model +//! @todo support more RowSourceType's, not only table + if (lookupFieldSchema->boundColumn()==-1) +//! @todo errmsg + return; + if (m_setVisibleValueOnSetValueInternal) { + //only for table views + if (!popup()) + createPopup(false/*!show*/); + } + if (popup()) { + const int rowToHighlight = rowToHighlightForLookupTable(); + popup()->tableView()->setHighlightedRow(rowToHighlight); + + const int visibleColumn = lookupFieldSchema->visibleColumn( popup()->tableView()->data()->columnsCount() ); + if (m_setVisibleValueOnSetValueInternal && -1!=visibleColumn) { + //only for table views + KexiTableItem *it = popup()->tableView()->highlightedItem(); + if (it) + valueToSet = it->at( visibleColumn ); + } + else { + hasValueToSet = false; + } + } + } + else if (relData) { + //use 'related table data' model + valueToSet = valueForString(origValue().toString(), &rowToHighlight, 0, 1); + } + else { + //use 'enum hints' model + const int row = origValue().toInt(); + valueToSet = field()->enumHint(row).stripWhiteSpace(); + } + if (hasValueToSet) + setValueOrTextInInternalEditor( valueToSet ); + /*impl.*/moveCursorToEndInInternalEditor(); + /*impl.*/selectAllInInternalEditor(); + + if (popup()) { + if (origValue().isNull()) { + popup()->tableView()->clearSelection(); + popup()->tableView()->setHighlightedRow(0); + } else { + if (relData) { + if (rowToHighlight!=-1) + popup()->tableView()->setHighlightedRow(rowToHighlight); + } + else if (!lookupFieldSchema) { + //popup()->tableView()->selectRow(origValue().toInt()); + popup()->tableView()->setHighlightedRow(origValue().toInt()); + } + } + } + } + else { + //todo: autocompl.? + if (popup()) + popup()->tableView()->clearSelection(); + /*impl.*/setValueInInternalEditor(add); //not setLineEditText(), because 'add' is entered by user! + //setLineEditText( add ); + /*impl.*/moveCursorToEndInInternalEditor(); + } +} + +KexiTableItem* KexiComboBoxBase::selectItemForEnteredValueInLookupTable(const QVariant& v) +{ + KexiDB::LookupFieldSchema *lookupFieldSchema = this->lookupFieldSchema(); + if (!popup() || !lookupFieldSchema) + return 0; //safety +//-not effective for large sets: please cache it! +//.stripWhiteSpace() is not generic! + + const bool valueIsText = v.type()==QVariant::String || v.type()==QVariant::CString; //most common case + const QString txt( valueIsText ? v.toString().stripWhiteSpace().lower() : QString::null ); + KexiTableViewData *lookupData = popup()->tableView()->data(); + const int visibleColumn = lookupFieldSchema->visibleColumn( lookupData->columnsCount() ); + if (-1 == visibleColumn) + return 0; + KexiTableViewData::Iterator it(lookupData->iterator()); + int row; + for (row = 0;it.current();++it, row++) { + if (valueIsText) { + if (it.current()->at(visibleColumn).toString().stripWhiteSpace().lower() == txt) + break; + } + else { + if (it.current()->at(visibleColumn) == v) + break; + } + } + + m_setValueOrTextInInternalEditor_enabled = false; // <-- this is the entered value, + // so do not change the internal editor's contents + if (it.current()) + popup()->tableView()->selectRow(row); + else + popup()->tableView()->clearSelection(); + + m_setValueOrTextInInternalEditor_enabled = true; + + return it.current(); +} + +QString KexiComboBoxBase::valueForString(const QString& str, int* row, + uint lookInColumn, uint returnFromColumn, bool allowNulls) +{ + KexiTableViewData *relData = column() ? column()->relatedData() : 0; + if (!relData) + return QString::null; //safety + //use 'related table data' model + //-not effective for large sets: please cache it! + //.stripWhiteSpace() is not generic! + + const QString txt = str.stripWhiteSpace().lower(); + KexiTableViewData::Iterator it( relData->iterator() ); + for (*row = 0;it.current();++it, (*row)++) { + if (it.current()->at(lookInColumn).toString().stripWhiteSpace().lower()==txt) + break; + } + if (it.current()) + return it.current()->at(returnFromColumn).toString(); + + *row = -1; + + if (column() && column()->relatedDataEditable()) + return str; //new value entered and that's allowed + + kexiwarn << "KexiComboBoxBase::valueForString(): no related row found, ID will be painted!" << endl; + if (allowNulls) + return QString::null; + return str; //for sanity but it's weird to show id to the user +} + +QVariant KexiComboBoxBase::value() +{ + KexiTableViewData *relData = column() ? column()->relatedData() : 0; + KexiDB::LookupFieldSchema *lookupFieldSchema = 0; + if (relData) { + if (m_internalEditorValueChanged) { + //we've user-entered text: look for id +//TODO: make error if matching text not found? + int rowToHighlight; + return valueForString(m_userEnteredValue.toString(), &rowToHighlight, 1, 0, true/*allowNulls*/); + } + else { + //use 'related table data' model + KexiTableItem *it = popup() ? popup()->tableView()->selectedItem() : 0; + return it ? it->at(0) : origValue();//QVariant(); + } + } + else if ((lookupFieldSchema = this->lookupFieldSchema())) + { + if (lookupFieldSchema->boundColumn()==-1) + return origValue(); + KexiTableItem *it = popup() ? popup()->tableView()->selectedItem() : 0; + if (/*!it &&*/ m_internalEditorValueChanged && !m_userEnteredValue.toString().isEmpty()) { // + //try to select a row using the user-entered text + if (!popup()) { + QVariant prevUserEnteredValue = m_userEnteredValue; + createPopup(false); + m_userEnteredValue = prevUserEnteredValue; + } + it = selectItemForEnteredValueInLookupTable( m_userEnteredValue ); + } + return it ? it->at( lookupFieldSchema->boundColumn() ) : QVariant(); + } + else if (popup()) { + //use 'enum hints' model + const int row = popup()->tableView()->currentRow(); + if (row>=0) + return QVariant( row ); + } + + if (valueFromInternalEditor().toString().isEmpty()) + return QVariant(); +/*! \todo don't return just 1st row, but use autocompletion feature + and: show message box if entered text does not match! */ + return origValue(); //unchanged +} + +QVariant KexiComboBoxBase::visibleValueForLookupField() +{ + KexiDB::LookupFieldSchema *lookupFieldSchema = this->lookupFieldSchema(); + if (!popup() || !lookupFieldSchema) + return QVariant(); + const int visibleColumn = lookupFieldSchema->visibleColumn( popup()->tableView()->data()->columnsCount() ); + if (-1 == visibleColumn) + return QVariant(); + KexiTableItem *it = popup()->tableView()->selectedItem(); + return it ? it->at( QMIN( (uint)visibleColumn, it->count()-1)/*sanity*/ ) : QVariant(); +} + +QVariant KexiComboBoxBase::visibleValue() +{ + return m_visibleValue; +} + +void KexiComboBoxBase::clear() +{ + if (popup()) + popup()->hide(); + slotInternalEditorValueChanged(QVariant()); +} + +tristate KexiComboBoxBase::valueChangedInternal() +{ + //avoid comparing values: + KexiTableViewData *relData = column() ? column()->relatedData() : 0; + KexiDB::LookupFieldSchema *lookupFieldSchema = this->lookupFieldSchema(); + if (relData || lookupFieldSchema) { + if (m_internalEditorValueChanged) + return true; + + //use 'related table data' model + KexiTableItem *it = popup() ? popup()->tableView()->selectedItem() : 0; + if (!it) + return false; + } + else { + //use 'enum hints' model + const int row = popup() ? popup()->tableView()->currentRow() : -1; + if (row<0 && !m_internalEditorValueChanged/*true if text box is cleared*/) + return false; + } + + return cancelled; +} + +bool KexiComboBoxBase::valueIsNull() +{ +// bool ok; + QVariant v( value() ); + return v.isNull(); +// return !ok || v.isNull(); +} + +bool KexiComboBoxBase::valueIsEmpty() +{ + return valueIsNull(); +} + +void KexiComboBoxBase::showPopup() +{ + createPopup(true); +} + +void KexiComboBoxBase::createPopup(bool show) +{ + if (!field()) + return; + m_insideCreatePopup = true; + QWidget* thisWidget = dynamic_cast<QWidget*>(this); + QWidget *widgetToFocus = internalEditor() ? internalEditor() : thisWidget; + if (!popup()) { + setPopup( column() ? new KexiComboBoxPopup(thisWidget, *column()) + : new KexiComboBoxPopup(thisWidget, *field()) ); + QObject::connect(popup(), SIGNAL(rowAccepted(KexiTableItem*,int)), + thisWidget, SLOT(slotRowAccepted(KexiTableItem*,int))); + QObject::connect(popup()->tableView(), SIGNAL(itemSelected(KexiTableItem*)), + thisWidget, SLOT(slotItemSelected(KexiTableItem*))); + + popup()->setFocusProxy( widgetToFocus ); + popup()->tableView()->setFocusProxy( widgetToFocus ); + popup()->installEventFilter(thisWidget); + + if (origValue().isNull()) + popup()->tableView()->clearSelection(); + else { + popup()->tableView()->selectRow( 0 ); + popup()->tableView()->setHighlightedRow( 0 ); + } + } + if (show && internalEditor() && !internalEditor()->isVisible()) + /*emit*/editRequested(); + + QPoint posMappedToGlobal = mapFromParentToGlobal(thisWidget->pos()); + if (posMappedToGlobal != QPoint(-1,-1)) { +//! todo alter the position to fit the popup within screen boundaries + popup()->move( posMappedToGlobal + QPoint(0, thisWidget->height()) ); + //to avoid flickering: first resize to 0-height, then show and resize back to prev. height + const int w = popupWidthHint(); + popup()->resize(w, 0); + if (show) + popup()->show(); + popup()->updateSize(w); + if (m_updatePopupSelectionOnShow) { + int rowToHighlight = -1; + KexiDB::LookupFieldSchema *lookupFieldSchema = this->lookupFieldSchema(); + KexiTableViewData *relData = column() ? column()->relatedData() : 0; + if (lookupFieldSchema) { + rowToHighlight = rowToHighlightForLookupTable(); + } + else if (relData) { + (void)valueForString(origValue().toString(), &rowToHighlight, 0, 1); + } + else //enum hint + rowToHighlight = origValue().toInt(); + +/*-->*/ m_moveCursorToEndInInternalEditor_enabled = show; + m_selectAllInInternalEditor_enabled = show; + m_setValueInInternalEditor_enabled = show; + if (rowToHighlight==-1) { + rowToHighlight = QMAX( popup()->tableView()->highlightedRow(), 0); + setValueInInternalEditor(QVariant()); + } + popup()->tableView()->selectRow( rowToHighlight ); + popup()->tableView()->setHighlightedRow( rowToHighlight ); + if (rowToHighlight < popup()->tableView()->rowsPerPage()) + popup()->tableView()->ensureCellVisible( 0, -1 ); + +/*-->*/ m_moveCursorToEndInInternalEditor_enabled = true; + m_selectAllInInternalEditor_enabled = true; + m_setValueInInternalEditor_enabled = true; + } + } + + if (show) { + moveCursorToEndInInternalEditor(); + selectAllInInternalEditor(); + widgetToFocus->setFocus(); + } + m_insideCreatePopup = false; +} + +void KexiComboBoxBase::hide() +{ + if (popup()) + popup()->hide(); +} + +void KexiComboBoxBase::slotRowAccepted(KexiTableItem * item, int row) +{ + Q_UNUSED(row); + //update our value + //..nothing to do? + updateButton(); + slotItemSelected(item); + /*emit*/acceptRequested(); +} + +void KexiComboBoxBase::acceptPopupSelection() +{ + if (!popup()) + return; + KexiTableItem *item = popup()->tableView()->highlightedItem(); + if (item) { + popup()->tableView()->selectRow( popup()->tableView()->highlightedRow() ); + slotRowAccepted(item, -1); + } + popup()->hide(); +} + +void KexiComboBoxBase::slotItemSelected(KexiTableItem*) +{ + kexidbg << "KexiComboBoxBase::slotItemSelected(): m_visibleValue = " << m_visibleValue << endl; + + QVariant valueToSet; + KexiTableViewData *relData = column() ? column()->relatedData() : 0; + KexiDB::LookupFieldSchema *lookupFieldSchema = this->lookupFieldSchema(); + + m_visibleValue = lookupFieldSchema ? visibleValueForLookupField() : QVariant(); + + if (relData) { + //use 'related table data' model + KexiTableItem *item = popup()->tableView()->selectedItem(); + if (item) + valueToSet = item->at(1); + } + else if (lookupFieldSchema) { + KexiTableItem *item = popup()->tableView()->selectedItem(); + const int visibleColumn = lookupFieldSchema->visibleColumn( popup()->tableView()->data()->columnsCount() ); + if (item && visibleColumn!=-1 /* && (int)item->size() >= visibleColumn --already checked*/) { + valueToSet = item->at( QMIN( (uint)visibleColumn, item->count()-1)/*sanity*/ ); + } + } + else { + //use 'enum hints' model + valueToSet = field()->enumHint( popup()->tableView()->currentRow() ); + if (valueToSet.toString().isEmpty() && !m_insideCreatePopup) { + clear(); + QWidget* thisWidget = dynamic_cast<QWidget*>(this); + thisWidget->parentWidget()->setFocus(); + return; + } + } + setValueOrTextInInternalEditor( valueToSet ); + if (m_setValueOrTextInInternalEditor_enabled) { + moveCursorToEndInInternalEditor(); + selectAllInInternalEditor(); + } + // a new (temp) popup table index is selected: do not update selection next time: + m_updatePopupSelectionOnShow = false; +} + +void KexiComboBoxBase::slotInternalEditorValueChanged(const QVariant& v) +{ + if (!m_slotInternalEditorValueChanged_enabled) + return; + m_userEnteredValue = v; + m_internalEditorValueChanged = true; + if (v.toString().isEmpty()) { + if (popup()) { + popup()->tableView()->clearSelection(); + } + return; + } +} + +void KexiComboBoxBase::setValueOrTextInInternalEditor(const QVariant& value) +{ + if (!m_setValueOrTextInInternalEditor_enabled) + return; + setValueInInternalEditor( value ); + //this text is not entered by hand: + m_userEnteredValue = QVariant(); + m_internalEditorValueChanged = false; +} + +bool KexiComboBoxBase::handleKeyPressForPopup( QKeyEvent *ke ) +{ + const int k = ke->key(); + int highlightedOrSelectedRow = popup() ? popup()->tableView()->highlightedRow() : -1; + if (popup() && highlightedOrSelectedRow < 0) + highlightedOrSelectedRow = popup()->tableView()->currentRow(); + + const bool enterPressed = k==Qt::Key_Enter || k==Qt::Key_Return; + + // The editor may be active but the pull down menu not existant/visible, + // e.g. when the user has pressed a normal button to activate the editor + // Don't handle the event here in that case. + if (!popup() || (!enterPressed && !popup()->isVisible())) { + return false; + } + + switch (k) { + case Qt::Key_Up: + popup()->tableView()->setHighlightedRow( + QMAX(highlightedOrSelectedRow-1, 0) ); + updateTextForHighlightedRow(); + return true; + case Qt::Key_Down: + popup()->tableView()->setHighlightedRow( + QMIN(highlightedOrSelectedRow+1, popup()->tableView()->rows()-1) ); + updateTextForHighlightedRow(); + return true; + case Qt::Key_PageUp: + popup()->tableView()->setHighlightedRow( + QMAX(highlightedOrSelectedRow-popup()->tableView()->rowsPerPage(), 0) ); + updateTextForHighlightedRow(); + return true; + case Qt::Key_PageDown: + popup()->tableView()->setHighlightedRow( + QMIN(highlightedOrSelectedRow+popup()->tableView()->rowsPerPage(), + popup()->tableView()->rows()-1) ); + updateTextForHighlightedRow(); + return true; + case Qt::Key_Home: + popup()->tableView()->setHighlightedRow( 0 ); + updateTextForHighlightedRow(); + return true; + case Qt::Key_End: + popup()->tableView()->setHighlightedRow( popup()->tableView()->rows()-1 ); + updateTextForHighlightedRow(); + return true; + case Qt::Key_Enter: + case Qt::Key_Return: //accept + //select row that is highlighted + if (popup()->tableView()->highlightedRow()>=0) + popup()->tableView()->selectRow( popup()->tableView()->highlightedRow() ); + //do not return true: allow to process event + default: ; + } + return false; +} + +void KexiComboBoxBase::updateTextForHighlightedRow() +{ + KexiTableItem *item = popup() ? popup()->tableView()->highlightedItem() : 0; + if (item) + slotItemSelected(item); +} + +void KexiComboBoxBase::undoChanges() +{ + KexiDB::LookupFieldSchema *lookupFieldSchema = this->lookupFieldSchema(); + if (lookupFieldSchema) { +// kexidbg << "KexiComboBoxBase::undoChanges(): m_visibleValue BEFORE = " << m_visibleValue << endl; + if (popup()) + popup()->tableView()->selectRow( popup()->tableView()->highlightedRow() ); + m_visibleValue = visibleValueForLookupField(); +// kexidbg << "KexiComboBoxBase::undoChanges(): m_visibleValue AFTER = " << m_visibleValue << endl; + setValueOrTextInInternalEditor( m_visibleValue ); + } +} diff --git a/kexi/widget/tableview/kexicomboboxbase.h b/kexi/widget/tableview/kexicomboboxbase.h new file mode 100644 index 00000000..1433ab0f --- /dev/null +++ b/kexi/widget/tableview/kexicomboboxbase.h @@ -0,0 +1,170 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Peter Simonsson <psn@linux.se> + Copyright (C) 2003-2007 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef _KEXICOMBOBOXBASE_H_ +#define _KEXICOMBOBOXBASE_H_ + +#include "kexidb/field.h" +#include "kexiinputtableedit.h" +#include <kexidb/lookupfieldschema.h> + +class KPushButton; +class KLineEdit; +class KexiComboBoxPopup; +class KexiTableItem; +class KexiTableViewColumn; + +/*! @short A base class for handling data-aware combo boxes. + This class is used by KexiComboBoxTableEdit and KexiDBComboBox. +*/ +class KEXIDATATABLE_EXPORT KexiComboBoxBase +{ + public: + KexiComboBoxBase(); + virtual ~KexiComboBoxBase(); + + //! \return column related to this combo; for KexiComboBoxTableEdit 0 is returned here + virtual KexiTableViewColumn *column() const = 0; + + //! \return database field related to this combo + virtual KexiDB::Field *field() const = 0; + + //! \return the original value + virtual QVariant origValue() const = 0; + + //! Note: Generally in current implementation this is integer > 0; may be null if no value is set + virtual QVariant value(); + + virtual QVariant visibleValue(); + + //! Reimplement this and call this impl.: used to clear internal editor + virtual void clear(); + + virtual tristate valueChangedInternal(); + virtual bool valueIsNull(); + virtual bool valueIsEmpty(); + + public: + virtual void hide(); + + void createPopup(bool show); + + void showPopup(); + + //! Call this from slot + virtual void slotRowAccepted(KexiTableItem *item, int row); + + //! Call this from slot + virtual void slotItemSelected(KexiTableItem*); + + //! Call this from slot + void slotInternalEditorValueChanged(const QVariant &v); + + //! Implement this to return the internal editor + virtual QWidget *internalEditor() const = 0; + + protected: + virtual void setValueInternal(const QVariant& add, bool removeOld); + + //! Used to select row item for an user-entered value \a v. + //! Only for "lookup table" mode. + KexiTableItem* selectItemForEnteredValueInLookupTable(const QVariant& v); + + /*! \return value from \a returnFromColumn related to \a str value from column \a lookInColumn. + If \a allowNulls is true, NULL is returend if no matched column found, else: + \a str is returned. + Example: lookInColumn=0, returnFromColumn=1 --returns user-visible string + for column #1 for id-column #0 */ + QString valueForString(const QString& str, int* row, uint lookInColumn, + uint returnFromColumn, bool allowNulls = false); + + //! sets \a value for the line edit without setting a flag (m_userEnteredValue) that indicates that + //! the text has been entered by hand (by a user) + void setValueOrTextInInternalEditor(const QVariant& value); //QString& text); + + //! \return lookup field schema for this combo box, if present and if is valid (i.e. has defined row source) + KexiDB::LookupFieldSchema* lookupFieldSchema() const; + + int rowToHighlightForLookupTable() const; + + //! Implement this to perform "move cursor to end" in the internal editor + virtual void moveCursorToEndInInternalEditor() = 0; + + //! Implement this to perform "select all" in the internal editor + virtual void selectAllInInternalEditor() = 0; + + //! Implement this to perform "set value" in the internal editor + virtual void setValueInInternalEditor(const QVariant& value) = 0; + + //! Implement this to return value from the internal editor + virtual QVariant valueFromInternalEditor() = 0; + + //! Implement this as signal + virtual void editRequested() = 0; + + //! Implement this as signal + virtual void acceptRequested() = 0; + + //! Implement this to return a position \a pos mapped from parent (e.g. viewport) + //! to global coordinates. QPoint(-1, -1) should be returned if this cannot be computed. + virtual QPoint mapFromParentToGlobal(const QPoint& pos) const = 0; + + //! Implement this to return a hint for popup width. + virtual int popupWidthHint() const = 0; + + //! Implement this to update button state. Table view just updates on/off state + //! for the button depending on visibility of the popup + virtual void updateButton() {} + + virtual KexiComboBoxPopup *popup() const = 0; + virtual void setPopup(KexiComboBoxPopup *popup) = 0; + + virtual QVariant visibleValueForLookupField(); + + void updateTextForHighlightedRow(); + + bool handleKeyPressForPopup( QKeyEvent *ke ); + + void acceptPopupSelection(); + + //! Used by KexiDBComboBox. + void undoChanges(); + + QVariant m_visibleValue; + + QVariant m_userEnteredValue; //!< value (usually a text) entered by hand (by the user) + + bool m_internalEditorValueChanged : 1; //!< true if user has text or other value inside editor + bool m_slotInternalEditorValueChanged_enabled : 1; //!< Used in slotInternalEditorValueChanged() + bool m_setValueOrTextInInternalEditor_enabled : 1; //!< Used in setValueOrTextInInternalEditor() and slotItemSelected() + bool m_mouseBtnPressedWhenPopupVisible : 1; //!< Used only by KexiComboBoxTableEdit + bool m_insideCreatePopup : 1; //!< true if we're inside createPopup(); used in slotItemSelected() + bool m_updatePopupSelectionOnShow : 1; //!< Set to false as soon as the item corresponding with the current + //!< value is selected in the popup table. This avoids selecting item + //!< for origValue() and thus loosing the recent choice. + bool m_moveCursorToEndInInternalEditor_enabled : 1; + bool m_selectAllInInternalEditor_enabled : 1; + bool m_setValueInInternalEditor_enabled : 1; + bool m_setVisibleValueOnSetValueInternal : 1; //!< Used in setValueInternal() to control whether + //!< we want to set visible value on setValueInternal() + //!< - true for table view's combo box +}; + +#endif diff --git a/kexi/widget/tableview/kexicomboboxpopup.cpp b/kexi/widget/tableview/kexicomboboxpopup.cpp new file mode 100644 index 00000000..5cd65d0d --- /dev/null +++ b/kexi/widget/tableview/kexicomboboxpopup.cpp @@ -0,0 +1,373 @@ +/* This file is part of the KDE project + Copyright (C) 2004-2007 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "kexicomboboxpopup.h" + +#include "kexidatatableview.h" +#include "kexitableview_p.h" +#include "kexitableitem.h" +#include "kexitableedit.h" + +#include <kexidb/lookupfieldschema.h> +#include <kexidb/expression.h> +#include <kexidb/parser/sqlparser.h> + +#include <kdebug.h> + +#include <qlayout.h> +#include <qevent.h> + +/*! @internal + Helper for KexiComboBoxPopup. */ +class KexiComboBoxPopup_KexiTableView : public KexiDataTableView +{ + public: + KexiComboBoxPopup_KexiTableView(QWidget* parent=0) + : KexiDataTableView(parent, "KexiComboBoxPopup_tv") + { + init(); + } + void init() + { + setReadOnly( true ); + setLineWidth( 0 ); + d->moveCursorOnMouseRelease = true; + KexiTableView::Appearance a(appearance()); + a.navigatorEnabled = false; +//! @todo add option for backgroundAltering?? + a.backgroundAltering = false; + a.fullRowSelection = true; + a.rowHighlightingEnabled = true; + a.rowMouseOverHighlightingEnabled = true; + a.persistentSelections = false; + a.rowMouseOverHighlightingColor = colorGroup().highlight(); + a.rowMouseOverHighlightingTextColor = colorGroup().highlightedText(); + a.rowHighlightingTextColor = a.rowMouseOverHighlightingTextColor; + a.gridEnabled = false; + setAppearance(a); + setInsertingEnabled( false ); + setSortingEnabled( false ); + setVerticalHeaderVisible( false ); + setHorizontalHeaderVisible( false ); + setContextMenuEnabled( false ); + setScrollbarToolTipsEnabled( false ); + installEventFilter(this); + setBottomMarginInternal( - horizontalScrollBar()->sizeHint().height() ); + } + virtual void setData( KexiTableViewData *data, bool owner = true ) + { KexiTableView::setData( data, owner ); } + bool setData(KexiDB::Cursor *cursor) + { return KexiDataTableView::setData( cursor ); } +}; + +//======================================== + +//! @internal +class KexiComboBoxPopupPrivate +{ + public: + KexiComboBoxPopupPrivate() + : int_f(0) + , privateQuery(0) + { + max_rows = KexiComboBoxPopup::defaultMaxRows; + } + ~KexiComboBoxPopupPrivate() { + delete int_f; + delete privateQuery; + } + + KexiComboBoxPopup_KexiTableView *tv; + KexiDB::Field *int_f; //TODO: remove this -temporary + KexiDB::QuerySchema* privateQuery; + int max_rows; +}; + +//======================================== + +const int KexiComboBoxPopup::defaultMaxRows = 8; + +KexiComboBoxPopup::KexiComboBoxPopup(QWidget* parent, KexiTableViewColumn &column) + : QFrame( parent, "KexiComboBoxPopup", WType_Popup ) +{ + init(); + //setup tv data + setData(&column, 0); +} + +KexiComboBoxPopup::KexiComboBoxPopup(QWidget* parent, KexiDB::Field &field) + : QFrame( parent, "KexiComboBoxPopup", WType_Popup ) +{ + init(); + //setup tv data + setData(0, &field); +} + +KexiComboBoxPopup::~KexiComboBoxPopup() +{ + delete d; +} + +void KexiComboBoxPopup::init() +{ + d = new KexiComboBoxPopupPrivate(); + setPaletteBackgroundColor(palette().color(QPalette::Active,QColorGroup::Base)); + setLineWidth( 1 ); + setFrameStyle( Box | Plain ); + + d->tv = new KexiComboBoxPopup_KexiTableView(this); + installEventFilter(this); + + connect(d->tv, SIGNAL(itemReturnPressed(KexiTableItem*,int,int)), + this, SLOT(slotTVItemAccepted(KexiTableItem*,int,int))); + + connect(d->tv, SIGNAL(itemMouseReleased(KexiTableItem*,int,int)), + this, SLOT(slotTVItemAccepted(KexiTableItem*,int,int))); + + connect(d->tv, SIGNAL(itemDblClicked(KexiTableItem*,int,int)), + this, SLOT(slotTVItemAccepted(KexiTableItem*,int,int))); +} + +void KexiComboBoxPopup::setData(KexiTableViewColumn *column, KexiDB::Field *field) +{ + if (column && !field) + field = column->field(); + if (!field) { + kexiwarn << "KexiComboBoxPopup::setData(): !field" << endl; + return; + } + + // case 1: simple related data + if (column && column->relatedData()) { + d->tv->setColumnStretchEnabled( true, -1 ); //only needed when using single column + setDataInternal( column->relatedData(), false /*!owner*/ ); + return; + } + // case 2: lookup field + KexiDB::LookupFieldSchema *lookupFieldSchema = 0; + if (field->table()) + lookupFieldSchema = field->table()->lookupFieldSchema( *field ); + delete d->privateQuery; + d->privateQuery = 0; + if (lookupFieldSchema) { + const QValueList<uint> visibleColumns( lookupFieldSchema->visibleColumns() ); + const bool multipleLookupColumnJoined = visibleColumns.count() > 1; +//! @todo support more RowSourceType's, not only table and query + KexiDB::Cursor *cursor = 0; + switch (lookupFieldSchema->rowSource().type()) { + case KexiDB::LookupFieldSchema::RowSource::Table: { + KexiDB::TableSchema *lookupTable + = field->table()->connection()->tableSchema( lookupFieldSchema->rowSource().name() ); + if (!lookupTable) +//! @todo errmsg + return; + if (multipleLookupColumnJoined) { + kdDebug() << "--- Orig query: " << endl; + lookupTable->query()->debug(); + d->privateQuery = new KexiDB::QuerySchema(*lookupTable->query()); + } + else { + cursor = field->table()->connection()->prepareQuery( *lookupTable ); + } + break; + } + case KexiDB::LookupFieldSchema::RowSource::Query: { + KexiDB::QuerySchema *lookupQuery + = field->table()->connection()->querySchema( lookupFieldSchema->rowSource().name() ); + if (!lookupQuery) +//! @todo errmsg + return; + if (multipleLookupColumnJoined) { + kdDebug() << "--- Orig query: " << endl; + lookupQuery->debug(); + d->privateQuery = new KexiDB::QuerySchema(*lookupQuery); + } + else { + cursor = field->table()->connection()->prepareQuery( *lookupQuery ); + } + break; + } + default:; + } + if (d->privateQuery) { + // append column computed using multiple columns + const KexiDB::QueryColumnInfo::Vector fieldsExpanded( d->privateQuery->fieldsExpanded() ); + uint fieldsExpandedSize( fieldsExpanded.size() ); + KexiDB::BaseExpr *expr = 0; + int count = visibleColumns.count(); + for (QValueList<uint>::ConstIterator it( visibleColumns.at(count-1) ); count>0; count--, --it) { + KexiDB::QueryColumnInfo *ci = ((*it) < fieldsExpandedSize) ? fieldsExpanded.at( *it ) : 0; + if (!ci) { + kdWarning() << "KexiComboBoxPopup::setData(): " << *it << " >= fieldsExpandedSize" << endl; + continue; + } + KexiDB::VariableExpr *fieldExpr + = new KexiDB::VariableExpr( ci->field->table()->name()+"."+ci->field->name() ); + fieldExpr->field = ci->field; + fieldExpr->tablePositionForField = d->privateQuery->tableBoundToColumn( *it ); + if (expr) { +//! @todo " " separator hardcoded... +//! @todo use SQL sub-parser here... + KexiDB::ConstExpr *constExpr = new KexiDB::ConstExpr(CHARACTER_STRING_LITERAL, " "); + expr = new KexiDB::BinaryExpr(KexiDBExpr_Arithm, constExpr, CONCATENATION, expr); + expr = new KexiDB::BinaryExpr(KexiDBExpr_Arithm, fieldExpr, CONCATENATION, expr); + } + else + expr = fieldExpr; + } + expr->debug(); + kdDebug() << expr->toString() << endl; + + KexiDB::Field *f = new KexiDB::Field(); + f->setExpression( expr ); + d->privateQuery->addField( f ); +#if 0 //does not work yet +// <remove later> +//! @todo temp: improved display by hiding all columns except the computed one + const int numColumntoHide = d->privateQuery->fieldsExpanded().count() - 1; + for (int i=0; i < numColumntoHide; i++) + d->privateQuery->setColumnVisible(i, false); +// </remove later> +#endif +//todo... + kdDebug() << "--- Private query: " << endl; + d->privateQuery->debug(); + cursor = field->table()->connection()->prepareQuery( *d->privateQuery ); + } + if (!cursor) +//! @todo errmsg + return; + + if (d->tv->data()) + d->tv->data()->disconnect( this ); + d->tv->setData( cursor ); + + connect( d->tv, SIGNAL(dataRefreshed()), this, SLOT(slotDataReloadRequested())); + updateSize(); + return; + } + + kdWarning() << "KexiComboBoxPopup::setData(KexiTableViewColumn &): no column relatedData \n - moving to setData(KexiDB::Field &)" << endl; + + // case 3: enum hints + d->tv->setColumnStretchEnabled( true, -1 ); //only needed when using single column + +//! @todo THIS IS PRIMITIVE: we'd need to employ KexiDB::Reference here! + d->int_f = new KexiDB::Field(field->name(), KexiDB::Field::Text); + KexiTableViewData *data = new KexiTableViewData(); + data->addColumn( new KexiTableViewColumn( *d->int_f ) ); + QValueVector<QString> hints = field->enumHints(); + for(uint i=0; i < hints.size(); i++) { + KexiTableItem *item = data->createItem();//new KexiTableItem(1); + (*item)[0]=QVariant(hints[i]); + kdDebug() << "added: '" << hints[i] <<"'"<<endl; + data->append( item ); + } + setDataInternal( data, true ); +} + +void KexiComboBoxPopup::setDataInternal( KexiTableViewData *data, bool owner ) +{ + if (d->tv->data()) + d->tv->data()->disconnect( this ); + d->tv->setData( data, owner ); + connect( d->tv, SIGNAL(dataRefreshed()), this, SLOT(slotDataReloadRequested())); + + updateSize(); +} + +void KexiComboBoxPopup::updateSize(int minWidth) +{ + const int rows = QMIN( d->max_rows, d->tv->rows() ); + + d->tv->adjustColumnWidthToContents(-1); + + KexiTableEdit *te = dynamic_cast<KexiTableEdit*>(parentWidget()); + const int width = QMAX( d->tv->tableSize().width(), + (te ? te->totalSize().width() : (parentWidget()?parentWidget()->width():0/*sanity*/)) ); + kexidbg << "KexiComboBoxPopup::updateSize(): size=" << size() << endl; + resize( QMAX(minWidth, width)/*+(d->tv->columns()>1?2:0)*/ /*(d->updateSizeCalled?0:1)*/, d->tv->rowHeight() * rows +2 ); + kexidbg << "KexiComboBoxPopup::updateSize(): size after=" << size() << endl; + + //stretch the last column + d->tv->setColumnStretchEnabled(true, d->tv->columns()-1); +} + +KexiTableView* KexiComboBoxPopup::tableView() +{ + return d->tv; +} + +void KexiComboBoxPopup::resize( int w, int h ) +{ + d->tv->horizontalScrollBar()->hide(); + d->tv->verticalScrollBar()->hide(); + d->tv->move(1,1); + d->tv->resize( w-2, h-2 ); + QFrame::resize(w,h); + update(); + updateGeometry(); +} + +void KexiComboBoxPopup::setMaxRows(int r) +{ + d->max_rows = r; +} + +int KexiComboBoxPopup::maxRows() const +{ + return d->max_rows; +} + +void KexiComboBoxPopup::slotTVItemAccepted(KexiTableItem *item, int row, int) +{ + hide(); + emit rowAccepted(item, row); +} + +bool KexiComboBoxPopup::eventFilter( QObject *o, QEvent *e ) +{ + if (o==this && e->type()==QEvent::Hide) { + emit hidden(); + } + else if (e->type()==QEvent::MouseButtonPress) { + kdDebug() << "QEvent::MousePress" << endl; + } + else if (o==d->tv) { + if (e->type()==QEvent::KeyPress) { + QKeyEvent *ke = static_cast<QKeyEvent*>(e); + const int k = ke->key(); + if ((ke->state()==NoButton && (k==Key_Escape || k==Key_F4)) + || (ke->state()==AltButton && k==Key_Up)) + { + hide(); + emit cancelled(); + return true; + } + } + } + return QFrame::eventFilter( o, e ); +} + +void KexiComboBoxPopup::slotDataReloadRequested() +{ + updateSize(); +} + +#include "kexicomboboxpopup.moc" diff --git a/kexi/widget/tableview/kexicomboboxpopup.h b/kexi/widget/tableview/kexicomboboxpopup.h new file mode 100644 index 00000000..42a15404 --- /dev/null +++ b/kexi/widget/tableview/kexicomboboxpopup.h @@ -0,0 +1,92 @@ +/* This file is part of the KDE project + Copyright (C) 2004-2007 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef KEXICOMBOBOXPOPUP_H +#define KEXICOMBOBOXPOPUP_H + +#include <qframe.h> + +class KexiComboBoxPopupPrivate; +class KexiTableView; +class KexiTableViewData; +class KexiTableViewColumn; +class KexiTableItem; +namespace KexiDB { + class Field; +} + +//! Internal class for displaying popup table view +class KexiComboBoxPopup : public QFrame +{ + Q_OBJECT + public: +//js TODO: more ctors! + /*! Constructor for creating a popup using definition from \a column. + If the column is lookup column, it's definition is used to display + one or more column within the popup. Otherwise column.field() is used + to display single-column data. */ + KexiComboBoxPopup(QWidget* parent, KexiTableViewColumn &column); + + /*! Alternative constructor supporting lookup fields and enum hints. */ + KexiComboBoxPopup(QWidget* parent, KexiDB::Field &field); + + virtual ~KexiComboBoxPopup(); + + KexiTableView* tableView(); + + /*! Sets maximum number of rows for this popup. */ + void setMaxRows(int r); + + /*! \return maximum number of rows for this popup. */ + int maxRows() const; + + /*! Default maximum number of rows for KexiComboBoxPopup objects. */ + static const int defaultMaxRows; + + virtual bool eventFilter( QObject *o, QEvent *e ); + + signals: + void rowAccepted(KexiTableItem *item, int row); + void cancelled(); + void hidden(); + + public slots: + virtual void resize( int w, int h ); + void updateSize(int minWidth = 0); + + protected slots: + void slotTVItemAccepted(KexiTableItem *item, int row, int col); + void slotDataReloadRequested(); + + protected: + void init(); + //! The main function for setting data; data can be set either by passing \a column or \a field. + //! The second case is used for lookup + void setData(KexiTableViewColumn *column, KexiDB::Field *field); + + //! used by setData() + void setDataInternal( KexiTableViewData *data, bool owner = true ); //!< helper + + KexiComboBoxPopupPrivate *d; + + friend class KexiComboBoxTableEdit; +}; + +#endif + diff --git a/kexi/widget/tableview/kexicomboboxtableedit.cpp b/kexi/widget/tableview/kexicomboboxtableedit.cpp new file mode 100644 index 00000000..75815a8a --- /dev/null +++ b/kexi/widget/tableview/kexicomboboxtableedit.cpp @@ -0,0 +1,446 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Peter Simonsson <psn@linux.se> + Copyright (C) 2003-2006 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <qlayout.h> +#include <qstyle.h> +#include <qwindowsstyle.h> +#include <qpainter.h> +#include <qapplication.h> +#include <qclipboard.h> + +#include "kexicomboboxtableedit.h" +#include <widget/utils/kexicomboboxdropdownbutton.h> +#include "kexicomboboxpopup.h" +#include "kexitableview.h" +#include "kexitableitem.h" +#include "kexi.h" + +#include <klineedit.h> + +//! @internal +class KexiComboBoxTableEdit::Private +{ +public: + Private() + : popup(0) + , currentEditorWidth(0) + , visibleTableViewColumn(0) + , internalEditor(0) + { + } + ~Private() + { + delete internalEditor; + delete visibleTableViewColumn; + } + + KPushButton *button; + KexiComboBoxPopup *popup; + int currentEditorWidth; + QSize totalSize; + KexiTableViewColumn* visibleTableViewColumn; + KexiTableEdit* internalEditor; +}; + +//====================================================== + +KexiComboBoxTableEdit::KexiComboBoxTableEdit(KexiTableViewColumn &column, QWidget *parent) + : KexiInputTableEdit(column, parent) + , KexiComboBoxBase() + , d(new Private()) +{ + setName("KexiComboBoxTableEdit"); + m_setVisibleValueOnSetValueInternal = true; + d->button = new KexiComboBoxDropDownButton( parentWidget() /*usually a viewport*/ ); + d->button->hide(); + d->button->setFocusPolicy( NoFocus ); + connect(d->button, SIGNAL(clicked()), this, SLOT(slotButtonClicked())); + + connect(m_lineedit, SIGNAL(textChanged(const QString&)), this, SLOT(slotLineEditTextChanged(const QString&))); + +// m_lineedit = new KLineEdit(this, "lineedit"); +// m_lineedit->setFrame(false); +// m_lineedit->setFrameStyle( QFrame::Plain | QFrame::Box ); +// m_lineedit->setLineWidth( 1 ); +// if (f.isNumericType()) { +// m_lineedit->setAlignment(AlignRight); +// } +// setView( m_lineedit ); + +// layout->addWidget(m_view); +// m_combo->setEditable( true ); +// m_combo->clear(); +// m_combo->insertStringList(f.enumHints()); +// QStringList::ConstIterator it, end( f.enumHints().constEnd() ); +// for ( it = f.enumHints().constBegin(); it != end; ++it) { +// if(!hints.at(i).isEmpty()) +// m_combo->insertItem(hints.at(i)); +// } + +//js: TODO +//js static_cast<KComboBox*>(m_view)->insertStringList(list); +//js static_cast<KComboBox*>(m_view)->setCurrentItem(static_cast<int>(t)); +} + +KexiComboBoxTableEdit::~KexiComboBoxTableEdit() +{ + delete d; +} + +void KexiComboBoxTableEdit::createInternalEditor(KexiDB::QuerySchema& schema) +{ + if (!m_column->visibleLookupColumnInfo || d->visibleTableViewColumn/*sanity*/) + return; + const KexiDB::Field::Type t = m_column->visibleLookupColumnInfo->field->type(); +//! @todo subtype? + KexiCellEditorFactoryItem *item = KexiCellEditorFactory::item(t); + if (!item || item->className()=="KexiInputTableEdit") + return; //unsupported type or there is no need to use subeditor for KexiInputTableEdit + //special cases: BLOB, Bool datatypes +//todo + //find real type to display + KexiDB::QueryColumnInfo *ci = m_column->visibleLookupColumnInfo; + KexiDB::QueryColumnInfo *visibleLookupColumnInfo = 0; + if (ci->indexForVisibleLookupValue() != -1) { + //Lookup field is defined + visibleLookupColumnInfo = schema.expandedOrInternalField( ci->indexForVisibleLookupValue() ); + } + d->visibleTableViewColumn = new KexiTableViewColumn(schema, *ci, visibleLookupColumnInfo); +//! todo set d->internalEditor visible and use it to enable data entering by hand + d->internalEditor = KexiCellEditorFactory::createEditor(*d->visibleTableViewColumn, 0); + m_lineedit->hide(); +} + +KexiComboBoxPopup *KexiComboBoxTableEdit::popup() const +{ + return d->popup; +} + +void KexiComboBoxTableEdit::setPopup(KexiComboBoxPopup *popup) +{ + d->popup = popup; +} + +void KexiComboBoxTableEdit::showFocus( const QRect& r, bool readOnly ) +{ +// d->button->move( pos().x()+ width(), pos().y() ); + updateFocus( r ); + d->button->setEnabled(!readOnly); + if (readOnly) + d->button->hide(); + else + d->button->show(); +} + +void KexiComboBoxTableEdit::resize(int w, int h) +{ + d->totalSize = QSize(w,h); + if (!column()->isReadOnly()) { + d->button->resize( h, h ); + QWidget::resize(w - d->button->width(), h); + } + m_rightMarginWhenFocused = m_rightMargin + (column()->isReadOnly() ? 0 : d->button->width()); + QRect r( pos().x(), pos().y(), w+1, h+1 ); + if (m_scrollView) + r.moveBy(m_scrollView->contentsX(), m_scrollView->contentsY()); + updateFocus( r ); + if (popup()) { + popup()->updateSize(); + } +} + +// internal +void KexiComboBoxTableEdit::updateFocus( const QRect& r ) +{ + if (!column()->isReadOnly()) { + if (d->button->width() > r.width()) + moveChild(d->button, r.right() + 1, r.top()); + else + moveChild(d->button, r.right() - d->button->width(), r.top() ); + } +} + +void KexiComboBoxTableEdit::hideFocus() +{ + d->button->hide(); +} + +QVariant KexiComboBoxTableEdit::visibleValue() +{ + return KexiComboBoxBase::visibleValue(); +/* KexiDB::LookupFieldSchema *lookupFieldSchema = this->lookupFieldSchema(); + if (!popup() || !lookupFieldSchema) + return QVariant(); + KexiTableItem *it = popup()->tableView()->selectedItem(); + return it ? it->at( lookupFieldSchema->visibleColumn() ) : QVariant();*/ +} + +void KexiComboBoxTableEdit::clear() +{ + m_lineedit->clear(); + KexiComboBoxBase::clear(); +} + +bool KexiComboBoxTableEdit::valueChanged() +{ + const tristate res = valueChangedInternal(); + if (~res) //no result: just compare values + return KexiInputTableEdit::valueChanged(); + return res == true; +} + +void KexiComboBoxTableEdit::paintFocusBorders( QPainter *p, QVariant &, int x, int y, int w, int h ) +{ +// d->currentEditorWidth = w; + if (!column()->isReadOnly()) { + if (w > d->button->width()) + w -= d->button->width(); + } + p->drawRect(x, y, w, h); +} + +void KexiComboBoxTableEdit::setupContents( QPainter *p, bool focused, const QVariant& val, + QString &txt, int &align, int &x, int &y_offset, int &w, int &h ) +{ + if (d->internalEditor) { + d->internalEditor->setupContents( p, focused, val, txt, align, x, y_offset, w, h ); + } + else { + KexiInputTableEdit::setupContents( p, focused, val, txt, align, x, y_offset, w, h ); + } + if (!column()->isReadOnly() && focused && (w > d->button->width())) + w -= (d->button->width() - x); + if (!val.isNull()) { + KexiTableViewData *relData = column()->relatedData(); + KexiDB::LookupFieldSchema *lookupFieldSchema = 0; + if (relData) { + int rowToHighlight; + txt = valueForString(val.toString(), &rowToHighlight, 0, 1); + } + else if ((lookupFieldSchema = this->lookupFieldSchema())) { + /* handled at at KexiTableView level + if (popup()) { + KexiTableItem *it = popup()->tableView()->selectedItem(); + if (it && lookupFieldSchema->visibleColumn()!=-1 && (int)it->size() >= lookupFieldSchema->visibleColumn()) + txt = it->at( lookupFieldSchema->visibleColumn() ).toString(); + }*/ + } + else { + //use 'enum hints' model + txt = field()->enumHint( val.toInt() ); + } + } +} + +void KexiComboBoxTableEdit::slotButtonClicked() +{ + // this method is sometimes called by hand: + // do not allow to simulate clicks when the button is disabled + if (column()->isReadOnly() || !d->button->isEnabled()) + return; + + if (m_mouseBtnPressedWhenPopupVisible) { + m_mouseBtnPressedWhenPopupVisible = false; + d->button->setOn(false); + return; + } + kdDebug() << "KexiComboBoxTableEdit::slotButtonClicked()" << endl; + if (!popup() || !popup()->isVisible()) { + kdDebug() << "SHOW POPUP" << endl; + showPopup(); + d->button->setOn(true); + } +} + +void KexiComboBoxTableEdit::slotPopupHidden() +{ + d->button->setOn(false); +// d->currentEditorWidth = 0; +} + +void KexiComboBoxTableEdit::updateButton() +{ + d->button->setOn(popup()->isVisible()); +} + +void KexiComboBoxTableEdit::hide() +{ + KexiInputTableEdit::hide(); + KexiComboBoxBase::hide(); + d->button->setOn(false); +} + +void KexiComboBoxTableEdit::show() +{ + KexiInputTableEdit::show(); + if (!column()->isReadOnly()) { + d->button->show(); + } +} + +bool KexiComboBoxTableEdit::handleKeyPress( QKeyEvent *ke, bool editorActive ) +{ + const int k = ke->key(); + if ((ke->state()==NoButton && k==Qt::Key_F4) + || (ke->state()==AltButton && k==Qt::Key_Down)) + { + //show popup + slotButtonClicked(); + return true; + } + else if (editorActive) { + const bool enterPressed = k==Qt::Key_Enter || k==Qt::Key_Return; + if (enterPressed && m_internalEditorValueChanged) { + createPopup(false); + selectItemForEnteredValueInLookupTable( m_userEnteredValue ); + return false; + } + + return handleKeyPressForPopup( ke ); + } + + return false; +} + +void KexiComboBoxTableEdit::slotLineEditTextChanged(const QString& s) +{ + slotInternalEditorValueChanged(s); +} + +int KexiComboBoxTableEdit::widthForValue( QVariant &val, const QFontMetrics &fm ) +{ + KexiTableViewData *relData = column() ? column()->relatedData() : 0; + if (lookupFieldSchema() || relData) { + // in 'lookupFieldSchema' or or 'related table data' model + // we're assuming val is already the text, not the index +//! @todo ok? + return QMAX(KEXITV_MINIMUM_COLUMN_WIDTH, fm.width(val.toString())); + } + //use 'enum hints' model + QValueVector<QString> hints = field()->enumHints(); + bool ok; + int idx = val.toInt(&ok); + if (!ok || idx < 0 || idx > int(hints.size()-1)) + return KEXITV_MINIMUM_COLUMN_WIDTH; + QString txt = hints.at( idx, &ok ); + if (!ok) + return KEXITV_MINIMUM_COLUMN_WIDTH; + return fm.width( txt ); +} + +bool KexiComboBoxTableEdit::eventFilter( QObject *o, QEvent *e ) +{ + if (!column()->isReadOnly() && e->type()==QEvent::MouseButtonPress && m_scrollView) { + QPoint gp = static_cast<QMouseEvent*>(e)->globalPos() + + QPoint(m_scrollView->childX(d->button), m_scrollView->childY(d->button)); + QRect r(d->button->mapToGlobal(d->button->geometry().topLeft()), + d->button->mapToGlobal(d->button->geometry().bottomRight())); + if (o==popup() && popup()->isVisible() && r.contains( gp )) { + m_mouseBtnPressedWhenPopupVisible = true; + } + } + return false; +} + +QSize KexiComboBoxTableEdit::totalSize() const +{ + return d->totalSize; +} + +QWidget *KexiComboBoxTableEdit::internalEditor() const +{ + return m_lineedit; +} + +void KexiComboBoxTableEdit::moveCursorToEndInInternalEditor() +{ + moveCursorToEnd(); +} + +void KexiComboBoxTableEdit::selectAllInInternalEditor() +{ + selectAll(); +} + +void KexiComboBoxTableEdit::moveCursorToEnd() +{ + m_lineedit->end(false/*!mark*/); +} + +void KexiComboBoxTableEdit::moveCursorToStart() +{ + m_lineedit->home(false/*!mark*/); +} + +void KexiComboBoxTableEdit::selectAll() +{ + m_lineedit->selectAll(); +} + +void KexiComboBoxTableEdit::setValueInInternalEditor(const QVariant& value) +{ + m_lineedit->setText(value.toString()); +} + +QVariant KexiComboBoxTableEdit::valueFromInternalEditor() +{ + return m_lineedit->text(); +} + +QPoint KexiComboBoxTableEdit::mapFromParentToGlobal(const QPoint& pos) const +{ + KexiTableView *tv = dynamic_cast<KexiTableView*>(m_scrollView); + if (!tv) + return QPoint(-1,-1); + return tv->viewport()->mapToGlobal(pos); +} + +int KexiComboBoxTableEdit::popupWidthHint() const +{ + return m_lineedit->width() + m_leftMargin + m_rightMarginWhenFocused; //QMAX(popup()->width(), d->currentEditorWidth); +} + +void KexiComboBoxTableEdit::handleCopyAction(const QVariant& value, const QVariant& visibleValue) +{ + Q_UNUSED(value); +//! @todo does not work with BLOBs! + qApp->clipboard()->setText( visibleValue.toString() ); +} + +void KexiComboBoxTableEdit::handleAction(const QString& actionName) +{ + const bool alreadyVisible = m_lineedit->isVisible(); + + if (actionName=="edit_paste") { + if (!alreadyVisible) { //paste as the entire text if the cell was not in edit mode + emit editRequested(); + m_lineedit->clear(); + } +//! @todo does not work with BLOBs! + setValueInInternalEditor( qApp->clipboard()->text() ); + } + else + KexiInputTableEdit::handleAction(actionName); +} + + +KEXI_CELLEDITOR_FACTORY_ITEM_IMPL(KexiComboBoxEditorFactoryItem, KexiComboBoxTableEdit) + +#include "kexicomboboxtableedit.moc" diff --git a/kexi/widget/tableview/kexicomboboxtableedit.h b/kexi/widget/tableview/kexicomboboxtableedit.h new file mode 100644 index 00000000..713fa55e --- /dev/null +++ b/kexi/widget/tableview/kexicomboboxtableedit.h @@ -0,0 +1,166 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Peter Simonsson <psn@linux.se> + Copyright (C) 2003-2006 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef _KEXICOMBOBOXTABLEEDIT_H_ +#define _KEXICOMBOBOXTABLEEDIT_H_ + +#include "kexidb/field.h" +#include "kexiinputtableedit.h" +#include "kexicomboboxbase.h" +#include <kexidb/lookupfieldschema.h> + +class KPushButton; +class KLineEdit; +class KexiComboBoxPopup; +class KexiTableItem; +class KexiTableViewColumn; + +/*! @short Drop-down cell editor. +*/ +class KexiComboBoxTableEdit : public KexiInputTableEdit, public KexiComboBoxBase +{ + Q_OBJECT + + public: + KexiComboBoxTableEdit(KexiTableViewColumn &column, QWidget *parent=0); + virtual ~KexiComboBoxTableEdit(); + + //! Implemented for KexiComboBoxBase + virtual KexiTableViewColumn *column() const { return m_column; } + + //! Implemented for KexiComboBoxBase + virtual KexiDB::Field *field() const { return m_column->field(); } + + //! Implemented for KexiComboBoxBase + virtual QVariant origValue() const { return m_origValue; } + + virtual void setValueInternal(const QVariant& add, bool removeOld) + { KexiComboBoxBase::setValueInternal(add, removeOld); } + + virtual QVariant value() { return KexiComboBoxBase::value(); } + + virtual void clear(); + + virtual bool valueChanged(); + + virtual QVariant visibleValue(); + + /*! Reimplemented: resizes a view(). */ + virtual void resize(int w, int h); + + virtual void showFocus( const QRect& r, bool readOnly ); + + virtual void hideFocus(); + + virtual void paintFocusBorders( QPainter *p, QVariant &cal, int x, int y, int w, int h ); + + /*! Setups contents of the cell. As a special case, if there is lookup field schema + defined, \a val already contains the visible value (usually the text) + set by \ref KexiTableView::paintcell(), so there is noo need to lookup the value + in the combo box's popup. */ + virtual void setupContents( QPainter *p, bool focused, const QVariant& val, + QString &txt, int &align, int &x, int &y_offset, int &w, int &h ); + + /*! Used to handle key press events for the item. */ + virtual bool handleKeyPress( QKeyEvent *ke, bool editorActive ); + + virtual int widthForValue( QVariant &val, const QFontMetrics &fm ); + + virtual void hide(); + virtual void show(); + + /*! \return total size of this editor, including popup button. */ + virtual QSize totalSize() const; + + virtual void createInternalEditor(KexiDB::QuerySchema& schema); + + /*! Reimplemented after KexiInputTableEdit. */ + virtual void handleAction(const QString& actionName); + + /*! Reimplemented after KexiInputTableEdit. + For a special case (combo box), \a visibleValue can be provided, + so it can be copied to the clipboard instead of unreadable \a value. */ + virtual void handleCopyAction(const QVariant& value, const QVariant& visibleValue); + + public slots: + //! Implemented for KexiDataItemInterface + virtual void moveCursorToEnd(); + + //! Implemented for KexiDataItemInterface + virtual void moveCursorToStart(); + + //! Implemented for KexiDataItemInterface + virtual void selectAll(); + + protected slots: + void slotButtonClicked(); + void slotRowAccepted(KexiTableItem *item, int row) { KexiComboBoxBase::slotRowAccepted(item, row); } + void slotItemSelected(KexiTableItem* item) { KexiComboBoxBase::slotItemSelected(item); } + void slotInternalEditorValueChanged(const QVariant& v) + { KexiComboBoxBase::slotInternalEditorValueChanged(v); } + void slotLineEditTextChanged(const QString& s); + void slotPopupHidden(); + + protected: + //! internal + void updateFocus( const QRect& r ); + + virtual bool eventFilter( QObject *o, QEvent *e ); + + //! Implemented for KexiComboBoxBase + virtual QWidget *internalEditor() const; + + //! Implemented for KexiComboBoxBase + virtual void moveCursorToEndInInternalEditor(); + + //! Implemented for KexiComboBoxBase + virtual void selectAllInInternalEditor(); + + //! Implemented for KexiComboBoxBase + virtual void setValueInInternalEditor(const QVariant& value); + + //! Implemented for KexiComboBoxBase + virtual QVariant valueFromInternalEditor(); + + //! Implemented for KexiComboBoxBase + virtual void editRequested() { KexiInputTableEdit::editRequested(); } + + //! Implemented for KexiComboBoxBase + virtual void acceptRequested() { KexiInputTableEdit::acceptRequested(); } + + //! Implemented for KexiComboBoxBase + virtual QPoint mapFromParentToGlobal(const QPoint& pos) const; + + //! Implemented for KexiComboBoxBase + virtual int popupWidthHint() const; + + //! Implemented this to update button state. + virtual void updateButton(); + + virtual KexiComboBoxPopup *popup() const; + virtual void setPopup(KexiComboBoxPopup *popup); + + class Private; + Private *d; +}; + +KEXI_DECLARE_CELLEDITOR_FACTORY_ITEM(KexiComboBoxEditorFactoryItem) + +#endif diff --git a/kexi/widget/tableview/kexidataawareobjectiface.cpp b/kexi/widget/tableview/kexidataawareobjectiface.cpp new file mode 100644 index 00000000..59edbed3 --- /dev/null +++ b/kexi/widget/tableview/kexidataawareobjectiface.cpp @@ -0,0 +1,2108 @@ +/* This file is part of the KDE project + Copyright (C) 2005-2007 Jaroslaw Staniek <js@iidea.pl> + + Based on KexiTableView code. + Copyright (C) 2002 Till Busch <till@bux.at> + Copyright (C) 2003 Lucijan Busch <lucijan@gmx.at> + Copyright (C) 2003 Daniel Molkentin <molkentin@kde.org> + Copyright (C) 2003 Joseph Wenninger <jowenn@kde.org> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "kexidataawareobjectiface.h" + +#include <qscrollview.h> +#include <qlabel.h> +#include <qtooltip.h> + +#include <kmessagebox.h> + +#include <kexi.h> +#include <kexiutils/validator.h> +#include <widget/utils/kexirecordnavigator.h> +#include <widget/utils/kexirecordmarker.h> +#include <kexidb/roweditbuffer.h> +#include <kexidataiteminterface.h> + +#include "kexitableviewheader.h" + +using namespace KexiUtils; + +KexiDataAwareObjectInterface::KexiDataAwareObjectInterface() +{ + m_data = 0; + m_itemIterator = 0; + m_readOnly = -1; //don't know + m_insertingEnabled = -1; //don't know + m_isSortingEnabled = true; + m_isFilteringEnabled = true; + m_deletionPolicy = AskDelete; + m_inside_acceptEditor = false; + m_acceptsRowEditAfterCellAccepting = false; + m_internal_acceptsRowEditAfterCellAccepting = false; + m_contentsMousePressEvent_dblClick = false; + m_navPanel = 0; + m_initDataContentsOnShow = false; + m_cursorPositionSetExplicityBeforeShow = false; + m_verticalHeader = 0; + m_horizontalHeader = 0; + m_insertItem = 0; +// m_rowEditBuffer = 0; + m_spreadSheetMode = false; + m_dropsAtRowEnabled = false; + m_updateEntireRowWhenMovingToOtherRow = false; + m_dragIndicatorLine = -1; + m_emptyRowInsertingEnabled = false; + m_popupMenu = 0; + m_contextMenuEnabled = true; + m_rowWillBeDeleted = -1; + m_alsoUpdateNextRow = false; + m_verticalHeaderAlreadyAdded = false; + m_vScrollBarValueChanged_enabled = true; + m_scrollbarToolTipsEnabled = true; + m_scrollBarTipTimerCnt = 0; + m_scrollBarTip = 0; + m_recentSearchDirection = KexiSearchAndReplaceViewInterface::Options::DefaultSearchDirection; + + // setup scrollbar tooltip and related members + m_scrollBarTip = new QLabel("",0, "vScrollBarToolTip", + Qt::WStyle_Customize |Qt::WStyle_NoBorder|Qt::WX11BypassWM|Qt::WStyle_StaysOnTop|Qt::WStyle_Tool); + m_scrollBarTip->setPalette(QToolTip::palette()); + m_scrollBarTip->setMargin(2); + m_scrollBarTip->setIndent(0); + m_scrollBarTip->setAlignment(Qt::AlignCenter); + m_scrollBarTip->setFrameStyle( QFrame::Plain | QFrame::Box ); + m_scrollBarTip->setLineWidth(1); + + clearVariables(); +} + +KexiDataAwareObjectInterface::~KexiDataAwareObjectInterface() +{ + delete m_insertItem; +// delete m_rowEditBuffer; + delete m_itemIterator; + delete m_scrollBarTip; + //we cannot delete m_data here... subclasses should do this +} + +void KexiDataAwareObjectInterface::clearVariables() +{ + m_editor = 0; +// m_rowEditBuffer = 0; + m_rowEditing = false; + m_newRowEditing = false; + m_curRow = -1; + m_curCol = -1; + m_currentItem = 0; +} + +void KexiDataAwareObjectInterface::setData( KexiTableViewData *data, bool owner ) +{ + const bool theSameData = m_data && m_data==data; + if (m_owner && m_data && m_data!=data/*don't destroy if it's the same*/) { + kexidbg << "KexiDataAwareObjectInterface::setData(): destroying old data (owned)" << endl; + delete m_itemIterator; + delete m_data; //destroy old data + m_data = 0; + m_itemIterator = 0; + } + m_owner = owner; + m_data = data; + if (m_data) + m_itemIterator = m_data->createIterator(); + + kdDebug(44021) << "KexiDataAwareObjectInterface::setData(): using shared data" << endl; + //add columns +//OK? + clearColumnsInternal(false); + if (m_data) { + int i = 0; + for (KexiTableViewColumn::ListIterator it(m_data->columns); + it.current(); ++it, i++) + { + KexiDB::Field *f = it.current()->field(); + if (it.current()->visible()) { + int wid = f->width(); + if (wid==0) + wid=KEXI_DEFAULT_DATA_COLUMN_WIDTH;//default col width in pixels +//! @todo add col width configuration and storage + addHeaderColumn(it.current()->isHeaderTextVisible() + ? it.current()->captionAliasOrName() : QString::null, + f->description(), it.current()->icon(), wid); + } + } + } + if (m_verticalHeader) { + m_verticalHeader->clear(); + if (m_data) + m_verticalHeader->addLabels(m_data->count()); + } + if (m_data && m_data->count()==0) + m_navPanel->setCurrentRecordNumber(0+1); + + if (m_data && !theSameData) { +//! @todo: store sorting settings? + setSorting(-1); +// connect(m_data, SIGNAL(refreshRequested()), this, SLOT(slotRefreshRequested())); + connectToReloadDataSlot(m_data, SIGNAL(reloadRequested())); + QObject* thisObject = dynamic_cast<QObject*>(this); + if (thisObject) { + QObject::connect(m_data, SIGNAL(destroying()), thisObject, SLOT(slotDataDestroying())); + QObject::connect(m_data, SIGNAL(rowsDeleted( const QValueList<int> & )), + thisObject, SLOT(slotRowsDeleted( const QValueList<int> & ))); + QObject::connect(m_data, SIGNAL(aboutToDeleteRow(KexiTableItem&,KexiDB::ResultInfo*,bool)), + thisObject, SLOT(slotAboutToDeleteRow(KexiTableItem&,KexiDB::ResultInfo*,bool))); + QObject::connect(m_data, SIGNAL(rowDeleted()), thisObject, SLOT(slotRowDeleted())); + QObject::connect(m_data, SIGNAL(rowInserted(KexiTableItem*,bool)), + thisObject, SLOT(slotRowInserted(KexiTableItem*,bool))); + QObject::connect(m_data, SIGNAL(rowInserted(KexiTableItem*,uint,bool)), + thisObject, SLOT(slotRowInserted(KexiTableItem*,uint,bool))); //not db-aware + QObject::connect(m_data, SIGNAL(rowRepaintRequested(KexiTableItem&)), + thisObject, SLOT(slotRowRepaintRequested(KexiTableItem&))); + // setup scrollbar's tooltip + QObject::connect(verticalScrollBar(),SIGNAL(sliderReleased()), + thisObject,SLOT(vScrollBarSliderReleased())); + QObject::connect(verticalScrollBar(),SIGNAL(valueChanged(int)), + thisObject,SLOT(vScrollBarValueChanged(int))); + QObject::connect(&m_scrollBarTipTimer,SIGNAL(timeout()), + thisObject,SLOT(scrollBarTipTimeout())); + } + } + + if (!m_data) { +// clearData(); + cancelRowEdit(); + //m_data->clearInternal(); + clearVariables(); + } + else { + if (!m_insertItem) {//first setData() call - add 'insert' item + m_insertItem = m_data->createItem(); //new KexiTableItem(m_data->columns.count()); + } + else {//just reinit + m_insertItem->init(m_data->columns.count()); + } + } + + //update gui mode + m_navPanel->setInsertingEnabled(m_data && isInsertingEnabled()); + if (m_verticalHeader) + m_verticalHeader->showInsertRow(m_data && isInsertingEnabled()); + + initDataContents(); + updateIndicesForVisibleValues(); + + if (m_data) + /*emit*/ dataSet( m_data ); +} + +void KexiDataAwareObjectInterface::initDataContents() +{ + m_editor = 0; +// QSize s(tableSize()); +// resizeContents(s.width(),s.height()); + + m_navPanel->setRecordCount(rows()); + + if (m_data && !m_cursorPositionSetExplicityBeforeShow) { + //set current row: + m_currentItem = 0; + int curRow = -1, curCol = -1; + if (m_data->columnsCount()>0) { + if (rows()>0) { + m_itemIterator->toFirst(); + m_currentItem = **m_itemIterator; + curRow = 0; + curCol = 0; + } + else {//no data + if (isInsertingEnabled()) { + m_currentItem = m_insertItem; + curRow = 0; + curCol = 0; + } + } + } + setCursorPosition(curRow, curCol, true/*force*/); + } + ensureCellVisible(m_curRow, m_curCol); +// updateRowCountInfo(); +// setNavRowCount(rows()); + +//OK? +// updateContents(); + updateWidgetContents(); + + m_cursorPositionSetExplicityBeforeShow = false; + + /*emit*/ dataRefreshed(); +} + +void KexiDataAwareObjectInterface::setSortingEnabled(bool set) +{ + if (m_isSortingEnabled && !set) + setSorting(-1); + m_isSortingEnabled = set; + /*emit*/ reloadActions(); +} + +void KexiDataAwareObjectInterface::setSorting(int col, bool ascending) +{ + if (!m_data || !m_isSortingEnabled) + return; +// d->pTopHeader->setSortIndicator(col, ascending ? Ascending : Descending); + setLocalSortingOrder(col, ascending ? 1 : -1); + m_data->setSorting(col, ascending); +} + +int KexiDataAwareObjectInterface::dataSortedColumn() const +{ + if (m_data && m_isSortingEnabled) + return m_data->sortedColumn(); + return -1; +} + +int KexiDataAwareObjectInterface::dataSortingOrder() const +{ + return m_data ? m_data->sortingOrder() : 0; +} + +bool KexiDataAwareObjectInterface::sort() +{ + if (!m_data || !m_isSortingEnabled) + return false; + + if (rows() < 2) + return true; + + if (!acceptRowEdit()) + return false; + + const int oldRow = m_curRow; + if (m_data->sortedColumn()!=-1) + m_data->sort(); + + //locate current record + if (!m_currentItem) { + m_itemIterator->toFirst(); + m_currentItem = **m_itemIterator; //m_data->first(); + m_curRow = 0; + if (!m_currentItem) + return true; + } + if (m_currentItem != m_insertItem) { + m_curRow = m_data->findRef(m_currentItem); + int jump = m_curRow - oldRow; + if (jump<0) + (*m_itemIterator) -= -jump; + else + (*m_itemIterator) += jump; + } + + updateGUIAfterSorting(); + editorShowFocus( m_curRow, m_curCol ); + if (m_verticalHeader) + m_verticalHeader->setCurrentRow(m_curRow); + if (m_horizontalHeader) + m_horizontalHeader->setSelectedSection(m_curCol); + if (m_navPanel) + m_navPanel->setCurrentRecordNumber(m_curRow+1); + return true; +} + +void KexiDataAwareObjectInterface::sortAscending() +{ + if (currentColumn()<0) + return; + sortColumnInternal( currentColumn(), 1 ); +} + +void KexiDataAwareObjectInterface::sortDescending() +{ + if (currentColumn()<0) + return; + sortColumnInternal( currentColumn(), -1 ); +} + +void KexiDataAwareObjectInterface::sortColumnInternal(int col, int order) +{ + //-select sorting + bool asc; + if (order == 0) {// invert + if (col==dataSortedColumn() && dataSortingOrder()==1) + asc = dataSortingOrder()==-1; //inverse sorting for this column -> descending order + else + asc = true; + } + else + asc = (order==1); + + int prevSortOrder = currentLocalSortingOrder(); + const int prevSortColumn = currentLocalSortingOrder(); + setSorting( col, asc ); + //-perform sorting + if (!sort()) + setLocalSortingOrder(prevSortColumn, prevSortOrder); //this will also remove indicator + //if prevSortColumn==-1 + if (col != prevSortColumn) + /*emit*/ sortedColumnChanged(col); +} + +bool KexiDataAwareObjectInterface::isInsertingEnabled() const +{ + if (isReadOnly()) + return false; + if (m_insertingEnabled == 1 || m_insertingEnabled == 0) + return (bool)m_insertingEnabled; + if (!hasData()) + return true; + return m_data->isInsertingEnabled(); +} + +void KexiDataAwareObjectInterface::setFilteringEnabled(bool set) +{ + m_isFilteringEnabled = set; +} + +bool KexiDataAwareObjectInterface::isDeleteEnabled() const +{ + return (m_deletionPolicy != NoDelete) && !isReadOnly(); +} + +void KexiDataAwareObjectInterface::setDeletionPolicy(DeletionPolicy policy) +{ + m_deletionPolicy = policy; +// updateContextMenu(); +} + +void KexiDataAwareObjectInterface::setReadOnly(bool set) +{ + if (isReadOnly() == set || (m_data && m_data->isReadOnly() && !set)) + return; //not allowed! + m_readOnly = (set ? 1 : 0); + if (set) + setInsertingEnabled(false); + updateWidgetContents(); + /*emit*/ reloadActions(); +} + +bool KexiDataAwareObjectInterface::isReadOnly() const +{ + if (!hasData()) + return true; + if (m_readOnly == 1 || m_readOnly == 0) + return (bool)m_readOnly; + if (!hasData()) + return true; + return m_data->isReadOnly(); +} + +void KexiDataAwareObjectInterface::setInsertingEnabled(bool set) +{ + if (isInsertingEnabled() == set || (m_data && !m_data->isInsertingEnabled() && set)) + return; //not allowed! + m_insertingEnabled = (set ? 1 : 0); + m_navPanel->setInsertingEnabled(set); + if (m_verticalHeader) + m_verticalHeader->showInsertRow(set); + if (set) + setReadOnly(false); +// update(); + updateWidgetContents(); + /*emit*/ reloadActions(); +} + +void KexiDataAwareObjectInterface::setSpreadSheetMode() +{ + m_spreadSheetMode = true; + setSortingEnabled( false ); + setInsertingEnabled( false ); + setAcceptsRowEditAfterCellAccepting( true ); + setFilteringEnabled( false ); + setEmptyRowInsertingEnabled( true ); + m_navPanelEnabled = false; +} + +void KexiDataAwareObjectInterface::selectNextRow() +{ + selectRow( QMIN( rows() - 1 +(isInsertingEnabled()?1:0), m_curRow + 1 ) ); +} + +void KexiDataAwareObjectInterface::selectPrevPage() +{ + selectRow( + QMAX( 0, m_curRow - rowsPerPage() ) + ); +} + +void KexiDataAwareObjectInterface::selectNextPage() +{ + selectRow( + QMIN( + rows() - 1 + (isInsertingEnabled()?1:0), + m_curRow + rowsPerPage() + ) + ); +} + +void KexiDataAwareObjectInterface::selectFirstRow() +{ + selectRow(0); +} + +void KexiDataAwareObjectInterface::selectLastRow() +{ +// selectRow(rows() - 1 + (isInsertingEnabled()?1:0)); + selectRow(rows() - 1); +} + +void KexiDataAwareObjectInterface::selectRow(int row) +{ + m_vScrollBarValueChanged_enabled = false; //disable tooltip + setCursorPosition(row, -1); + m_vScrollBarValueChanged_enabled = true; +} + +void KexiDataAwareObjectInterface::selectPrevRow() +{ + selectRow( QMAX( 0, m_curRow - 1 ) ); +} + +void KexiDataAwareObjectInterface::clearSelection() +{ +// selectRow( -1 ); + int oldRow = m_curRow; +// int oldCol = m_curCol; + m_curRow = -1; + m_curCol = -1; + m_currentItem = 0; + updateRow( oldRow ); + m_navPanel->setCurrentRecordNumber(0); +// setNavRowNumber(-1); +} + +void KexiDataAwareObjectInterface::setCursorPosition(int row, int col/*=-1*/, bool forceSet) +{ + int newrow = row; + int newcol = col; + + if(rows() <= 0) { + if (m_verticalHeader) + m_verticalHeader->setCurrentRow(-1); + if (m_horizontalHeader) + m_horizontalHeader->setSelectedSection(-1); + if (isInsertingEnabled()) { + m_currentItem=m_insertItem; + newrow=0; + if (col>=0) + newcol=col; + else + newcol=0; + } + else { + m_currentItem=0; + m_curRow=-1; + m_curCol=-1; + return; + } + } + + if(col>=0) + { + newcol = QMAX(0, col); + newcol = QMIN(columns() - 1, newcol); + } + else { + newcol = m_curCol; //no changes + newcol = QMAX(0, newcol); //may not be < 0 ! + } + newrow = QMAX(0, row); + newrow = QMIN(rows() - 1 + (isInsertingEnabled()?1:0), newrow); + +// d->pCurrentItem = itemAt(d->curRow); +// kdDebug(44021) << "setCursorPosition(): d->curRow=" << d->curRow << " oldRow=" << oldRow << " d->curCol=" << d->curCol << " oldCol=" << oldCol << endl; + + if ( forceSet || m_curRow != newrow || m_curCol != newcol ) + { + kexidbg << "setCursorPosition(): " <<QString("old:%1,%2 new:%3,%4").arg(m_curCol) + .arg(m_curRow).arg(newcol).arg(newrow) << endl; + + // cursor moved: get rid of editor + if (m_editor) { + if (!m_contentsMousePressEvent_dblClick) { + if (!acceptEditor()) { + return; + } + //update row num. again + newrow = QMIN( rows() - 1 + (isInsertingEnabled()?1:0), newrow); + } + } + if (m_errorMessagePopup) { + m_errorMessagePopup->close(); + } + + if (m_curRow != newrow || forceSet) {//update current row info + m_navPanel->setCurrentRecordNumber(newrow+1); +// setNavRowNumber(newrow); +// d->navBtnPrev->setEnabled(newrow>0); +// d->navBtnFirst->setEnabled(newrow>0); +// d->navBtnNext->setEnabled(newrow<(rows()-1+(isInsertingEnabled()?1:0))); +// d->navBtnLast->setEnabled(newrow!=(rows()-1)); + } + + // cursor moved to other row: end of row editing + bool newRowInserted = false; + if (m_rowEditing && m_curRow != newrow) { + newRowInserted = m_newRowEditing; + if (!acceptRowEdit()) { + //accepting failed: cancel setting the cursor + return; + } + //update row number, because number of rows changed + newrow = QMIN( rows() - 1 + (isInsertingEnabled()?1:0), newrow); + + m_navPanel->setCurrentRecordNumber(newrow+1); //refresh + } + + //change position + int oldRow = m_curRow; + int oldCol = m_curCol; + m_curRow = newrow; + m_curCol = newcol; + +// int cw = columnWidth( d->curCol ); +// int rh = rowHeight(); +// ensureVisible( columnPos( d->curCol ) + cw / 2, rowPos( d->curRow ) + rh / 2, cw / 2, rh / 2 ); +// center(columnPos(d->curCol) + cw / 2, rowPos(d->curRow) + rh / 2, cw / 2, rh / 2); +// kdDebug(44021) << " contentsY() = "<< contentsY() << endl; + +//js if (oldRow > d->curRow) +//js ensureVisible(columnPos(d->curCol), rowPos(d->curRow) + rh, columnWidth(d->curCol), rh); +//js else// if (oldRow <= d->curRow) +//js ensureVisible(columnPos(d->curCol), rowPos(d->curRow), columnWidth(d->curCol), rh); + + + //show editor-dependent focus, if we're changing the current column + if (oldCol>=0 && oldCol<columns() && m_curCol!=oldCol) { + //find the editor for this column + KexiDataItemInterface *edit = editor( oldCol ); + if (edit) { + edit->hideFocus(); + } + } + + // position changed, so subsequent searching should be started from scratch + // (e.g. from the current cell or the top-left cell) + m_positionOfRecentlyFoundValue.exists = false; + + //show editor-dependent focus, if needed + editorShowFocus( m_curRow, m_curCol ); + + if (m_updateEntireRowWhenMovingToOtherRow) + updateRow( oldRow ); + else + updateCell( oldRow, oldCol ); + +// //quite clever: ensure the cell is visible: +// ensureCellVisible(m_curRow, m_curCol); + +// QPoint pcenter = QRect( columnPos(d->curCol), rowPos(d->curRow), columnWidth(d->curCol), rh).center(); +// ensureVisible(pcenter.x(), pcenter.y(), columnWidth(d->curCol)/2, rh/2); + +// ensureVisible(columnPos(d->curCol), rowPos(d->curRow) - contentsY(), columnWidth(d->curCol), rh); + if (m_verticalHeader && (oldRow != m_curRow || forceSet)) + m_verticalHeader->setCurrentRow(m_curRow); + + if (m_updateEntireRowWhenMovingToOtherRow) + updateRow( m_curRow ); + else + updateCell( m_curRow, m_curCol ); + + if (m_curCol != oldCol || m_curRow != oldRow || forceSet) {//ensure this is also refreshed + if (!m_updateEntireRowWhenMovingToOtherRow) //only if entire row has not been updated + updateCell( oldRow, m_curCol ); + } + //update row + if (forceSet || m_curRow != oldRow) { + if (isInsertingEnabled() && m_curRow == rows()) { + kdDebug(44021) << "NOW insert item is current" << endl; + m_currentItem = m_insertItem; + } + else { + kdDebug(44021) << QString("NOW item at %1 (%2) is current") + .arg(m_curRow).arg((ulong)itemAt(m_curRow)) << endl; + //NOT EFFECTIVE!!!!!!!!!!! + //set item iterator + if (!newRowInserted && isInsertingEnabled() && m_currentItem == m_insertItem && m_curRow == (rows()-1)) { + //moving from 'insert item' to last item + m_itemIterator->toLast(); + } + else if (!newRowInserted && !forceSet && m_currentItem != m_insertItem && 0==m_curRow) + m_itemIterator->toFirst(); + else if (!newRowInserted && !forceSet && m_currentItem != m_insertItem && oldRow>=0 && (oldRow+1)==m_curRow) //just move next + ++(*m_itemIterator); + else if (!newRowInserted && !forceSet && m_currentItem != m_insertItem && oldRow>=0 && (oldRow-1)==m_curRow) //just move back + --(*m_itemIterator); + else { //move at: + m_itemIterator->toFirst(); + (*m_itemIterator)+=m_curRow; + } + if (!**m_itemIterator) { //sanity + m_itemIterator->toFirst(); + (*m_itemIterator)+=m_curRow; + } + m_currentItem = **m_itemIterator; + //itemAt(m_curRow); + } + } + + //quite clever: ensure the cell is visible: + ensureCellVisible(m_curRow, m_curCol); + + if (m_horizontalHeader && (oldCol != m_curCol || forceSet)) + m_horizontalHeader->setSelectedSection(m_curCol); + + /*emit*/ itemSelected(m_currentItem); + /*emit*/ cellSelected(m_curCol, m_curRow); + /* only needed for forms */ + selectCellInternal(); + } + else { + kexidbg << "setCursorPosition(): NO CHANGE" << endl; + } + + if(m_initDataContentsOnShow) { + m_cursorPositionSetExplicityBeforeShow = true; + } +} + +bool KexiDataAwareObjectInterface::acceptRowEdit() +{ + if (!m_rowEditing || /*sanity*/!m_data->rowEditBuffer()) + return true; + if (m_inside_acceptEditor) { + m_internal_acceptsRowEditAfterCellAccepting = true; + return true; + } + m_internal_acceptsRowEditAfterCellAccepting = false; + + const int columnEditedBeforeAccepting = m_editor ? currentColumn() : -1; + if (!acceptEditor()) + return false; + kdDebug() << "EDIT ROW ACCEPTING..." << endl; + + bool success = true; +// bool allow = true; +// int faultyColumn = -1; // will be !=-1 if cursor has to be moved to that column + const bool inserting = m_newRowEditing; +// QString msg, desc; +// bool inserting = d->pInsertItem && d->pInsertItem==d->pCurrentItem; + + if (m_data->rowEditBuffer()->isEmpty() && !m_newRowEditing) { +/* if (d->newRowEditing) { + cancelRowEdit(); + kdDebug() << "-- NOTHING TO INSERT!!!" << endl; + return true; + } + else {*/ + kdDebug() << "-- NOTHING TO ACCEPT!!!" << endl; +// } + } + else {//not empty edit buffer or new row to insert: + if (m_newRowEditing) { +// emit aboutToInsertRow(d->pCurrentItem, m_data->rowEditBuffer(), success, &faultyColumn); +// if (success) { + kdDebug() << "-- INSERTING: " << endl; + m_data->rowEditBuffer()->debug(); + success = m_data->saveNewRow(*m_currentItem); +// if (!success) { +// } +// } + } + else { +// emit aboutToUpdateRow(d->pCurrentItem, m_data->rowEditBuffer(), success, &faultyColumn); + if (success) { + //accept changes for this row: + kdDebug() << "-- UPDATING: " << endl; + m_data->rowEditBuffer()->debug(); + kdDebug() << "-- BEFORE: " << endl; + m_currentItem->debug(); + success = m_data->saveRowChanges(*m_currentItem);//, &msg, &desc, &faultyColumn); + kdDebug() << "-- AFTER: " << endl; + m_currentItem->debug(); + +// if (!success) { +// } + } + } + } + + if (success) { + //editing is finished: + if (m_newRowEditing) { + //update current-item-iterator + m_itemIterator->toLast(); + m_currentItem = **m_itemIterator; + } + m_rowEditing = false; + m_newRowEditing = false; + //indicate on the vheader that we are not editing + if (m_verticalHeader) + m_verticalHeader->setEditRow(-1); + + updateAfterAcceptRowEdit(); + + kdDebug() << "EDIT ROW ACCEPTED:" << endl; +// /*debug*/itemAt(m_curRow); + + if (inserting) { +// emit rowInserted(d->pCurrentItem); + //update navigator's data + m_navPanel->setRecordCount(rows()); + } + else { +// emit rowUpdated(d->pCurrentItem); + } + + /*emit*/ rowEditTerminated(m_curRow); + } + else { +// if (!allow) { +// kdDebug() << "INSERT/EDIT ROW - DISALLOWED by signal!" << endl; +// } +// else { +// kdDebug() << "EDIT ROW - ERROR!" << endl; +// } + int faultyColumn = -1; + if (m_data->result()->column >= 0 && m_data->result()->column < columns()) + faultyColumn = m_data->result()->column; + else if (columnEditedBeforeAccepting >= 0) + faultyColumn = columnEditedBeforeAccepting; + if (faultyColumn >= 0) { + setCursorPosition(m_curRow, faultyColumn); + } + + const int button = showErrorMessageForResult( m_data->result() ); + if (KMessageBox::No == button) { + //discard changes + cancelRowEdit(); + } + else { + if (faultyColumn >= 0) { + //edit this cell + startEditCurrentCell(); + } + } + } + + return success; +} + +bool KexiDataAwareObjectInterface::cancelRowEdit() +{ + if (!hasData()) + return false; + if (!m_rowEditing) + return false; + cancelEditor(); + m_rowEditing = false; + //indicate on the vheader that we are not editing + if (m_verticalHeader) + m_verticalHeader->setEditRow(-1); + m_alsoUpdateNextRow = m_newRowEditing; + if (m_newRowEditing) { + m_newRowEditing = false; + //remove current edited row (it is @ the end of list) + m_data->removeLast(); + //current item is now empty, last row + m_currentItem = m_insertItem; + //update visibility + if (m_verticalHeader) + m_verticalHeader->removeLabel(false); //-1 label +// updateContents(columnPos(0), rowPos(rows()), +// viewport()->width(), d->rowHeight*3 + (m_navPanel ? m_navPanel->height() : 0)*3 ); +// updateContents(); //js: above did not work well so we do that dirty + updateWidgetContents(); +//TODO: still doesn't repaint properly!! +// QSize s(tableSize()); +// resizeContents(s.width(), s.height()); + updateWidgetContentsSize(); +// m_verticalHeader->update(); + //--no cancel action is needed for datasource, + // because the row was not yet stored. + } + + m_data->clearRowEditBuffer(); + updateAfterCancelRowEdit(); + +//! \todo (js): cancel changes for this row! + kexidbg << "EDIT ROW CANCELLED." << endl; + + /*emit*/ rowEditTerminated(m_curRow); + return true; +} + +void KexiDataAwareObjectInterface::updateAfterCancelRowEdit() +{ + updateRow(m_curRow); + if (m_alsoUpdateNextRow) + updateRow(m_curRow+1); + m_alsoUpdateNextRow = false; +} + +void KexiDataAwareObjectInterface::updateAfterAcceptRowEdit() +{ + updateRow(m_curRow); +} + +void KexiDataAwareObjectInterface::removeEditor() +{ + if (!m_editor) + return; + m_editor->hideWidget(); + m_editor = 0; +} + +bool KexiDataAwareObjectInterface::cancelEditor() +{ + if (m_errorMessagePopup) { + m_errorMessagePopup->close(); + } + if (!m_editor) + return false; + removeEditor(); + return true; +} + +//! @internal +class KexiDataAwareObjectInterfaceToolTip : public QToolTip { + public: + KexiDataAwareObjectInterfaceToolTip( const QString & text, const QPoint & pos, QWidget * widget ) + : QToolTip(widget), m_text(text) + { + tip( QRect(pos, QSize(100, 100)), text ); + } + virtual void maybeTip(const QPoint & p) { + tip( QRect(p, QSize(100, 100)), m_text); + } + QString m_text; +}; + +bool KexiDataAwareObjectInterface::acceptEditor() +{ + if (!hasData()) + return true; + if (!m_editor || m_inside_acceptEditor) + return true; + + m_inside_acceptEditor = true;//avoid recursion + + QVariant newval; + Validator::Result res = Validator::Ok; + QString msg, desc; + bool setNull = false; +// bool allow = true; +// static const QString msg_NOT_NULL = i18n("\"%1\" column requires a value to be entered."); + + //autoincremented field can be omitted (left as null or empty) if we're inserting a new row + const bool autoIncColumnCanBeOmitted = m_newRowEditing && m_editor->field()->isAutoIncrement(); +// const bool autoIncColumnCanBeOmitted = m_newRowEditing && m_editor->columnInfo()->field->isAutoIncrement(); + + bool valueChanged = m_editor->valueChanged(); + bool editCurrentCellAgain = false; + + if (valueChanged) { + if (!m_editor->valueIsValid()) { + //used e.g. for date or time values - the value can be null but not necessary invalid + res = Validator::Error; + editCurrentCellAgain = true; + QWidget *par = dynamic_cast<QScrollView*>(this) ? dynamic_cast<QScrollView*>(this)->viewport() : + dynamic_cast<QWidget*>(this); + QWidget *edit = dynamic_cast<QWidget*>(m_editor); + if (par && edit) { +//! @todo allow displaying user-defined warning +//! @todo also use for other error messages + if (!m_errorMessagePopup) { +// m_errorMessagePopup->close(); + m_errorMessagePopup = new KexiArrowTip( + i18n("Error: %1").arg(m_editor->columnInfo()->field->typeName())+"?", + dynamic_cast<QWidget*>(this)); + m_errorMessagePopup->move( + par->mapToGlobal(edit->pos()) + QPoint(6, edit->height() + 0) ); + m_errorMessagePopup->show(); + } + m_editor->setFocus(); + } + } + else if (m_editor->valueIsNull()) {//null value entered +// if (m_editor->columnInfo()->field->isNotNull() && !autoIncColumnCanBeOmitted) { + if (m_editor->field()->isNotNull() && !autoIncColumnCanBeOmitted) { + kdDebug() << "KexiDataAwareObjectInterface::acceptEditor(): NULL NOT ALLOWED!" << endl; + res = Validator::Error; +// msg = Validator::msgColumnNotEmpty().arg(m_editor->columnInfo()->field->captionOrName()) + msg = Validator::msgColumnNotEmpty().arg(m_editor->field()->captionOrName()) + + "\n\n" + Kexi::msgYouCanImproveData(); + desc = i18n("The column's constraint is declared as NOT NULL."); + editCurrentCellAgain = true; + // allow = false; + // removeEditor(); + // return true; + } + else { + kdDebug() << "KexiDataAwareObjectInterface::acceptEditor(): NULL VALUE WILL BE SET" << endl; + //ok, just leave newval as NULL + setNull = true; + } + } + else if (m_editor->valueIsEmpty()) {//empty value entered +// if (m_editor->columnInfo()->field->hasEmptyProperty()) { + if (m_editor->field()->hasEmptyProperty()) { +// if (m_editor->columnInfo()->field->isNotEmpty() && !autoIncColumnCanBeOmitted) { + if (m_editor->field()->isNotEmpty() && !autoIncColumnCanBeOmitted) { + kdDebug() << "KexiDataAwareObjectInterface::acceptEditor(): EMPTY NOT ALLOWED!" << endl; + res = Validator::Error; +// msg = Validator::msgColumnNotEmpty().arg(m_editor->columnInfo()->field->captionOrName()) + msg = Validator::msgColumnNotEmpty().arg(m_editor->field()->captionOrName()) + + "\n\n" + Kexi::msgYouCanImproveData(); + desc = i18n("The column's constraint is declared as NOT EMPTY."); + editCurrentCellAgain = true; + // allow = false; + // removeEditor(); + // return true; + } + else { + kdDebug() << "KexiDataAwareObjectInterface::acceptEditor(): EMPTY VALUE WILL BE SET" << endl; + } + } + else { +// if (m_editor->columnInfo()->field->isNotNull() && !autoIncColumnCanBeOmitted) { + if (m_editor->field()->isNotNull() && !autoIncColumnCanBeOmitted) { + kdDebug() << "KexiDataAwareObjectInterface::acceptEditor(): NEITHER NULL NOR EMPTY VALUE CAN BE SET!" << endl; + res = Validator::Error; +// msg = Validator::msgColumnNotEmpty().arg(m_editor->columnInfo()->field->captionOrName()) + msg = Validator::msgColumnNotEmpty().arg(m_editor->field()->captionOrName()) + + "\n\n" + Kexi::msgYouCanImproveData(); + desc = i18n("The column's constraint is declared as NOT EMPTY and NOT NULL."); + editCurrentCellAgain = true; +// allow = false; + // removeEditor(); + // return true; + } + else { + kdDebug() << "KexiDataAwareObjectInterface::acceptEditor(): NULL VALUE WILL BE SET BECAUSE EMPTY IS NOT ALLOWED" << endl; + //ok, just leave newval as NULL + setNull = true; + } + } + } + }//changed + + const int realFieldNumber = fieldNumberForColumn(m_curCol); + if (realFieldNumber < 0) { + kdWarning() << "KexiDataAwareObjectInterface::acceptEditor(): fieldNumberForColumn(m_curCol) < 0" << endl; + m_inside_acceptEditor = false; + return false; + } + + KexiTableViewColumn *currentTVColumn = column(m_curCol); + + //try to get the value entered: + if (res == Validator::Ok) { + if ((!setNull && !valueChanged) + || (m_editor->field()->type()!=KexiDB::Field::Boolean && setNull && m_currentItem->at( realFieldNumber ).isNull())) { + kdDebug() << "KexiDataAwareObjectInterface::acceptEditor(): VALUE NOT CHANGED." << endl; + removeEditor(); + if (m_acceptsRowEditAfterCellAccepting || m_internal_acceptsRowEditAfterCellAccepting) + acceptRowEdit(); + m_inside_acceptEditor = false; + return true; + } + if (!setNull) {//get the new value +// bool ok; + newval = m_editor->value(); +//! @todo validation rules for this value? +/* + if (!ok) { + kdDebug() << "KexiDataAwareObjectInterface::acceptEditor(): INVALID VALUE - NOT CHANGED." << endl; + res = KexiValidator::Error; +//js: TODO get detailed info on why m_editor->value() failed + msg = i18n("Entered value is invalid.") + + "\n\n" + KexiValidator::msgYouCanImproveData(); + editCurrentCellAgain = true; +// removeEditor(); +// return true; + }*/ + } + + //Check other validation rules: + //1. check using validator +// KexiValidator *validator = m_data->column(m_curCol)->validator(); + Validator *validator = currentTVColumn->validator(); + if (validator) { +// res = validator->check(m_data->column(m_curCol)->field()->captionOrName(), + res = validator->check(currentTVColumn->field()->captionOrName(), + newval, msg, desc); + } + } + + //show the validation result if not OK: + if (res == Validator::Error) { + if (!msg.isEmpty()) { + if (desc.isEmpty()) + KMessageBox::sorry(dynamic_cast<QWidget*>(this), msg); + else + KMessageBox::detailedSorry(dynamic_cast<QWidget*>(this), msg, desc); + } + editCurrentCellAgain = true; +// allow = false; + } + else if (res == Validator::Warning) { + //js: todo: message!!! + KMessageBox::messageBox(dynamic_cast<QWidget*>(this), KMessageBox::Sorry, msg + "\n" + desc); + editCurrentCellAgain = true; + } + + if (res == Validator::Ok) { + //2. check using signal + //bool allow = true; +// emit aboutToChangeCell(d->pCurrentItem, newval, allow); +// if (allow) { + //send changes to the backend + QVariant visibleValue; + if (!newval.isNull()/* visible value should be null if value is null */ + && currentTVColumn->visibleLookupColumnInfo) + { + visibleValue = m_editor->visibleValue(); //visible value for lookup field + } + //should be also added to the buffer + if (m_data->updateRowEditBufferRef(m_currentItem, m_curCol, currentTVColumn, + newval, /*allowSignals*/true, currentTVColumn->visibleLookupColumnInfo ? &visibleValue : 0)) + { + kdDebug() << "KexiDataAwareObjectInterface::acceptEditor(): ------ EDIT BUFFER CHANGED TO:" << endl; + m_data->rowEditBuffer()->debug(); + } else { + kdDebug() << "KexiDataAwareObjectInterface::acceptEditor(): ------ CHANGE FAILED in KexiDataAwareObjectInterface::updateRowEditBuffer()" << endl; + res = Validator::Error; + + //now: there might be called cancelEditor() in updateRowEditBuffer() handler, + //if this is true, d->pEditor is NULL. + + if (m_editor && m_data->result()->column>=0 && m_data->result()->column<columns()) { + //move to faulty column (if m_editor is not cleared) + setCursorPosition(m_curRow, m_data->result()->column); + } + if (!m_data->result()->msg.isEmpty()) { + const int button = showErrorMessageForResult( m_data->result() ); + if (KMessageBox::No == button) { + //discard changes + cancelEditor(); + if (m_acceptsRowEditAfterCellAccepting) + cancelRowEdit(); + m_inside_acceptEditor = false; + return false; + } + } + } + } + + if (res == Validator::Ok) { + removeEditor(); + /*emit*/ itemChanged(m_currentItem, m_curRow, m_curCol, + m_currentItem->at( realFieldNumber )); + /*emit*/ itemChanged(m_currentItem, m_curRow, m_curCol); + } + m_inside_acceptEditor = false; + if (res == Validator::Ok) { + if (m_acceptsRowEditAfterCellAccepting || m_internal_acceptsRowEditAfterCellAccepting) + acceptRowEdit(); + return true; + } + if (m_editor) { + //allow to edit the cell again, (if m_pEditor is not cleared) + + if (m_editor->hasFocusableWidget()) { + m_editor->showWidget(); + m_editor->setFocus(); + } +// startEditCurrentCell(newval.type()==QVariant::String ? newval.toString() : QString::null); +// m_editor->setFocus(); + } + return false; +} + +void KexiDataAwareObjectInterface::startEditCurrentCell(const QString &setText) +{ + kdDebug() << "** KexiDataAwareObjectInterface::startEditCurrentCell("<<setText<<")"<<endl; +// if (columnType(d->curCol) == KexiDB::Field::Boolean) +// return; + if (isReadOnly() || !columnEditable(m_curCol)) + return; + if (m_editor) { + if (m_editor->hasFocusableWidget()) { + m_editor->showWidget(); + m_editor->setFocus(); + } + } +// ensureVisible(columnPos(m_curCol), rowPos(m_curRow)+rowHeight(), +// columnWidth(m_curCol), rowHeight()); +//OK? + //ensureCellVisible(m_curRow+1, m_curCol); + if (!m_editor) + createEditor(m_curRow, m_curCol, setText, !setText.isEmpty()); +} + +void KexiDataAwareObjectInterface::deleteAndStartEditCurrentCell() +{ + if (isReadOnly() || !columnEditable(m_curCol)) + return; + if (m_editor) {//if we've editor - just clear it + m_editor->clear(); + return; + } +//js if (columnType(m_curCol) == KexiDB::Field::Boolean) +//js return; +// ensureVisible(columnPos(m_curCol), rowPos(m_curRow) + rowHeight(), +// columnWidth(m_curCol), rowHeight()); +//OK? + ensureCellVisible(m_curRow+1, m_curCol); + createEditor(m_curRow, m_curCol, QString::null, false/*removeOld*/); + if (!m_editor) + return; + m_editor->clear(); + if (m_editor->acceptEditorAfterDeleteContents()) + acceptEditor(); + if (!m_editor || !m_editor->hasFocusableWidget()) + updateCell(m_curRow, m_curCol); +} + +void KexiDataAwareObjectInterface::deleteCurrentRow() +{ + if (m_newRowEditing) {//we're editing fresh new row: just cancel this! + cancelRowEdit(); + return; + } + + if (!acceptRowEdit()) + return; + + if (!isDeleteEnabled() || !m_currentItem || m_currentItem == m_insertItem) + return; + switch (m_deletionPolicy) { + case NoDelete: + return; + case ImmediateDelete: + break; + case AskDelete: + if (KMessageBox::Cancel == KMessageBox::warningContinueCancel(dynamic_cast<QWidget*>(this), + i18n("Do you want to delete selected row?"), 0, + KGuiItem(i18n("&Delete Row"),"editdelete"), + "dontAskBeforeDeleteRow"/*config entry*/, + KMessageBox::Notify|KMessageBox::Dangerous)) + return; + break; + case SignalDelete: + /*emit*/ itemDeleteRequest(m_currentItem, m_curRow, m_curCol); + /*emit*/ currentItemDeleteRequest(); + return; + default: + return; + } + + if (!deleteItem(m_currentItem)) {//nothing + } +} + +KexiTableItem *KexiDataAwareObjectInterface::insertEmptyRow(int row) +{ + if ( !acceptRowEdit() || !isEmptyRowInsertingEnabled() + || (row!=-1 && row >= ((int)rows()+(isInsertingEnabled()?1:0) ) ) ) + return 0; + + KexiTableItem *newItem = m_data->createItem(); //new KexiTableItem(m_data->columns.count()); + insertItem(newItem, row); + return newItem; +} + +void KexiDataAwareObjectInterface::insertItem(KexiTableItem *newItem, int row) +{ + const bool changeCurrentRow = row==-1 || row==m_curRow; + if (changeCurrentRow) { + //change current row + row = (m_curRow >= 0 ? m_curRow : 0); + m_currentItem = newItem; + m_curRow = row; + } + else if (m_curRow >= row) { + m_curRow++; + } + + m_data->insertRow(*newItem, row, true /*repaint*/); + + if (changeCurrentRow) { + //update iter... + m_itemIterator->toFirst(); + (*m_itemIterator)+=m_curRow; + } +/* + QSize s(tableSize()); + resizeContents(s.width(),s.height()); + + //redraw only this row and below: + int leftcol = d->pTopHeader->sectionAt( d->pTopHeader->offset() ); +// updateContents( columnPos( leftcol ), rowPos(d->curRow), +// clipper()->width(), clipper()->height() - (rowPos(d->curRow) - contentsY()) ); + updateContents( columnPos( leftcol ), rowPos(row), + clipper()->width(), clipper()->height() - (rowPos(row) - contentsY()) ); + + m_verticalHeader->addLabel(); + + //update navigator's data + setNavRowCount(rows()); + + if (d->curRow >= row) { + //update + editorShowFocus( d->curRow, d->curCol ); + } + */ +} + +void KexiDataAwareObjectInterface::slotRowInserted(KexiTableItem *item, bool repaint) +{ + int row = m_data->findRef(item); + slotRowInserted( item, row, repaint ); +} + +void KexiDataAwareObjectInterface::slotRowInserted(KexiTableItem * /*item*/, uint row, bool repaint) +{ + if (repaint && (int)row<rows()) { + updateWidgetContentsSize(); + +/* updateAllVisibleRowsBelow() used instead + //redraw only this row and below: + int leftcol = d->pTopHeader->sectionAt( d->pTopHeader->offset() ); + updateContents( columnPos( leftcol ), rowPos(row), + clipper()->width(), clipper()->height() - (rowPos(row) - contentsY()) ); +*/ + updateAllVisibleRowsBelow(row); + + if (!m_verticalHeaderAlreadyAdded) { + if (m_verticalHeader) + m_verticalHeader->addLabel(); + } + else //it was added because this inserting was interactive + m_verticalHeaderAlreadyAdded = false; + + //update navigator's data + m_navPanel->setRecordCount(rows()); + + if (m_curRow >= (int)row) { + //update + editorShowFocus( m_curRow, m_curCol ); + } + } +} + +tristate KexiDataAwareObjectInterface::deleteAllRows(bool ask, bool repaint) +{ + if (!hasData()) + return true; + if (m_data->count()<1) + return true; + + if (ask) { + QString tableName = m_data->dbTableName(); + if (!tableName.isEmpty()) { + tableName.prepend(" \""); + tableName.append("\""); + } + if (KMessageBox::Cancel == KMessageBox::warningContinueCancel(dynamic_cast<QWidget*>(this), + i18n("Do you want to clear the contents of table %1?").arg(tableName), + 0, KGuiItem(i18n("&Clear Contents")) )) + return cancelled; + } + + cancelRowEdit(); +// acceptRowEdit(); +// m_verticalHeader->clear(); + const bool repaintLater = repaint && m_spreadSheetMode; + const int oldRows = rows(); + + bool res = m_data->deleteAllRows(repaint && !repaintLater); + + if (res) { + if (m_spreadSheetMode) { +// const uint columns = m_data->columns.count(); + for (int i=0; i<oldRows; i++) { + m_data->append(m_data->createItem());//new KexiTableItem(columns)); + } + } + } + if (repaintLater) + m_data->reload(); + +// d->clearVariables(); +// m_verticalHeader->setCurrentRow(-1); + +// d->pUpdateTimer->start(1,true); +// if (repaint) +// viewport()->repaint(); + return res; +} + +void KexiDataAwareObjectInterface::clearColumns(bool repaint) +{ + cancelRowEdit(); + m_data->clearInternal(); + + clearColumnsInternal(repaint); + updateIndicesForVisibleValues(); + + if (repaint) +// viewport()->repaint(); +//OK? + updateWidgetContents(); + +/* for(int i=0; i < rows(); i++) + { + m_verticalHeader->removeLabel(); + } + + editorCancel(); + m_contents->clear(); + + d->clearVariables(); + d->numCols = 0; + + while(d->pTopHeader->count()>0) + d->pTopHeader->removeLabel(0); + + m_verticalHeader->setCurrentRow(-1); + + viewport()->repaint(); + +// d->pColumnTypes.resize(0); +// d->pColumnModes.resize(0); +// d->pColumnDefaults.clear();*/ +} + +void KexiDataAwareObjectInterface::reloadData() +{ +// cancelRowEdit(); + acceptRowEdit(); + if (m_verticalHeader) + m_verticalHeader->clear(); + + if (m_curCol>=0 && m_curCol<columns()) { + //find the editor for this column + KexiDataItemInterface *edit = editor( m_curCol ); + if (edit) { + edit->hideFocus(); + } + } +// setCursorPosition(-1, -1, true); + clearVariables(); + if (m_verticalHeader) + m_verticalHeader->setCurrentRow(-1); + + if (dynamic_cast<QWidget*>(this) && dynamic_cast<QWidget*>(this)->isVisible()) + initDataContents(); + else + m_initDataContentsOnShow = true; + + if (m_verticalHeader) + m_verticalHeader->addLabels(m_data->count()); + + updateWidgetScrollBars(); +} + +int KexiDataAwareObjectInterface::columnType(int col) +{ + KexiTableViewColumn* c = m_data ? column(col) : 0; + return c ? c->field()->type() : KexiDB::Field::InvalidType; +} + +bool KexiDataAwareObjectInterface::columnEditable(int col) +{ + KexiTableViewColumn* c = m_data ? column(col) : 0; + return c ? (! c->isReadOnly()) : false; +} + +int KexiDataAwareObjectInterface::rows() const +{ + if (!hasData()) + return 0; + return m_data->count(); +} + +int KexiDataAwareObjectInterface::dataColumns() const +{ + if (!hasData()) + return 0; + return m_data->columns.count(); +} + +QVariant KexiDataAwareObjectInterface::columnDefaultValue(int /*col*/) const +{ + return QVariant(0); +//TODO(js) +// return m_data->columns[col].defaultValue; +} + +void KexiDataAwareObjectInterface::setAcceptsRowEditAfterCellAccepting(bool set) +{ + m_acceptsRowEditAfterCellAccepting = set; +} + +void KexiDataAwareObjectInterface::setDropsAtRowEnabled(bool set) +{ +// const bool old = d->dropsAtRowEnabled; + if (!set) + m_dragIndicatorLine = -1; + if (m_dropsAtRowEnabled && !set) { + m_dropsAtRowEnabled = false; +// update(); + updateWidgetContents(); + } + else { + m_dropsAtRowEnabled = set; + } +} + +void KexiDataAwareObjectInterface::setEmptyRowInsertingEnabled(bool set) +{ + m_emptyRowInsertingEnabled = set; + /*emit*/ reloadActions(); +} + +void KexiDataAwareObjectInterface::slotAboutToDeleteRow(KexiTableItem& item, + KexiDB::ResultInfo* /*result*/, bool repaint) +{ + if (repaint) { + m_rowWillBeDeleted = m_data->findRef(&item); + } +} + +void KexiDataAwareObjectInterface::slotRowDeleted() +{ + if (m_rowWillBeDeleted >= 0) { + if (m_rowWillBeDeleted > 0 && m_rowWillBeDeleted >= (rows()-1) && !m_spreadSheetMode) + m_rowWillBeDeleted = rows()-1; //move up if it's the last row + updateWidgetContentsSize(); + + if (! (m_spreadSheetMode && m_rowWillBeDeleted>=(rows()-1))) + setCursorPosition(m_rowWillBeDeleted, m_curCol, true/*forceSet*/); + if (m_verticalHeader) + m_verticalHeader->removeLabel(); + + updateAllVisibleRowsBelow(m_curRow); //needed for KexiTableView + + //update navigator's data + m_navPanel->setRecordCount(rows()); + + m_rowWillBeDeleted = -1; + } +} + +bool KexiDataAwareObjectInterface::beforeDeleteItem(KexiTableItem *) +{ + //always return + return true; +} + +bool KexiDataAwareObjectInterface::deleteItem(KexiTableItem *item)/*, bool moveCursor)*/ +{ + if (!item || !beforeDeleteItem(item)) + return false; + + QString msg, desc; +// bool current = (item == d->pCurrentItem); + const bool lastRowDeleted = m_spreadSheetMode && m_data->last() == item; //we need to know this so we + //can return to the last row + //after reinserting it + if (!m_data->deleteRow(*item, true /*repaint*/)) { + /*const int button =*/ + showErrorMessageForResult( m_data->result() ); +// if (KMessageBox::No == button) { + //discard changes + // } + return false; + } + else { +//setCursorPosition() wil lset this! if (current) + //d->pCurrentItem = m_data->current(); + } + +// repaintAfterDelete(); + if (m_spreadSheetMode) { //append empty row for spreadsheet mode + m_data->append(m_data->createItem());//new KexiTableItem(m_data->columns.count())); + if (m_verticalHeader) + m_verticalHeader->addLabels(1); + if (lastRowDeleted) //back to the last row + setCursorPosition(rows()-1, m_curCol, true/*forceSet*/); + /*emit*/ newItemAppendedForAfterDeletingInSpreadSheetMode(); + } + return true; +} + +KexiTableViewColumn* KexiDataAwareObjectInterface::column(int col) +{ + return m_data->column(col); +} + +bool KexiDataAwareObjectInterface::hasDefaultValueAt(const KexiTableViewColumn& tvcol) +{ + if (m_rowEditing && m_data->rowEditBuffer() && m_data->rowEditBuffer()->isDBAware()) { + return m_data->rowEditBuffer()->hasDefaultValueAt( *tvcol.columnInfo ); + } + return false; +} + +const QVariant* KexiDataAwareObjectInterface::bufferedValueAt(int col, bool useDefaultValueIfPossible) +{ + if (m_rowEditing && m_data->rowEditBuffer()) + { + KexiTableViewColumn* tvcol = column(col); + if (tvcol->isDBAware) { + //get the stored value + const int realFieldNumber = fieldNumberForColumn(col); + if (realFieldNumber < 0) { + kdWarning() << "KexiDataAwareObjectInterface::bufferedValueAt(): " + "fieldNumberForColumn(m_curCol) < 0" << endl; + return 0; + } + QVariant *storedValue = &m_currentItem->at( realFieldNumber ); + + //db-aware data: now, try to find a buffered value (or default one) + const QVariant *cv = m_data->rowEditBuffer()->at( *tvcol->columnInfo, + storedValue->isNull() && useDefaultValueIfPossible); + if (cv) + return cv; + return storedValue; + } + //not db-aware data: + const QVariant *cv = m_data->rowEditBuffer()->at( tvcol->field()->name() ); + if (cv) + return cv; + } + //not db-aware data: + const int realFieldNumber = fieldNumberForColumn(col); + if (realFieldNumber < 0) { + kdWarning() << "KexiDataAwareObjectInterface::bufferedValueAt(): " + "fieldNumberForColumn(m_curCol) < 0" << endl; + return 0; + } + return &m_currentItem->at( realFieldNumber ); +} + +void KexiDataAwareObjectInterface::startEditOrToggleValue() +{ + if ( !isReadOnly() && columnEditable(m_curCol) ) { + if (columnType(m_curCol) == KexiDB::Field::Boolean) { + boolToggled(); + } + else { + startEditCurrentCell(); + return; + } + } +} + +void KexiDataAwareObjectInterface::boolToggled() +{ + startEditCurrentCell(); + if (m_editor) { + m_editor->clickedOnContents(); + } + acceptEditor(); + updateCell(m_curRow, m_curCol); + +/* int s = m_currentItem->at(m_curCol).toInt(); + QVariant oldValue=m_currentItem->at(m_curCol); + (*m_currentItem)[m_curCol] = QVariant(s ? 0 : 1); + updateCell(m_curRow, m_curCol); +// emit itemChanged(m_currentItem, m_curRow, m_curCol, oldValue); +// emit itemChanged(m_currentItem, m_curRow, m_curCol);*/ +} + +void KexiDataAwareObjectInterface::slotDataDestroying() +{ + m_data = 0; + m_itemIterator = 0; +} + +void KexiDataAwareObjectInterface::addNewRecordRequested() +{ + if (!isInsertingEnabled()) + return; + if (m_rowEditing) { + if (!acceptRowEdit()) + return; + } +// setFocus(); + selectRow(rows()); + startEditCurrentCell(); + if (m_editor) + m_editor->setFocus(); +} + +bool KexiDataAwareObjectInterface::handleKeyPress(QKeyEvent *e, int &curRow, int &curCol, + bool fullRowSelection, bool *moveToFirstField, bool *moveToLastField) +{ + if (moveToFirstField) + *moveToFirstField = false; + if (moveToLastField) + *moveToLastField = false; + + const bool nobtn = e->state()==Qt::NoButton; + const int k = e->key(); + //kdDebug() << "-----------" << e->state() << " " << k << endl; + + if ((k == Qt::Key_Up && nobtn) || (k == Qt::Key_PageUp && e->state()==Qt::ControlButton)) { + selectPrevRow(); + e->accept(); + } + else if ((k == Qt::Key_Down && nobtn) || (k == Qt::Key_PageDown && e->state()==Qt::ControlButton)) { + selectNextRow(); + e->accept(); + } + else if (k == Qt::Key_PageUp && nobtn) { + selectPrevPage(); + e->accept(); + } + else if (k == Qt::Key_PageDown && nobtn) { + selectNextPage(); + e->accept(); + } + else if (k == Qt::Key_Home) { + if (fullRowSelection) { + //we're in row-selection mode: home key always moves to 1st row + curRow = 0;//to 1st row + } + else {//cell selection mode: different actions depending on ctrl and shift keys state + if (nobtn) { + curCol = 0;//to 1st col + } + else if (e->state()==Qt::ControlButton) { + curRow = 0;//to 1st row and col + curCol = 0; + } + else + return false; + } + if (moveToFirstField) + *moveToFirstField = true; + //do not accept yet + e->ignore(); + } + else if (k == Qt::Key_End) { + if (fullRowSelection) { + //we're in row-selection mode: home key always moves to last row + curRow = m_data->count()-1+(isInsertingEnabled()?1:0);//to last row + } + else {//cell selection mode: different actions depending on ctrl and shift keys state + if (nobtn) { + curCol = columns()-1;//to last col + } + else if (e->state()==Qt::ControlButton) { + curRow = m_data->count()-1 /*+(isInsertingEnabled()?1:0)*/; //to last row and col + curCol = columns()-1;//to last col + } + else + return false; + } + if (moveToLastField) + *moveToLastField = true; + //do not accept yet + e->ignore(); + } + else if (isInsertingEnabled() && (e->state()==Qt::ControlButton && k == Qt::Key_Equal + || e->state()==(Qt::ControlButton|Qt::ShiftButton) && k == Qt::Key_Equal)) { + curRow = m_data->count(); //to the new row + curCol = 0;//to first col + if (moveToFirstField) + *moveToFirstField = true; + //do not accept yet + e->ignore(); + } + else + return false; + + return true; +} + +void KexiDataAwareObjectInterface::vScrollBarValueChanged(int v) +{ + Q_UNUSED(v); + if (!m_vScrollBarValueChanged_enabled) + return; + + if (m_scrollbarToolTipsEnabled) { + const QRect r( verticalScrollBar()->sliderRect() ); + const int row = lastVisibleRow()+1; + if (row<=0) { + m_scrollBarTipTimer.stop(); + m_scrollBarTip->hide(); + return; + } + m_scrollBarTip->setText( i18n("Row: ") + QString::number(row) ); + m_scrollBarTip->adjustSize(); + QWidget* thisWidget = dynamic_cast<QWidget*>(this); + m_scrollBarTip->move( + thisWidget->mapToGlobal( r.topLeft() + verticalScrollBar()->pos() ) + + QPoint( - m_scrollBarTip->width()-5, + r.height()/2 - m_scrollBarTip->height()/2) ); + if (verticalScrollBar()->draggingSlider()) { + kdDebug(44021) << " draggingSlider() " << endl; + m_scrollBarTipTimer.stop(); + m_scrollBarTip->show(); + m_scrollBarTip->raise(); + } + else { + m_scrollBarTipTimerCnt++; + if (m_scrollBarTipTimerCnt>4) { + m_scrollBarTipTimerCnt=0; + m_scrollBarTip->show(); + m_scrollBarTip->raise(); + m_scrollBarTipTimer.start(500, true); + } + } + } + //update bottom view region +/* if (m_navPanel && (contentsHeight() - contentsY() - clipper()->height()) <= QMAX(d->rowHeight,m_navPanel->height())) { + slotUpdate(); + triggerUpdate(); + }*/ +} + +bool KexiDataAwareObjectInterface::scrollbarToolTipsEnabled() const +{ + return m_scrollbarToolTipsEnabled; +} + +void KexiDataAwareObjectInterface::setScrollbarToolTipsEnabled(bool set) +{ + m_scrollbarToolTipsEnabled = set; +} + +void KexiDataAwareObjectInterface::vScrollBarSliderReleased() +{ + kdDebug(44021) << "vScrollBarSliderReleased()" << endl; + m_scrollBarTip->hide(); +} + +void KexiDataAwareObjectInterface::scrollBarTipTimeout() +{ + if (m_scrollBarTip->isVisible()) { +// kdDebug(44021) << "TIMEOUT! - hide" << endl; + if (m_scrollBarTipTimerCnt>0) { + m_scrollBarTipTimerCnt=0; + m_scrollBarTipTimer.start(500, true); + return; + } + m_scrollBarTip->hide(); + } + m_scrollBarTipTimerCnt=0; +} + +void KexiDataAwareObjectInterface::focusOutEvent(QFocusEvent* e) +{ + Q_UNUSED(e); + m_scrollBarTipTimer.stop(); + m_scrollBarTip->hide(); + + updateCell(m_curRow, m_curCol); +} + +int KexiDataAwareObjectInterface::showErrorMessageForResult(KexiDB::ResultInfo* resultInfo) +{ + QWidget *thisWidget = dynamic_cast<QWidget*>(this); + if (resultInfo->allowToDiscardChanges) { + return KMessageBox::questionYesNo(thisWidget, resultInfo->msg + + (resultInfo->desc.isEmpty() ? QString::null : ("\n"+resultInfo->desc)), + QString::null, + KGuiItem(i18n("Correct Changes", "Correct"), QString::null, i18n("Correct changes")), + KGuiItem(i18n("Discard Changes")) ); + } + + if (resultInfo->desc.isEmpty()) + KMessageBox::sorry(thisWidget, resultInfo->msg); + else + KMessageBox::detailedSorry(thisWidget, resultInfo->msg, resultInfo->desc); + + return KMessageBox::Ok; +} + +void KexiDataAwareObjectInterface::updateIndicesForVisibleValues() +{ + m_indicesForVisibleValues.resize( m_data ? m_data->columnsCount() : 0 ); + if (!m_data) + return; + for (uint i=0; i < m_data->columnsCount(); i++) { + KexiTableViewColumn* tvCol = m_data->column(i); + if (tvCol->columnInfo && tvCol->columnInfo->indexForVisibleLookupValue()!=-1) + // retrieve visible value from lookup field + m_indicesForVisibleValues[ i ] = tvCol->columnInfo->indexForVisibleLookupValue(); + else + m_indicesForVisibleValues[ i ] = i; + } +} + +/*! Performs searching \a stringValue in \a where string. + \a matchAnyPartOfField, \a matchWholeField, \a wholeWordsOnly options are used to control how to search. + + If \a matchWholeField is true, \a wholeWordsOnly is not checked. + \a firstCharacter is in/out parameter. If \a matchAnyPartOfField is true and \a matchWholeField is false, + \a firstCharacter >= 0, the search will be performed after skipping first \a firstCharacter characters. + + If \a forward is false, we are searching backwart from \a firstCharacter position. \a firstCharacter == -1 + means then the last character. \a firstCharacter == INT_MAX means "before first" place, so searching fails + immediately. + On success, true is returned and \a firstCharacter is set to position of the matched string. */ +static inline bool findInString(const QString& stringValue, int stringLength, const QString& where, + int& firstCharacter, bool matchAnyPartOfField, bool matchWholeField, + bool caseSensitive, bool wholeWordsOnly, bool forward) +{ + if (where.isEmpty()) { + firstCharacter = -1; + return false; + } + + if (matchAnyPartOfField) { + if (forward) { + int pos = firstCharacter == -1 ? 0 : firstCharacter; + if (wholeWordsOnly) { + const int whereLength = where.length(); + while (true) { + pos = where.find( stringValue, pos, caseSensitive ); + if (pos == -1) + break; + if ((pos > 0 && where.at(pos-1).isLetterOrNumber()) + ||((pos+stringLength-1) < (whereLength-1) && where.at(pos+stringLength-1+1).isLetterOrNumber())) + { + pos++; // invalid match because before or after the string there is non-white space + } + else + break; + }//while + firstCharacter = pos; + } + else {// !wholeWordsOnly + firstCharacter = where.find( stringValue, pos, caseSensitive ); + } + return firstCharacter != -1; + } + else { // !matchAnyPartOfField + if (firstCharacter == INT_MAX) { + firstCharacter = -1; //next time we'll be looking at different cell + return false; + } + int pos = firstCharacter; + if (wholeWordsOnly) { + const int whereLength = where.length(); + while (true) { + pos = where.findRev( stringValue, pos, caseSensitive ); + if (pos == -1) + break; + if ((pos > 0 && where.at(pos-1).isLetterOrNumber()) + ||((pos+stringLength-1) < (whereLength-1) && where.at(pos+stringLength-1+1).isLetterOrNumber())) + { + // invalid match because before or after the string there is non-white space + pos--; + if (pos < 0) // it can make pos < 0 + break; + } + else + break; + }//while + firstCharacter = pos; + } + else {// !wholeWordsOnly + firstCharacter = where.findRev( stringValue, pos, caseSensitive ); + } + return firstCharacter != -1; + } + } + else if (matchWholeField) { + if (firstCharacter != -1 && firstCharacter != 0) { //we're not at 0-th char + firstCharacter = -1; + } + else if ( (caseSensitive ? where : where.lower()) == stringValue) { + firstCharacter = 0; + return true; + } + } + else {// matchStartOfField + if (firstCharacter != -1 && firstCharacter != 0) { //we're not at 0-th char + firstCharacter = -1; + } + else if (where.startsWith(stringValue, caseSensitive)) { + if (wholeWordsOnly) { + // If where.length() < stringValue.length(), true will be returned too - fine. + return !where.at( stringValue.length() ).isLetterOrNumber(); + } + firstCharacter = 0; + return true; + } + } + return false; +} + +tristate KexiDataAwareObjectInterface::find(const QVariant& valueToFind, + const KexiSearchAndReplaceViewInterface::Options& options, bool next) +{ + if (!hasData()) + return cancelled; + const QVariant prevSearchedValue( m_recentlySearchedValue ); + m_recentlySearchedValue = valueToFind; + const KexiSearchAndReplaceViewInterface::Options::SearchDirection prevSearchDirection = m_recentSearchDirection; + m_recentSearchDirection = options.searchDirection; + if (valueToFind.isNull() || valueToFind.toString().isEmpty()) + return cancelled; + + const bool forward = (options.searchDirection == KexiSearchAndReplaceViewInterface::Options::SearchUp) + ? !next : next; //direction can be reversed + + if ((!prevSearchedValue.isNull() && prevSearchedValue!=valueToFind) + || (prevSearchDirection!=options.searchDirection && options.searchDirection==KexiSearchAndReplaceViewInterface::Options::SearchAllRows)) + { + // restart searching when value has been changed or new direction is SearchAllRows + m_positionOfRecentlyFoundValue.exists = false; + } + + const bool startFrom1stRowAndCol = !m_positionOfRecentlyFoundValue.exists && next + && options.searchDirection == KexiSearchAndReplaceViewInterface::Options::SearchAllRows; + const bool startFromLastRowAndCol = + (!m_positionOfRecentlyFoundValue.exists && !next && options.searchDirection == KexiSearchAndReplaceViewInterface::Options::SearchAllRows) + ||(m_curRow >= rows() && !forward); //we're at "insert" row, and searching backwards: move to the last cell + + if (!startFrom1stRowAndCol && !startFromLastRowAndCol && m_curRow >= rows()) { + //we're at "insert" row, and searching forward: no chances to find something + return false; + } + KexiTableViewData::Iterator it( (startFrom1stRowAndCol || startFromLastRowAndCol) + ? m_data->iterator() : *m_itemIterator /*start from the current cell*/ ); + if (startFromLastRowAndCol) + it.toLast(); + int firstCharacter; + if (m_positionOfRecentlyFoundValue.exists) {// start after the next/prev char position + if (forward) + firstCharacter = m_positionOfRecentlyFoundValue.lastCharacter + 1; + else { + firstCharacter = (m_positionOfRecentlyFoundValue.firstCharacter > 0) ? + (m_positionOfRecentlyFoundValue.firstCharacter - 1) : INT_MAX /* this means 'before first'*/; + } + } + else { + firstCharacter = -1; //forward ? -1 : INT_MAX; + } + + const int columnsCount = m_data->columnsCount(); + int row, col; + if (startFrom1stRowAndCol) { + row = 0; + col = 0; + } + else if (startFromLastRowAndCol) { + row = rows()-1; + col = columnsCount-1; + } + else { + row = m_curRow; + col = m_curCol; + } + + //sache some flags for efficiency + const bool matchAnyPartOfField + = options.textMatching == KexiSearchAndReplaceViewInterface::Options::MatchAnyPartOfField; + const bool matchWholeField + = options.textMatching == KexiSearchAndReplaceViewInterface::Options::MatchWholeField; + const bool caseSensitive = options.caseSensitive; + const bool wholeWordsOnly = options.wholeWordsOnly; +//unused const bool promptOnReplace = options.promptOnReplace; + int columnNumber = (options.columnNumber == KexiSearchAndReplaceViewInterface::Options::CurrentColumn) + ? m_curCol : options.columnNumber; + if (columnNumber>=0) + col = columnNumber; + const bool lookInAllColumns = columnNumber == KexiSearchAndReplaceViewInterface::Options::AllColumns; + int firstColumn; // real number of the first column, can be smaller than lastColumn if forward==true + int lastColumn; // real number of the last column + if (lookInAllColumns) { + firstColumn = forward ? 0 : columnsCount-1; + lastColumn = forward ? columnsCount-1 : 0; + } + else { + firstColumn = columnNumber; + lastColumn = columnNumber; + } + const QString stringValue( caseSensitive ? valueToFind.toString() : valueToFind.toString().lower() ); + const int stringLength = stringValue.length(); + + // search + const int prevRow = m_curRow; + KexiTableItem *item; + while ( (item = it.current()) ) { + for (; forward ? col <= lastColumn : col >= lastColumn; + col = forward ? (col+1) : (col-1)) + { + const QVariant cell( item->at( m_indicesForVisibleValues[ col ] ) ); + if (findInString(stringValue, stringLength, cell.toString(), firstCharacter, + matchAnyPartOfField, matchWholeField, caseSensitive, wholeWordsOnly, forward)) + { + //*m_itemIterator = it; + //m_currentItem = *it; + //m_curRow = row; + //m_curCol = col; + setCursorPosition(row, col, true/*forceSet*/); + if (prevRow != m_curRow) + updateRow(prevRow); + // remember the exact position for the found value + m_positionOfRecentlyFoundValue.exists = true; + m_positionOfRecentlyFoundValue.firstCharacter = firstCharacter; +//! @todo for regexp lastCharacter should be computed + m_positionOfRecentlyFoundValue.lastCharacter = firstCharacter + stringLength - 1; + return true; + } + }//for + if (forward) { + ++it; + ++row; + } + else { + --it; + --row; + } + col = firstColumn; + }//while + return false; +} + +tristate KexiDataAwareObjectInterface::findNextAndReplace( + const QVariant& valueToFind, const QVariant& replacement, + const KexiSearchAndReplaceViewInterface::Options& options, bool replaceAll) +{ + Q_UNUSED(replacement); + Q_UNUSED(options); + Q_UNUSED(replaceAll); + + if (isReadOnly()) + return cancelled; + if (valueToFind.isNull() || valueToFind.toString().isEmpty()) + return cancelled; + //! @todo implement KexiDataAwareObjectInterface::findAndReplace() + return false; +} diff --git a/kexi/widget/tableview/kexidataawareobjectiface.h b/kexi/widget/tableview/kexidataawareobjectiface.h new file mode 100644 index 00000000..4cf2aa6a --- /dev/null +++ b/kexi/widget/tableview/kexidataawareobjectiface.h @@ -0,0 +1,918 @@ +/* This file is part of the KDE project + Copyright (C) 2005-2007 Jaroslaw Staniek <js@iidea.pl> + + Based on KexiTableView code. + Copyright (C) 2002 Till Busch <till@bux.at> + Copyright (C) 2003 Lucijan Busch <lucijan@gmx.at> + Copyright (C) 2003 Daniel Molkentin <molkentin@kde.org> + Copyright (C) 2003 Joseph Wenninger <jowenn@kde.org> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIDATAAWAREOBJECTINTERFACE_H +#define KEXIDATAAWAREOBJECTINTERFACE_H + +#include <qguardedptr.h> + +#include <qtimer.h> +#include <kdebug.h> +#include <widget/utils/kexiarrowtip.h> +#include <kexisearchandreplaceiface.h> +#include "kexitableviewdata.h" + +class QObject; +class QScrollBar; +class KPopupMenu; +class KexiTableItem; +class KexiTableViewData; +class KexiRecordMarker; +class KexiTableViewHeader; +class KexiRecordNavigator; +#include <kexidataiteminterface.h> + +namespace KexiDB { + class RowEditBuffer; +} + +//! default column width in pixels +#define KEXI_DEFAULT_DATA_COLUMN_WIDTH 120 + +//! \brief The KexiDataAwareObjectInterface is an interface for record-based data object. +/** This interface is implemented by KexiTableView and KexiFormView + and used by KexiDataAwareView. If yu're implementing this interface, + add KEXI_DATAAWAREOBJECTINTERFACE convenience macro just after Q_OBJECT. + + You should add following code to your destructor so data is deleted: + \code + if (m_owner) + delete m_data; + m_data = 0; + \endcode + This is not performed in KexiDataAwareObjectInterface because you may need + to access m_data in your desctructor. +*/ +class KEXIDATATABLE_EXPORT KexiDataAwareObjectInterface +{ + public: + KexiDataAwareObjectInterface(); + virtual ~KexiDataAwareObjectInterface(); + + /*! Sets data for this object. if \a owner is true, the object will own + \a data and therefore will be destroyed when needed, else: \a data is (possibly) shared and + not owned by the widget. + If widget already has _different_ data object assigned (and owns this data), + old data is destroyed before new assignment. + */ + void setData( KexiTableViewData *data, bool owner = true ); + + /*! \return data structure displayed for this object */ + inline KexiTableViewData *data() const { return m_data; } + + /*! \return currently selected column number or -1. */ + inline int currentColumn() const { return m_curCol; } + + /*! \return currently selected row number or -1. */ + inline int currentRow() const { return m_curRow; } + + /*! \return last row visible on the screen (counting from 0). + The returned value is guaranteed to be smaller or equal to currentRow() or -1 + if there are no rows. */ + virtual int lastVisibleRow() const = 0; + + /*! \return currently selected item (row data) or null. */ + KexiTableItem *selectedItem() const { return m_currentItem; } + + /*! \return number of rows in this view. */ + int rows() const; + + /*! \return number of visible columns in this view. + By default returns dataColumns(), what is proper table view. + In case of form view, there can be a number of duplicated columns defined + (data-aware widgets, see KexiFormScrollView::columns()), + so columns() can return greater number than dataColumns(). */ + virtual int columns() const { return dataColumns(); } + + /*! Helper function. + \return number of columns of data. */ + int dataColumns() const; + + /*! \return true if data represented by this object + is not editable (it can be editable with other ways although, + outside of this object). */ + virtual bool isReadOnly() const; + + /*! Sets readOnly flag for this object. + Unless the flag is set, the widget inherits readOnly flag from it's data + structure assigned with setData(). The default value if false. + + This method is useful when you need to switch on the flag indepentently + from the data structure. + Note: it is not allowed to force readOnly off + when internal data is readOnly - in that case the method does nothing. + You can check internal data flag calling data()->isReadOnly(). + + If \a set is true, insertingEnabled flag will be cleared automatically. + \sa isInsertingEnabled() + */ + void setReadOnly(bool set); + + /*! \return true if sorting is enabled. */ + inline bool isSortingEnabled() const { return m_isSortingEnabled; } + + /*! Sets sorting on column \a col, or (when \a col == -1) sets rows unsorted + this will do not work if sorting is disabled with setSortingEnabled() */ + virtual void setSorting(int col, bool ascending=true); + + /*! Enables or disables sorting for this object + This method is different that setSorting() because it prevents both user + and programmer from sorting by clicking a column's header or calling setSorting(). + By default sorting is enabled. + */ + virtual void setSortingEnabled(bool set); + + /*! \return sorted column number or -1 if no column is sorted within data. + This does not mean that any sorting has been performed within GUI of this object, + because the data could be changed in the meantime outside of this GUI object. */ + int dataSortedColumn() const; + + /*! \return 1 if ascending order for data sorting data is set, -1 for descending, + 0 for no sorting. + This does not mean that any sorting has been performed within GUI of this objetct, + because the data could be changed in the meantime outside of this GUI object. + */ + int dataSortingOrder() const; + + /*! Sorts all rows by column selected with setSorting(). + If there is currently row edited, it is accepted. + If acception failed, sort() will return false. + \return true on success. */ + virtual bool sort(); + + /*! Sorts currently selected column in ascending order. + This slot is used typically for "data_sort_az" action. */ + void sortAscending(); + + /*! Sorts currently selected column in descending order. + This slot is used typically for "data_sort_za" action. */ + void sortDescending(); + + /*! \return true if data inserting is enabled (the default). */ + virtual bool isInsertingEnabled() const; + + /*! Sets insertingEnabled flag. If true, empty row is available + at the end of this widget for new entering new data. + Unless the flag is set, the widget inherits insertingEnabled flag from it's data + structure assigned with setData(). The default value if false. + + Note: it is not allowed to force insertingEnabled on when internal data + has insertingEnabled set off - in that case the method does nothing. + You can check internal data flag calling data()->insertingEnabled(). + + Setting this flag to true will have no effect if read-only flag is true. + \sa setReadOnly() + */ + void setInsertingEnabled(bool set); + + /*! \return true if row deleting is enabled. + Equal to deletionPolicy() != NoDelete && !isReadOnly()). */ + bool isDeleteEnabled() const; + + /*! \return true if inserting empty rows are enabled (false by default). + Mostly usable for not db-aware objects (e.g. used in Kexi Alter Table). + Note, that if inserting is disabled, or the data set is read-only, + this flag will be ignored. */ + bool isEmptyRowInsertingEnabled() const { return m_emptyRowInsertingEnabled; } + + /*! Sets emptyRowInserting flag. + Note, that if inserting is disabled, this flag is ignored. */ + void setEmptyRowInsertingEnabled(bool set); + + /*! Enables or disables filtering. Filtering is enabled by default. */ + virtual void setFilteringEnabled(bool set); + + /*! \return true if filtering is enabled. */ + inline bool isFilteringEnabled() const { return m_isFilteringEnabled; } + + /*! Added for convenience: configure this object + to behave more like spreadsheet (it's used for things like alter-table view). + - hides navigator + - disables sorting, inserting and filtering + - enables accepting row after cell accepting; see setAcceptsRowEditAfterCellAccepting() + - enables inserting empty row; see setEmptyRowInsertingEnabled() */ + virtual void setSpreadSheetMode(); + + /*! \return true id "spreadSheetMode" is enabled. It's false by default. */ + bool spreadSheetMode() const { return m_spreadSheetMode; } + + /*! \return true if currently selected row is edited. */ + inline bool rowEditing() const { return m_rowEditing; } + + enum DeletionPolicy + { + NoDelete = 0, + AskDelete = 1, + ImmediateDelete = 2, + SignalDelete = 3 + }; + + /*! \return deletion policy for this object. + The default (after allocating) is AskDelete. */ + DeletionPolicy deletionPolicy() const { return m_deletionPolicy; } + + virtual void setDeletionPolicy(DeletionPolicy policy); + + /*! Deletes currently selected record; does nothing if no record + is currently selected. If record is in edit mode, editing + is cancelled before deleting. */ + virtual void deleteCurrentRow(); + + /*! Inserts one empty row above row \a row. If \a row is -1 (the default), + new row is inserted above the current row (or above 1st row if there is no current). + A new item becomes current if row is -1 or if row is equal currentRow(). + This method does nothing if: + -inserting flag is disabled (see isInsertingEnabled()) + -read-only flag is set (see isReadOnly()) + \ return inserted row's data + */ + virtual KexiTableItem *insertEmptyRow(int row = -1); + + /*! For reimplementation: called by deleteItem(). If returns false, deleting is aborted. + Default implementation just returns true. */ + virtual bool beforeDeleteItem(KexiTableItem *item); + + /*! Deletes \a item. Used by deleteCurrentRow(). Calls beforeDeleteItem() before deleting, + to double-check if deleting is allowed. + \return true on success. */ + bool deleteItem(KexiTableItem *item);//, bool moveCursor=true); + + /*! Inserts newItem at \a row. -1 means current row. Used by insertEmptyRow(). */ + void insertItem(KexiTableItem *newItem, int row = -1); + + /*! Clears entire table data, its visible representation + and deletes data at database backend (if this is db-aware object). + Does not clear columns information. + Does not destroy KexiTableViewData object (if present) but only clears its contents. + Displays confirmation dialog if \a ask is true (the default is false). + Repaints widget if \a repaint is true (the default). + For empty tables, true is returned immediately. + If isDeleteEnabled() is false, false is returned. + For spreadsheet mode all current rows are just replaced by empty rows. + \return true on success, false on failure, and cancelled if user cancelled deletion + (only possible if \a ask is true). + */ + tristate deleteAllRows(bool ask = false, bool repaint = true); + + /*! \return maximum number of rows that can be displayed per one "page" + for current view's size. */ + virtual int rowsPerPage() const = 0; + + virtual void selectRow(int row); + virtual void selectNextRow(); + virtual void selectPrevRow(); + virtual void selectNextPage(); //!< page down action + virtual void selectPrevPage(); //!< page up action + virtual void selectFirstRow(); + virtual void selectLastRow(); + virtual void addNewRecordRequested(); + + /*! Clears current selection. Current row and column will be now unspecified: + currentRow(), currentColumn() will return -1, and selectedItem() will return null. */ + virtual void clearSelection(); + + /*! Moves cursor to \a row and \a col. If \a col is -1, current column number is used. + If forceSet is true, cursor position is updated even if \a row and \a col doesn't + differ from actual position. */ + virtual void setCursorPosition(int row, int col = -1, bool forceSet = false); + + /*! Ensures that cell at \a row and \a col is visible. + If \a col is -1, current column number is used. \a row and \a col (if not -1) must + be between 0 and rows() (or cols() accordingly). */ + virtual void ensureCellVisible(int row, int col/*=-1*/) = 0; + + /*! Specifies, if this object automatically accepts + row editing (using acceptRowEdit()) on accepting any cell's edit + (i.e. after acceptEditor()). \sa acceptsRowEditAfterCellAccepting() */ + virtual void setAcceptsRowEditAfterCellAccepting(bool set); + + /*! \return true, if this object automatically accepts + row editing (using acceptRowEdit()) on accepting any cell's edit + (i.e. after acceptEditor()). + By default this flag is set to false. + Not that if the query for this table has given constraints defined, + like NOT NULL / NOT EMPTY for more than one field - editing a record would + be impossible for the flag set to true, because of constraints violation. + However, setting this flag to true can be useful especially for not-db-aware + data set (it's used e.g. in Kexi Alter Table's field editor). */ + bool acceptsRowEditAfterCellAccepting() const { return m_acceptsRowEditAfterCellAccepting; } + + /*! \return true, if this table accepts dropping data on the rows. */ + bool dropsAtRowEnabled() const { return m_dropsAtRowEnabled; } + + /*! Specifies, if this table accepts dropping data on the rows. + If enabled: + - dragging over row is indicated by drawing a line at bottom side of this row + - dragOverRow() signal will be emitted on dragging, + -droppedAtRow() will be emitted on dropping + By default this flag is set to false. */ + virtual void setDropsAtRowEnabled(bool set); + + /*! \return currently used data (field/cell) editor or 0 if there is no data editing. */ + inline KexiDataItemInterface *editor() const { return m_editor; } + + /*! Cancels row editing All changes made to the editing + row during this current session will be undone. + \return true on success or false on failure (e.g. when editor does not exist) */ + virtual bool cancelRowEdit(); + + /*! Accepts row editing. All changes made to the editing + row during this current session will be accepted (saved). + \return true if accepting was successful, false otherwise + (e.g. when current row contain data that does not meet given constraints). */ + virtual bool acceptRowEdit(); + + virtual void removeEditor(); + + /*! Cancels changes made to the currently active editor. + Reverts the editor's value to old one. + \return true on success or false on failure (e.g. when editor does not exist) */ + virtual bool cancelEditor(); + + //! Accepst changes made to the currently active editor. + //! \return true on success or false on failure (e.g. when editor does not exist or there is data validation error) + virtual bool acceptEditor(); + + //! Creates editors and shows it, what usually means the beginning of a cell editing + virtual void createEditor(int row, int col, const QString& addText = QString::null, + bool removeOld = false) = 0; + + /*! Used when Return key is pressed on cell, the cell has been double clicked + or "+" navigator's button is clicked. + Also used when we want to continue editing a cell after "invalid value" message + was displayed (in this case, \a setText is usually not empty, what means + that text will be set in the cell replacing previous value). + */ + virtual void startEditCurrentCell(const QString& setText = QString::null); + + /*! Deletes currently selected cell's contents, if allowed. + In most cases delete is not accepted immediately but "row editing" mode is just started. */ + virtual void deleteAndStartEditCurrentCell(); + + inline KexiTableItem *itemAt(int row) const; + + /*! \return column information for column number \a col. + Default implementation just returns column # col, + but for Kexi Forms column data + corresponding to widget number is used here + (see KexiFormScrollView::fieldNumberForColumn()). */ + virtual KexiTableViewColumn* column(int col); + + /*! \return field number within data model connected to a data-aware + widget at column \a col. Can return -1 if there's no such column. */ + virtual int fieldNumberForColumn(int col) { return col; } + + bool hasDefaultValueAt(const KexiTableViewColumn& tvcol); + + const QVariant* bufferedValueAt(int col, bool useDefaultValueIfPossible = true); + + //! \return a type of column \a col - one of KexiDB::Field::Type + int columnType(int col); + + //! \return default value for column \a col + QVariant columnDefaultValue(int col) const; + + /*! \return true is column \a col is editable. + Default implementation takes information about 'readOnly' flag from data member. + Within forms, this is reimplemented for checking 'readOnly' flag from a widget + ('readOnly' flag from data member is still checked though). + */ + virtual bool columnEditable(int col); + + /*! Redraws the current cell. To be implemented. */ + virtual void updateCurrentCell() = 0; + + inline KexiRecordMarker* verticalHeader() const { return m_verticalHeader; } + + //! signals + virtual void itemChanged(KexiTableItem *, int row, int col) = 0; + virtual void itemChanged(KexiTableItem *, int row, int col, QVariant oldValue) = 0; + virtual void itemDeleteRequest(KexiTableItem *, int row, int col) = 0; + virtual void currentItemDeleteRequest() = 0; + //! Emitted for spreadsheet mode when an item was deleted and a new item has been appended + virtual void newItemAppendedForAfterDeletingInSpreadSheetMode() = 0; + + /*! Data has been refreshed on-screen - emitted from initDataContents(). */ + virtual void dataRefreshed() = 0; + virtual void dataSet( KexiTableViewData *data ) = 0; + + /*! \return a pointer to context menu. This can be used to plug some actions there. */ + KPopupMenu* contextMenu() const { return m_popupMenu; } + + /*! \return true if the context menu is enabled (visible) for the view. + True by default. */ + bool contextMenuEnabled() const { return m_contextMenuEnabled; } + + /*! Enables or disables the context menu for the view. */ + void setContextMenuEnabled(bool set) { m_contextMenuEnabled = set; } + + /*! \return true if vertical scrollbar's tooltips are enabled (true by default). */ + bool scrollbarToolTipsEnabled() const; + + /*! Enables or disables vertical scrollbar's tooltip. */ + void setScrollbarToolTipsEnabled(bool set); + + /*! Typically handles pressing Enter or F2 key: + if current cell has boolean type, toggles it's value, + otherwise starts editing (startEditCurrentCell()). */ + void startEditOrToggleValue(); + + /*! \return true if new row is edited; implies: rowEditing==true. */ + inline bool newRowEditing() const { return m_newRowEditing; } + + /*! Reaction on toggling a boolean value of a cell: + we're starting to edit the cell and inverting it's state. */ + virtual void boolToggled(); + + virtual void connectCellSelectedSignal(const QObject* receiver, + const char* intIntMember) = 0; + + virtual void connectRowEditStartedSignal(const QObject* receiver, + const char* intMember) = 0; + + virtual void connectRowEditTerminatedSignal(const QObject* receiver, + const char* voidMember) = 0; + + virtual void connectReloadActionsSignal(const QObject* receiver, + const char* voidMember) = 0; + + virtual void connectDataSetSignal(const QObject* receiver, + const char* kexiTableViewDataMember) = 0; + + virtual void connectToReloadDataSlot(const QObject* sender, + const char* voidSignal) = 0; + + virtual void slotDataDestroying(); + + //! Copy current selection to a clipboard (e.g. cell) + virtual void copySelection() = 0; + + //! Cut current selection to a clipboard (e.g. cell) + virtual void cutSelection() = 0; + + //! Paste current clipboard contents (e.g. to a cell) + virtual void paste() = 0; + + /*! Finds \a valueToFind within the data items + \a options are used to control the process. Selection is moved to found value. + If \a next is true, "find next" is performed, else "find previous" is performed. + + Searching behaviour also depends on status of the previous search: for every search, + position of the cells containing the found value is stored internally + by the data-aware interface (not in options). + Moreover, position (start, end) of the found value is also stored. + Thus, the subsequent search will reuse this information to be able to start + searching exactly after the previously found value (or before for "find previous" option). + The flags can be zeroed, what will lead to seaching from the first character + of the current item (cell). + + \return true if value has been found, false if value has not been found, + and cancelled if there is nothing to find or there is no data to search in. */ + virtual tristate find(const QVariant& valueToFind, + const KexiSearchAndReplaceViewInterface::Options& options, bool next); + + /*! Finds \a valueToFind within the data items and replaces with \a replacement + \a options are used to control the process. + \return true if value has been found and replaced, false if value + has not been found and replaced, and cancelled if there is nothing + to find or there is no data to search in or the data is read only. + If \a replaceAll is true, all found values are replaced. */ + virtual tristate findNextAndReplace(const QVariant& valueToFind, + const QVariant& replacement, + const KexiSearchAndReplaceViewInterface::Options& options, bool replaceAll); + + /*! \return vertical scrollbar */ + virtual QScrollBar* verticalScrollBar() const = 0; + + /*! Used in KexiTableView::keyPressEvent() (and in continuous forms). + \return true when the key press event \e was consumed. + You should also check e->isAccepted(), if it's true, nothing should be done; + if it is false, you should call setCursorPosition() for the altered \a curCol + and \c curRow variables. + + If \a moveToFirstField is not 0, *moveToFirstField will be set to true + when the cursor should be moved to the first field (in tab order) and to false otherwise. + If \a moveToLastField is not 0, *moveToLastField will be set to true + when the cursor should be moved to the last field (in tab order) and to false otherwise. + Note for forms: if moveToFirstField and moveToLastField are not 0, + \a curCol is altered after calling this method, so setCursorPosition() will set to + the index of an appropriate column (field). This is needed because field widgets can be + inserted and ordered in custom tab order, so the very first field in the data source + can be other than the very first field in the form. + + Used by KexiTableView::keyPressEvent() and KexiTableView::keyPressEvent(). */ + virtual bool handleKeyPress(QKeyEvent *e, int &curRow, int &curCol, bool fullRowSelection, + bool *moveToFirstField = 0, bool *moveToLastField = 0); + + protected: + /*! Reimplementation for KexiDataAwareObjectInterface. + Initializes data contents (resizes it, sets cursor at 1st row). + Sets record count for record navigator. + Sets cursor positin (using setCursorPosition()) to first row or sets + (-1, -1) position if no rows are available. + Called on setData(). Also called once on show event after + refreshRequested() signal was received from KexiTableViewData object. */ + virtual void initDataContents(); + + /*! Clears columns information and thus all internal table data + and its visible representation. Repaints widget if \a repaint is true. */ + virtual void clearColumns(bool repaint = true); + + /*! Called by clearColumns() to clear internals of the object. + For example, KexiTableView removes contents of it's horizontal header. */ + virtual void clearColumnsInternal(bool repaint) = 0; + + /*! @internal for implementation + This should append another section within horizontal header or any sort of caption + for a field using provided names. \a width is a hint for new field's width. */ + virtual void addHeaderColumn(const QString& caption, const QString& description, + const QIconSet& icon, int size) = 0; + + /*! @internal for implementation + \return sorting order (within GUI): -1: descending, 1: ascending, 0: no sorting. + This does not mean that any sorting has been performed within GUI of this object, + because the data could be changed in the meantime outside of this GUI object. + @see dataSortingOrder()*/ + virtual int currentLocalSortingOrder() const = 0; + + /*! @internal for implementation + \return sorted column number for this widget or -1 if no column + is sorted witin GUI. + This does not mean that the same sorting is performed within data member + which is used by this widget, because the data could be changed in the meantime + outside of this GUI widget. + @see dataSortedColumn() */ + virtual int currentLocalSortColumn() const = 0; + + /*! @internal for implementation + Shows sorting indicator order within GUI: -1: descending, 1: ascending, + 0: no sorting. This should not perform any sorting within data member + which is used by this object. + col = -1 should mean "no sorting" as well. */ + virtual void setLocalSortingOrder(int col, int order) = 0; + + /*! @internal Sets order for \a column: -1: descending, 1: ascending, + 0: invert order */ + virtual void sortColumnInternal(int col, int order = 0); + + /*! @internal for implementation + Updates GUI after sorting. + After sorting you need to ensure current row and column + is visible to avoid user confusion. For exaple, in KexiTableView + implementation, current cell is centered (if possible) + and updateContents() is called. */ + virtual void updateGUIAfterSorting() = 0; + + /*! Emitted in initActions() to force reload actions + You should remove existing actions and add them again. + Define and emit reloadActions() signal here. */ + virtual void reloadActions() = 0; + + /*! Reloads data for this object. */ + virtual void reloadData(); + + /*! for implementation as a signal */ + virtual void itemSelected(KexiTableItem *) = 0; + + /*! for implementation as a signal */ + virtual void cellSelected(int col, int row) = 0; + + /*! for implementation as a signal */ + virtual void sortedColumnChanged(int col) = 0; + + /*! for implementation as a signal */ + virtual void rowEditTerminated(int row) = 0; + + /*! Clear temporary members like the pointer to current editor. + If you reimplement this method, don't forget to call this one. */ + virtual void clearVariables(); + + /*! @internal + Creates editor structure without filling it with data. + Used in createEditor() and few places to be able to display cell contents + dependending on its type. If \a ignoreMissingEditor is false (the default), + and editor cannot be instantiated, current row editing (if present) is cancelled. + */ + virtual KexiDataItemInterface *editor( int col, bool ignoreMissingEditor = false ) = 0; + + /*! Updates editor's position, size and shows its focus (not the editor!) + for \a row and \a col, using editor(). Does nothing if editor not found. */ + virtual void editorShowFocus( int row, int col ) = 0; + + /*! Redraws specified cell. */ + virtual void updateCell(int row, int col) = 0; + + /*! Redraws all cells of specified row. */ + virtual void updateRow(int row) = 0; + + /*! Updates contents of the widget. Just call update() here on your widget. */ + virtual void updateWidgetContents() = 0; + + /*! Updates widget's contents size e.g. using QScrollView::resizeContents(). */ + virtual void updateWidgetContentsSize() = 0; + + /*! Updates scrollbars of the widget. + QScrollView::updateScrollbars() will be usually called here. */ + virtual void updateWidgetScrollBars() = 0; + + /*! @internal + Updates row appearance after canceling row edit. + Used by cancelRowEdit(). By default just calls updateRow(m_curRow). + Reimplemented by KexiFormScrollView. */ + virtual void updateAfterCancelRowEdit(); + + /*! @internal + Updates row appearance after accepting row edit. + Used by acceptRowEdit(). By default just calls updateRow(m_curRow). + Reimplemented by KexiFormScrollView. */ + virtual void updateAfterAcceptRowEdit(); + + //! Handles KexiTableViewData::rowRepaintRequested() signal + virtual void slotRowRepaintRequested(KexiTableItem& item) { Q_UNUSED( item ); } + + //! Handles KexiTableViewData::aboutToDeleteRow() signal. Prepares info for slotRowDeleted(). + virtual void slotAboutToDeleteRow(KexiTableItem& item, KexiDB::ResultInfo* result, + bool repaint); + + //! Handles KexiTableViewData::rowDeleted() signal to repaint when needed. + virtual void slotRowDeleted(); + + //! Handles KexiTableViewData::rowInserted() signal to repaint when needed. + virtual void slotRowInserted(KexiTableItem *item, bool repaint); + + //! Like above, not db-aware version + virtual void slotRowInserted(KexiTableItem *item, uint row, bool repaint); + + virtual void slotRowsDeleted( const QValueList<int> & ) {} + + //! for sanity checks (return true if m_data is present; else: outputs warning) + inline bool hasData() const; + + /*! Only needed for forms: called by KexiDataAwareObjectInterface::setCursorPosition() + if cursor's position is really changed. */ + virtual void selectCellInternal() {} + + /*! Used in KexiDataAwareObjectInterface::slotRowDeleted() + to repaint tow \a row and all visible below. + Implemented if there is more than one row displayed, i.e. currently for KexiTableView. */ + virtual void updateAllVisibleRowsBelow(int row) { Q_UNUSED( row ); } + + //! Call this from the subclass. */ + virtual void focusOutEvent(QFocusEvent* e); + + /*! Handles verticalScrollBar()'s valueChanged(int) signal. + Called when vscrollbar's value has been changed. + Call this method from the subclass. */ + virtual void vScrollBarValueChanged(int v); + + /*! Handles sliderReleased() signal of the verticalScrollBar(). Used to hide the "row number" tooltip. */ + virtual void vScrollBarSliderReleased(); + + /*! Handles timeout() signal of the m_scrollBarTipTimer. If the tooltip is visible, + m_scrollBarTipTimerCnt is set to 0 and m_scrollBarTipTimerCnt is restarted; + else the m_scrollBarTipTimerCnt is just set to 0.*/ + virtual void scrollBarTipTimeout(); + + /*! Shows error message box suitable for \a resultInfo. This can be "sorry" or "detailedSorry" + message box or "queryYesNo" if resultInfo->allowToDiscardChanges is true. + \return code of button clicked: KMessageBox::Ok in case of "sorry" or "detailedSorry" messages + and KMessageBox::Yes or KMessageBox::No in case of "queryYesNo" message. */ + int showErrorMessageForResult(KexiDB::ResultInfo* resultInfo); + + /*! Prepares array of indices of visible values to search within. + This is per-interface global cache. + Needed for faster lookup because there could be lookup values. + Called whenever columns definition changes, i.e. in setData() and clearColumns(). + @see find() */ + void updateIndicesForVisibleValues(); + + //! data structure displayed for this object + KexiTableViewData *m_data; + + //! true if m_data member is owned by this object + bool m_owner : 1; + + //! cursor position + int m_curRow, m_curCol; + + //! current data item + KexiTableItem *m_currentItem; + + //! data item's iterator + KexiTableViewData::Iterator *m_itemIterator; + + //! item data for inserting + KexiTableItem *m_insertItem; + + //! when (current or new) row is edited - changed field values are temporary stored here +// KexiDB::RowEditBuffer *m_rowEditBuffer; + + /*! true if currently selected row is edited */ + bool m_rowEditing : 1; + + /*! true if new row is edited; implies: rowEditing==true. */ + bool m_newRowEditing : 1; + + /*! 'sorting by column' availability flag for widget */ + bool m_isSortingEnabled : 1; + + /*! true if filtering is enabled for the view. */ + bool m_isFilteringEnabled : 1; + + /*! Public version of 'acceptsRowEditAfterCellAcceptin' flag (available for a user). + It's OR'es together with above flag. + */ + bool m_acceptsRowEditAfterCellAccepting : 1; + + /*! Used in acceptEditor() to avoid infinite recursion, + eg. when we're calling acceptRowEdit() during cell accepting phase. */ + bool m_inside_acceptEditor : 1; + + /*! @internal if true, this object automatically accepts + row editing (using acceptRowEdit()) on accepting any cell's edit + (i.e. after acceptEditor()). */ + bool m_internal_acceptsRowEditAfterCellAccepting : 1; + + /*! true, if inserting empty rows are enabled (false by default) */ + bool m_emptyRowInsertingEnabled : 1; + + /*! Contains 1 if the object is readOnly, 0 if not; + otherwise (-1 means "do not know") the 'readOnly' flag from object's + internal data structure (KexiTableViewData *KexiTableView::m_data) is reused. + */ + int m_readOnly; + +//! @todo really keep this here and not in KexiTableView? + /*! true if currently double click action was is performed + (so accept/cancel editor shoudn't be executed) */ + bool m_contentsMousePressEvent_dblClick : 1; + + /*! like for readOnly: 1 if inserting is enabled */ + int m_insertingEnabled; + + /*! true, if initDataContents() should be called on show event. */ + bool m_initDataContentsOnShow : 1; + + /*! Set to true in setCursorPosition() to indicate that cursor position was set + before show() and it shouldn't be changed on show(). + Only used if initDataContentsOnShow is true. */ + bool m_cursorPositionSetExplicityBeforeShow : 1; + + /*! true if spreadSheetMode is enabled. False by default. + @see KexiTableView::setSpreadSheetMode() */ + bool m_spreadSheetMode : 1; + + /*! true, if this table accepts dropping data on the rows (false by default). */ + bool m_dropsAtRowEnabled : 1; + + /*! true, if this entire (visible) row should be updated when boving to other row. + False by default. For table view with 'row highlighting' flag enabled, it is true. */ + bool m_updateEntireRowWhenMovingToOtherRow : 1; + + DeletionPolicy m_deletionPolicy; + +//! @todo make generic interface out of KexiRecordMarker + KexiRecordMarker *m_verticalHeader; + +//! @todo make generic interface out of KexiTableViewHeader + KexiTableViewHeader *m_horizontalHeader; + + KexiDataItemInterface *m_editor; +// KexiTableEdit *m_editor; + + /*! Navigation panel, used if navigationPanelEnabled is true. */ + KexiRecordNavigator *m_navPanel; //!< main navigation widget + + bool m_navPanelEnabled : 1; + + /*! true, if certical header shouldn't be increased in + KexiTableView::slotRowInserted() because it was already done + in KexiTableView::createEditor(). */ + bool m_verticalHeaderAlreadyAdded : 1; + + /*! Row number that over which user drags a mouse pointer. + Used to indicate dropping possibility for that row. + Equal -1 if no indication is needed. */ + int m_dragIndicatorLine; + + /*! Context menu widget. */ + KPopupMenu *m_popupMenu; + + bool m_contextMenuEnabled : 1; + + //! Used by updateAfterCancelRowEdit() + bool m_alsoUpdateNextRow : 1; + + /*! Row number (>=0 or -1 == no row) that will be deleted in deleteRow(). + It is set in slotAboutToDeleteRow(KexiTableItem&,KexiDB::ResultInfo*,bool)) slot + received from KexiTableViewData member. + This value will be used in slotRowDeleted() after rowDeleted() signal + is received from KexiTableViewData member and then cleared (set to -1). */ + int m_rowWillBeDeleted; + + /*! Displays passive error popup label used when invalid data has been entered. */ + QGuardedPtr<KexiArrowTip> m_errorMessagePopup; + + /*! Used to enable/disable execution of vScrollBarValueChanged() + when users navigate through rows using keyboard, so vscrollbar tooltips are not visible. */ + bool m_vScrollBarValueChanged_enabled : 1; + + /*! True, if vscrollbar tooltips are enabled (true by default). */ + bool m_scrollbarToolTipsEnabled : 1; + + QLabel* m_scrollBarTip; //!< scrollbar tooltip + QTimer m_scrollBarTipTimer; //!< scrollbar tooltip's timer + uint m_scrollBarTipTimerCnt; //!< helper for timeout counting (scrollbar tooltip) + + //! Used to mark recently found value + class PositionOfValue { + public: + PositionOfValue() : firstCharacter(0), lastCharacter(0), exists(false) + {} + uint firstCharacter; + uint lastCharacter; + bool exists : 1; + }; + + /*! Used to mark recently found value. Updated on succesful execution of find(). + If the current cursor's position changes, or data in the current cell changes, + positionOfRecentlyFoundValue.exists is set to false. */ + PositionOfValue m_positionOfRecentlyFoundValue; + + /*! Used to compare whether we're looking for new value. */ + QVariant m_recentlySearchedValue; + + /*! Used to compare whether the search direction has changed. */ + KexiSearchAndReplaceViewInterface::Options::SearchDirection m_recentSearchDirection; + + //! Setup by updateIndicesForVisibleValues() and used by find() + QValueVector<uint> m_indicesForVisibleValues; +}; + +inline bool KexiDataAwareObjectInterface::hasData() const +{ + if (!m_data) + kdDebug() << "KexiDataAwareObjectInterface: No data assigned!" << endl; + return m_data!=0; +} + +inline KexiTableItem *KexiDataAwareObjectInterface::itemAt(int row) const +{ + KexiTableItem *item = m_data->at(row); + if (!item) + kdDebug() << "KexiTableView::itemAt(" << row << "): NO ITEM!!" << endl; + else { +/* kdDebug() << "KexiTableView::itemAt(" << row << "):" << endl; + int i=1; + for (KexiTableItem::Iterator it = item->begin();it!=item->end();++it,i++) + kdDebug() << i<<": " << (*it).toString()<< endl;*/ + } + return item; +} + +//! Convenience macro used for KexiDataAwareObjectInterface implementations. +#define KEXI_DATAAWAREOBJECTINTERFACE \ +public: \ + void connectCellSelectedSignal(const QObject* receiver, const char* intIntMember) { \ + connect(this, SIGNAL(cellSelected(int,int)), receiver, intIntMember); \ + } \ + void connectRowEditStartedSignal(const QObject* receiver, const char* intMember) { \ + connect(this, SIGNAL(rowEditStarted(int)), receiver, intMember); \ + } \ + void connectRowEditTerminatedSignal(const QObject* receiver, const char* voidMember) { \ + connect(this, SIGNAL(rowEditTerminated(int)), receiver, voidMember); \ + } \ + void connectReloadActionsSignal(const QObject* receiver, const char* voidMember) { \ + connect(this, SIGNAL(reloadActions()), receiver, voidMember); \ + } \ + void connectDataSetSignal(const QObject* receiver, \ + const char* kexiTableViewDataMember) { \ + connect(this, SIGNAL(dataSet(KexiTableViewData*)), receiver, kexiTableViewDataMember); \ + } \ + void connectToReloadDataSlot(const QObject* sender, const char* voidSignal) { \ + connect(sender, voidSignal, this, SLOT(reloadData())); \ + } + +#endif diff --git a/kexi/widget/tableview/kexidataawarepropertyset.cpp b/kexi/widget/tableview/kexidataawarepropertyset.cpp new file mode 100644 index 00000000..92fda11e --- /dev/null +++ b/kexi/widget/tableview/kexidataawarepropertyset.cpp @@ -0,0 +1,260 @@ +/* This file is part of the KDE project + Copyright (C) 2004-2005 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "kexidataawarepropertyset.h" +#include "kexitableviewdata.h" +#include "kexidataawareobjectiface.h" + +#include <koproperty/property.h> +#include <kexiviewbase.h> + +#define MAX_FIELDS 101 //nice prime number (default prop. set vector size) + +KexiDataAwarePropertySet::KexiDataAwarePropertySet(KexiViewBase *view, + KexiDataAwareObjectInterface* dataObject) + : QObject( view, QCString(view->name())+"KexiDataAwarePropertySet" ) + , m_view(view) + , m_dataObject(dataObject) + , m_row(-99) +{ + m_sets.setAutoDelete(true); + +// connect(m_dataObject, SIGNAL(dataSet(KexiTableViewData*)), +// this, SLOT(slotDataSet(KexiTableViewData*))); + m_dataObject->connectDataSetSignal(this, SLOT(slotDataSet(KexiTableViewData*))); +// connect(m_dataObject, SIGNAL(cellSelected(int,int)), +// this, SLOT(slotCellSelected(int,int))); + m_dataObject->connectCellSelectedSignal(this, SLOT(slotCellSelected(int,int))); +// + slotDataSet( m_dataObject->data() ); + const bool wasDirty = view->dirty(); + clear(); + if (!wasDirty) + view->setDirty(false); +} + +KexiDataAwarePropertySet::~KexiDataAwarePropertySet() +{ +} + +void KexiDataAwarePropertySet::slotDataSet( KexiTableViewData *data ) +{ + if (!m_currentTVData.isNull()) { + m_currentTVData->disconnect( this ); + clear(); + } + m_currentTVData = data; + if (!m_currentTVData.isNull()) { + connect(m_currentTVData, SIGNAL(rowDeleted()), this, SLOT(slotRowDeleted())); + connect(m_currentTVData, SIGNAL(rowsDeleted( const QValueList<int> & )), + this, SLOT(slotRowsDeleted( const QValueList<int> & ))); + connect(m_currentTVData, SIGNAL(rowInserted(KexiTableItem*,uint,bool)), + this, SLOT(slotRowInserted(KexiTableItem*,uint,bool))); + connect(m_currentTVData, SIGNAL(reloadRequested()), + this, SLOT(slotReloadRequested())); + } +} + +void KexiDataAwarePropertySet::removeCurrentPropertySet() +{ + remove( m_dataObject->currentRow() ); +} + +void KexiDataAwarePropertySet::remove(uint row) +{ + KoProperty::Set *set = m_sets.at(row); + if (!set) + return; + set->debug(); + m_sets.remove(row); + m_view->setDirty(); + m_view->propertySetSwitched(); +} + +uint KexiDataAwarePropertySet::size() const +{ + return m_sets.size(); +} + +void KexiDataAwarePropertySet::clear(uint minimumSize) +{ + m_sets.clear(); + m_sets.resize(QMAX(minimumSize, MAX_FIELDS)); + m_view->setDirty(true); + m_view->propertySetSwitched(); +} + +void KexiDataAwarePropertySet::slotReloadRequested() +{ + clear(); +} + +void KexiDataAwarePropertySet::insert(uint row, KoProperty::Set* set, bool newOne) +{ + if (!set || row >= m_sets.size()) { + kexiwarn << "KexiDataAwarePropertySet::insert() invalid args: rew="<< row<< " propertyset="<< set<< endl; + return; + } + if (set->parent() && set->parent()!=this) { + kexiwarn << "KexiDataAwarePropertySet::insert() propertyset's parent must be NULL or this KexiDataAwarePropertySet" << endl; + return; + } + + m_sets.insert(row, set); + + connect(set, SIGNAL(propertyChanged(KoProperty::Set&, KoProperty::Property&)), m_view, SLOT(setDirty())); + + if (newOne) { + //add a special property indicating that this is brand new set, + //not just changed + KoProperty::Property* prop = new KoProperty::Property("newrow"); + prop->setVisible(false); + set->addProperty( prop ); + m_view->setDirty(); + } +} + +KoProperty::Set* KexiDataAwarePropertySet::currentPropertySet() const +{ + return (m_dataObject->currentRow() >= 0) ? m_sets.at( m_dataObject->currentRow() ) : 0; +} + +uint KexiDataAwarePropertySet::currentRow() const +{ + return m_dataObject->currentRow(); +} + +void KexiDataAwarePropertySet::slotRowDeleted() +{ + m_view->setDirty(); + removeCurrentPropertySet(); + + //let's move up all property sets that are below that deleted + m_sets.setAutoDelete(false);//to avoid auto deleting in insert() + const int r = m_dataObject->currentRow(); + for (int i=r;i<int(m_sets.size()-1);i++) { + KoProperty::Set *set = m_sets[i+1]; + m_sets.insert( i , set ); + } + m_sets.insert( m_sets.size()-1, 0 ); + m_sets.setAutoDelete(true);//revert the flag + + m_view->propertySetSwitched(); + emit rowDeleted(); +} + +void KexiDataAwarePropertySet::slotRowsDeleted( const QValueList<int> &rows ) +{ + //let's move most property sets up & delete unwanted + m_sets.setAutoDelete(false);//to avoid auto deleting in insert() + const int orig_size = size(); + int prev_r = -1; + int num_removed = 0, cur_r = -1; + for (QValueList<int>::ConstIterator r_it = rows.constBegin(); r_it!=rows.constEnd() && *r_it < orig_size; ++r_it) { + cur_r = *r_it;// - num_removed; + if (prev_r>=0) { +// kdDebug() << "move " << prev_r+num_removed-1 << ".." << cur_r-1 << " to " << prev_r+num_removed-1 << ".." << cur_r-2 << endl; + int i=prev_r; + KoProperty::Set *set = m_sets.take(i+num_removed); + kdDebug() << "property set " << i+num_removed << " deleted" << endl; + delete set; + num_removed++; + for (; (i+num_removed)<cur_r; i++) { + m_sets.insert( i, m_sets[i+num_removed] ); + kdDebug() << i << " <- " << i+num_removed << endl; + } + } + prev_r = cur_r - num_removed; + } + //move remaining property sets up + if (cur_r>=0) { + KoProperty::Set *set = m_sets.take(cur_r); + kdDebug() << "property set " << cur_r << " deleted" << endl; + delete set; + num_removed++; + for (int i=prev_r; (i+num_removed)<orig_size; i++) { + m_sets.insert( i, m_sets[i+num_removed] ); + kdDebug() << i << " <- " << i+num_removed << endl; + } + } + //finally: clear last rows + for (int i=orig_size-num_removed; i<orig_size; i++) { + kdDebug() << i << " <- zero" << endl; + m_sets.insert( i, 0 ); + } + m_sets.setAutoDelete(true);//revert the flag + + if (num_removed>0) + m_view->setDirty(); + m_view->propertySetSwitched(); +} + +//void KexiDataAwarePropertySet::slotEmptyRowInserted(KexiTableItem*, uint /*index*/) +void KexiDataAwarePropertySet::slotRowInserted(KexiTableItem*, uint row, bool /*repaint*/) +{ + m_view->setDirty(); + + //let's move down all property set that are below + m_sets.setAutoDelete(false);//to avoid auto deleting in insert() +// const int r = m_dataObject->currentRow(); + m_sets.resize(m_sets.size()+1); + for (int i=int(m_sets.size())-1; i>(int)row; i--) { + KoProperty::Set *set = m_sets[i-1]; + m_sets.insert( i , set ); + } + m_sets.insert( row, 0 ); + m_sets.setAutoDelete(true);//revert the flag + + m_view->propertySetSwitched(); + + emit rowInserted(); +} + +void KexiDataAwarePropertySet::slotCellSelected(int, int row) +{ + if(row == m_row) + return; + m_row = row; + m_view->propertySetSwitched(); +} + +KoProperty::Set* KexiDataAwarePropertySet::findPropertySetForItem(KexiTableItem& item) +{ + if (m_currentTVData.isNull()) + return 0; + int idx = m_currentTVData->findRef(&item); + if (idx<0) + return 0; + return m_sets[idx]; +} + +int KexiDataAwarePropertySet::findRowForPropertyValue(const QCString& propertyName, const QVariant& value) +{ + const int size = m_sets.size(); + for (int i=0; i<size; i++) { + KoProperty::Set *set = m_sets[i]; + if (!set || !set->contains(propertyName)) + continue; + if (set->property(propertyName).value() == value) + return i; + } + return -1; +} + +#include "kexidataawarepropertyset.moc" diff --git a/kexi/widget/tableview/kexidataawarepropertyset.h b/kexi/widget/tableview/kexidataawarepropertyset.h new file mode 100644 index 00000000..cee55da0 --- /dev/null +++ b/kexi/widget/tableview/kexidataawarepropertyset.h @@ -0,0 +1,149 @@ +/* This file is part of the KDE project + Copyright (C) 2004-2005 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIDATAAWAREPROPERTYSET_H +#define KEXIDATAAWAREPROPERTYSET_H + +#include <qguardedptr.h> +#include <qptrvector.h> +#include <koproperty/set.h> + +typedef QPtrVector<KoProperty::Set> SetVector; + +class KexiViewBase; +class KexiTableItem; +class KexiTableViewData; +class KexiDataAwareObjectInterface; + +/*! This helper class handles data changes of a single + object implementing KexiDataAwareObjectInterface (e.g. KexiTableView) inside + a KexiViewBase container. + + It is currently used in KexiAlterTableDialog and KexiQueryDesignerGuiEditor, + and may be used for similar purposes, when each KexiDataAwareObjectInterface's + row can be associated with single KoProperty::Set object, and given + KexiDataAwareObjectInterface object has to inform the world about currently + selected row/property set. + + Following functionality is built-in: + - auto-initializing after resetting of table view's data + - destroying single property set that is associated with deleted row + - inserting single property set that and associating it with new row + - all property sets are cleared when view's data is cleared (using clear()) + - setting view's 'dirty' flag when needed + - signalling via KexiViewBase::propertySetSwitched() that current property + set has changed (e.g. on moving to other row) +*/ +class KEXIDATATABLE_EXPORT KexiDataAwarePropertySet : public QObject +{ + Q_OBJECT + + public: + /*! You can instantiate KexiDataAwarePropertySet object + for existing \a tableView and \a view. \a tableView can have data assigned + (KexiDataAwareObjectInterface::setData()) now but it can be done later as well + (but assigning data is needed for proper functionality). + Any changed reassignments of table view's data will be handled automatically. */ + KexiDataAwarePropertySet(KexiViewBase *view, KexiDataAwareObjectInterface* dataObject); + + virtual ~KexiDataAwarePropertySet(); + + uint size() const; + + KoProperty::Set* currentPropertySet() const; + + uint currentRow() const; + + inline KoProperty::Set* at(uint row) const { return m_sets[row]; } + + /*! \return a pointer to property set assigned for \a item or null if \a item has no + property set assigned or it's not owned by assigned table view or + if assigned table view has no data set. */ + KoProperty::Set* findPropertySetForItem(KexiTableItem& item); + + /*! \return number of the first row containing \a propertyName property equal to \a value. + This is used e.g. in the Table Designer to find a row by field name. + If no such row has been found, -1 is returned. */ + int findRowForPropertyValue(const QCString& propertyName, const QVariant& value); + + signals: + /*! Emmited when row is deleted. + KexiDataAwareObjectInterface::rowDeleted() signal is usually used but when you're using + KexiDataAwarePropertySet, you never know if currentPropertySet() is updated. + So use this signal instead. */ + void rowDeleted(); + + /*! Emmited when row is inserted. + Purpose of this signal is similar to rowDeleted() signal. */ + void rowInserted(); + + public slots: + void removeCurrentPropertySet(); + + void clear(uint minimumSize = 0); + + /*! Inserts \a set property set at \a row position. + If there was a buffer at this position before, it will be destroyed. + If \a newOne is true, the property set will be marked as newly created, + simply by adding "newrow" property. + + The property set \a set will be owned by this object, so you should not + delete this property set by hand but call removeCurrentPropertySet() + or remove(uint) instead. + Note that property set's parent (QObject::parent()) must be null + or qual to this KexiDataAwarePropertySet object, otherwise this method + will fail with a warning. + */ + void insert(uint row, KoProperty::Set* set, bool newOne = false); + + /*! Removes a property set at \a row position. */ + void remove(uint row); + + protected slots: + /*! Handles table view's data source changes. */ + void slotDataSet( KexiTableViewData *data ); + + //! Called on row delete in a tableview. + void slotRowDeleted(); + + //! Called on multiple rows delete in a tableview. + void slotRowsDeleted( const QValueList<int> &rows ); + + //! Called on \a row insertion in a tableview. + void slotRowInserted(KexiTableItem* item, uint row, bool repaint); + + //! Called on selecting another cell in a tableview. + void slotCellSelected(int, int row); + + //! Called on clearing tableview's data: just clears all property sets. + void slotReloadRequested(); + + protected: + SetVector m_sets; //!< prop. sets vector + + QGuardedPtr<KexiViewBase> m_view; + KexiDataAwareObjectInterface* m_dataObject; +// QGuardedPtr<KexiTableView> m_tableView; + QGuardedPtr<KexiTableViewData> m_currentTVData; + + int m_row; //!< used to know if a new row is selected in slotCellSelected() +}; + +#endif + diff --git a/kexi/widget/tableview/kexidatatableview.cpp b/kexi/widget/tableview/kexidatatableview.cpp new file mode 100644 index 00000000..9248e890 --- /dev/null +++ b/kexi/widget/tableview/kexidatatableview.cpp @@ -0,0 +1,121 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Lucijan Busch <lucijan@kde.org> + Copyright (C) 2003 Joseph Wenninger <jowenn@kde.org> + Copyright (C) 2003-2004 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <qtimer.h> +#include <qapplication.h> + +#include <kmessagebox.h> +#include <klocale.h> +#include <kdebug.h> +#include <kaction.h> + +#include <kexidb/connection.h> +#include <kexidb/cursor.h> + +#include "kexidatatableview.h" + + +KexiDataTableView::KexiDataTableView(QWidget *parent, const char *name) + : KexiTableView(0, parent, name) +{ + init(); +} + +KexiDataTableView::KexiDataTableView(QWidget *parent, const char *name, KexiDB::Cursor *cursor) + : KexiTableView(0, parent, name) +{ + init(); + setData(cursor); +} + +KexiDataTableView::~KexiDataTableView() +{ +} + +void +KexiDataTableView::init() +{ + m_cursor = 0; + +// m_maxRecord = 0; +// m_records = 0; +// m_first = false; + +// connect(this, SIGNAL(contentsMoving(int, int)), this, SLOT(slotMoving(int))); +// connect(verticalScrollBar(), SIGNAL(sliderMoved(int)), this, SLOT(slotMoving(int))); +} + +/*void KexiDataTableView::initActions(KActionCollection *col) +{ + KexiTableView::initActions(col); + new KAction(i18n("Filter"), "filter", 0, this, SLOT(filter()), col, "tablepart_filter"); +}*/ + +bool KexiDataTableView::setData(KexiDB::Cursor *cursor) +{ +//js if (!m_first) +//js clearColumns(); + if (!cursor) { + clearColumns(); + m_cursor = 0; + return true; + } + if (cursor!=m_cursor) { + clearColumns(); + } + m_cursor = cursor; + + if (!m_cursor->query()) { + kdDebug() << "KexiDataTableView::setData(): WARNING: cursor should have query schema defined!\n--aborting setData()." << endl; + m_cursor->debug(); + clearColumns(); + return false; + } + + if (m_cursor->fieldCount()<1) { + clearColumns(); + return true; + } + + if (!m_cursor->isOpened() && !m_cursor->open()) { + kdDebug() << "KexiDataTableView::setData(): WARNING: cannot open cursor\n--aborting setData(). \n" << + m_cursor->serverErrorMsg() << endl; + m_cursor->debug(); + clearColumns(); + return false; + } + + KexiTableViewData *tv_data = new KexiTableViewData(m_cursor); + + QString caption = m_cursor->query()->caption(); + if (caption.isEmpty()) + caption = m_cursor->query()->name(); + + setCaption( caption ); + + //PRIMITIVE!! data setting: + tv_data->preloadAllRows(); + + KexiTableView::setData(tv_data); + return true; +} + +#include "kexidatatableview.moc" diff --git a/kexi/widget/tableview/kexidatatableview.h b/kexi/widget/tableview/kexidatatableview.h new file mode 100644 index 00000000..6f421894 --- /dev/null +++ b/kexi/widget/tableview/kexidatatableview.h @@ -0,0 +1,94 @@ +/* This file is part of the KDE project + Copyright (C) 2003 Lucijan Busch <lucijan@kde.org> + Copyright (C) 2003 Joseph Wenninger <jowenn@kde.org> + Copyright (C) 2003-2004 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef KEXIDATATABLEVIEW_H +#define KEXIDATATABLEVIEW_H + +#include "kexitableview.h" + +class KexiTableItem; +class QVariant; +class KXMLGUIClient; + +namespace KexiDB { + class Cursor; +} + +/** + * Database aware table widget. + */ +class KEXIDATATABLE_EXPORT KexiDataTableView : public KexiTableView +{ + Q_OBJECT + + public: + /** + * creates a blank widget + */ + KexiDataTableView(QWidget *parent, const char *name =0); + + /*! Creates a table widget and fills it using data from \a cursor. + Cursor will be opened (with open()) if it is not yet opened. + Cursor must be defined on query schema, not raw statement (see Connection::prepareQuery() + and Connection::executeQuery()), otherwise the table view remain not filled with data. + Cursor \a cursor will not be owned by this object. + */ + KexiDataTableView(QWidget *parent, const char *name, KexiDB::Cursor *cursor); + + ~KexiDataTableView(); + +// virtual void initActions(KActionCollection *col); + + /*! Fills table view with data using \a cursor. \return true on success. + Cursor \a cursor will not be owned by this object. */ + bool setData(KexiDB::Cursor *cursor); + + /*! \return cursor used as data source for this table view, + or NULL if no valid cursor is defined. */ + KexiDB::Cursor *cursor() { return m_cursor; } + + /** + * @returns the number of records in the data set, (if data set is present) + * @note not all of the records have to be processed + */ + int recordCount() { return m_data->count(); } + + #ifndef KEXI_NO_PRINT +// virtual void print(KPrinter &printer); + #endif + + protected: + void init(); + + /*! Reimplemented: called by deleteItem() - we are deleting data associated with \a item. */ +// virtual bool beforeDeleteItem(KexiTableItem *item); + + protected slots: +// void slotClearData(); + + private: + //db stuff + KexiDB::Cursor *m_cursor; + +// QMap<KexiDBUpdateRecord*,KexiTableItem*> m_insertMapping; +}; + +#endif diff --git a/kexi/widget/tableview/kexidatetableedit.cpp b/kexi/widget/tableview/kexidatetableedit.cpp new file mode 100644 index 00000000..8a1fbcae --- /dev/null +++ b/kexi/widget/tableview/kexidatetableedit.cpp @@ -0,0 +1,290 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Lucijan Busch <lucijan@gmx.at> + Copyright (C) 2003 Daniel Molkentin <molkentin@kde.org> + Copyright (C) 2003-2004,2006 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "kexidatetableedit.h" + +#include <qapplication.h> +#include <qpainter.h> +#include <qvariant.h> +#include <qrect.h> +#include <qpalette.h> +#include <qcolor.h> +#include <qfontmetrics.h> +#include <qdatetime.h> +#include <qcursor.h> +#include <qpoint.h> +#include <qlayout.h> +#include <qtoolbutton.h> +#include <qdatetimeedit.h> +#include <qclipboard.h> + +#include <kdebug.h> +#include <klocale.h> +#include <kglobal.h> +#include <kdatepicker.h> +#include <kdatetbl.h> +#include <klineedit.h> +#include <kpopupmenu.h> +#include <kdatewidget.h> + +#include <kexiutils/utils.h> + + +KexiDateTableEdit::KexiDateTableEdit(KexiTableViewColumn &column, QWidget *parent) + : KexiInputTableEdit(column, parent) +{ + setName("KexiDateTableEdit"); + +//! @todo add QValidator so date like "2006-59-67" cannot be even entered + + m_lineedit->setInputMask( m_formatter.inputMask() ); +} + +KexiDateTableEdit::~KexiDateTableEdit() +{ +} + +void KexiDateTableEdit::setValueInInternalEditor(const QVariant &value) +{ + if (value.isValid() && value.toDate().isValid()) + m_lineedit->setText( m_formatter.dateToString( value.toDate() ) ); + else + m_lineedit->setText( QString::null ); +} + +void KexiDateTableEdit::setValueInternal(const QVariant& add_, bool removeOld) +{ + if (removeOld) { + //new date entering... just fill the line edit +//! @todo cut string if too long.. + QString add(add_.toString()); + m_lineedit->setText(add); + m_lineedit->setCursorPosition(add.length()); + return; + } + setValueInInternalEditor( m_origValue ); + m_lineedit->setCursorPosition(0); //ok? +} + +void KexiDateTableEdit::setupContents( QPainter *p, bool focused, const QVariant& val, + QString &txt, int &align, int &x, int &y_offset, int &w, int &h ) +{ + Q_UNUSED(p); + Q_UNUSED(focused); + Q_UNUSED(x); + Q_UNUSED(w); + Q_UNUSED(h); +#ifdef Q_WS_WIN + y_offset = -1; +#else + y_offset = 0; +#endif + if (val.toDate().isValid()) + txt = m_formatter.dateToString(val.toDate()); +// txt = val.toDate().toString(Qt::LocalDate); + align |= AlignLeft; +} + +bool KexiDateTableEdit::valueIsNull() +{ +// if (m_lineedit->text().replace(m_formatter.separator(),"").stripWhiteSpace().isEmpty()) + if (m_formatter.isEmpty(m_lineedit->text())) //empty date is null + return true; + return dateValue().isNull(); +} + +bool KexiDateTableEdit::valueIsEmpty() +{ + return valueIsNull();//js OK? TODO (nonsense?) +} + +QDate KexiDateTableEdit::dateValue() const +{ + return m_formatter.stringToDate( m_lineedit->text() ); +} + +QVariant KexiDateTableEdit::value() +{ + return m_formatter.stringToVariant( m_lineedit->text() ); +} + +bool KexiDateTableEdit::valueIsValid() +{ + if (m_formatter.isEmpty(m_lineedit->text())) //empty date is valid + return true; + return m_formatter.stringToDate( m_lineedit->text() ).isValid(); +} + +void KexiDateTableEdit::handleCopyAction(const QVariant& value, const QVariant& visibleValue) +{ + Q_UNUSED(visibleValue); + if (!value.isNull() && value.toDate().isValid()) + qApp->clipboard()->setText( m_formatter.dateToString(value.toDate()) ); + else + qApp->clipboard()->setText( QString::null ); +} + +void KexiDateTableEdit::handleAction(const QString& actionName) +{ + const bool alreadyVisible = m_lineedit->isVisible(); + + if (actionName=="edit_paste") { + const QVariant newValue( m_formatter.stringToDate(qApp->clipboard()->text()) ); + if (!alreadyVisible) { //paste as the entire text if the cell was not in edit mode + emit editRequested(); + m_lineedit->clear(); + } + setValueInInternalEditor( newValue ); + } + else + KexiInputTableEdit::handleAction(actionName); +} + +/* +void +KexiDateTableEdit::slotDateChanged(QDate date) +{ + m_edit->setDate(date); + repaint(); +} + +void +KexiDateTableEdit::slotShowDatePicker() +{ + QDate date = m_edit->date(); + + m_datePicker->setDate(date); + m_datePicker->setFocus(); + m_datePicker->show(); + m_datePicker->setFocus(); +} + +//! @internal helper +void KexiDateTableEdit::moveToFirstSection() +{ + if (!m_dte_date_obj) + return; +#ifdef QDateTimeEditor_HACK + if (m_dte_date) + m_dte_date->setFocusSection(0); +#else +#ifdef Q_WS_WIN //tmp + QKeyEvent ke_left(QEvent::KeyPress, Qt::Key_Left, 0, 0); + for (int i=0; i<8; i++) + QApplication::sendEvent( m_dte_date_obj, &ke_left ); +#endif +#endif +} + +bool KexiDateTableEdit::eventFilter( QObject *o, QEvent *e ) +{ + if (o==m_datePicker) { + kdDebug() << e->type() << endl; + switch (e->type()) { + case QEvent::Hide: + m_datePickerPopupMenu->hide(); + break; + case QEvent::KeyPress: + case QEvent::KeyRelease: { + kdDebug() << "ok!" << endl; + QKeyEvent *ke = (QKeyEvent *)e; + if (ke->key()==Key_Enter || ke->key()==Key_Return) { + //accepting picker + acceptDate(); + return true; + } + else if (ke->key()==Key_Escape) { + //canceling picker + m_datePickerPopupMenu->hide(); + kdDebug() << "reject" << endl; + return true; + } + else m_datePickerPopupMenu->setFocus(); + break; + } + default: + break; + } + } +#ifdef Q_WS_WIN //tmp + else if (e->type()==QEvent::FocusIn && o->parent() && o->parent()->parent()==m_edit + && m_setNumberOnFocus >= 0 && m_dte_date_obj) + { + // there was a number character passed as 'add' parameter in init(): + moveToFirstSection(); + QKeyEvent ke(QEvent::KeyPress, int(Qt::Key_0)+m_setNumberOnFocus, + '0'+m_setNumberOnFocus, 0, QString::number(m_setNumberOnFocus)); + QApplication::sendEvent( m_dte_date_obj, &ke ); + m_setNumberOnFocus = -1; + } +#endif +#ifdef QDateTimeEditor_HACK + else if (e->type()==QEvent::KeyPress && m_dte_date) { + QKeyEvent *ke = static_cast<QKeyEvent*>(e); + if ((ke->key()==Qt::Key_Right && !m_sentEvent && cursorAtEnd()) + || (ke->key()==Qt::Key_Left && !m_sentEvent && cursorAtStart())) + { + //the editor should send this key event: + m_sentEvent = true; //avoid recursion + QApplication::sendEvent( this, ke ); + m_sentEvent = false; + ke->ignore(); + return true; + } + } +#endif + return false; +} + +void KexiDateTableEdit::acceptDate() +{ + m_edit->setDate(m_datePicker->date()); + m_datePickerPopupMenu->hide(); + kdDebug() << "accept" << endl; +} + +bool KexiDateTableEdit::cursorAtStart() +{ +#ifdef QDateTimeEditor_HACK + return m_dte_date && m_edit->hasFocus() && m_dte_date->focusSection()==0; +#else + return false; +#endif +} + +bool KexiDateTableEdit::cursorAtEnd() +{ +#ifdef QDateTimeEditor_HACK + return m_dte_date && m_edit->hasFocus() + && m_dte_date->focusSection()==int(m_dte_date->sectionCount()-1); +#else + return false; +#endif +} + +void KexiDateTableEdit::clear() +{ + m_edit->setDate(QDate()); +}*/ + +KEXI_CELLEDITOR_FACTORY_ITEM_IMPL(KexiDateEditorFactoryItem, KexiDateTableEdit) + +#include "kexidatetableedit.moc" diff --git a/kexi/widget/tableview/kexidatetableedit.h b/kexi/widget/tableview/kexidatetableedit.h new file mode 100644 index 00000000..4f2a4f59 --- /dev/null +++ b/kexi/widget/tableview/kexidatetableedit.h @@ -0,0 +1,66 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Lucijan Busch <lucijan@gmx.at> + Copyright (C) 2003 Daniel Molkentin <molkentin@kde.org> + Copyright (C) 2003-2004,2006 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef KEXIDATETABLEEDIT_H +#define KEXIDATETABLEEDIT_H + +#include "kexiinputtableedit.h" +#include <widget/utils/kexidatetimeformatter.h> + +/*! @short Editor class for Date type. + It is a replacement QDateEdit due to usability problems: + people are accustomed to use single-character cursor. + Date format is retrieved from the KDE global settings. + and input/output is performed using KLineEdit (from KexiInputTableEdit). +*/ +class KexiDateTableEdit : public KexiInputTableEdit +{ + Q_OBJECT + + public: + KexiDateTableEdit(KexiTableViewColumn &column, QWidget *parent=0); + virtual ~KexiDateTableEdit(); + virtual void setupContents( QPainter *p, bool focused, const QVariant& val, + QString &txt, int &align, int &x, int &y_offset, int &w, int &h ); + virtual QVariant value(); + virtual bool valueIsNull(); + virtual bool valueIsEmpty(); + virtual bool valueIsValid(); + + /*! Reimplemented after KexiInputTableEdit. */ + virtual void handleAction(const QString& actionName); + + /*! Reimplemented after KexiInputTableEdit. */ + virtual void handleCopyAction(const QVariant& value, const QVariant& visibleValue); + + protected: + //! helper + void setValueInInternalEditor(const QVariant &value); + virtual void setValueInternal(const QVariant& add, bool removeOld); + QDate dateValue() const; + + //! Used to format and convert date values + KexiDateFormatter m_formatter; +}; + +KEXI_DECLARE_CELLEDITOR_FACTORY_ITEM(KexiDateEditorFactoryItem) + +#endif diff --git a/kexi/widget/tableview/kexidatetimetableedit.cpp b/kexi/widget/tableview/kexidatetimetableedit.cpp new file mode 100644 index 00000000..fbca7cd6 --- /dev/null +++ b/kexi/widget/tableview/kexidatetimetableedit.cpp @@ -0,0 +1,165 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Lucijan Busch <lucijan@gmx.at> + Copyright (C) 2003 Daniel Molkentin <molkentin@kde.org> + Copyright (C) 2003-2004,2006 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "kexidatetimetableedit.h" + +#include <qapplication.h> +#include <qpainter.h> +#include <qvariant.h> +#include <qrect.h> +#include <qpalette.h> +#include <qcolor.h> +#include <qfontmetrics.h> +#include <qdatetime.h> +#include <qcursor.h> +#include <qpoint.h> +#include <qlayout.h> +#include <qtoolbutton.h> +#include <qdatetimeedit.h> +#include <qclipboard.h> + +#include <kdebug.h> +#include <klocale.h> +#include <kglobal.h> +#include <kdatepicker.h> +#include <kdatetbl.h> +#include <klineedit.h> +#include <kpopupmenu.h> +#include <kdatewidget.h> + +#include <kexiutils/utils.h> + +KexiDateTimeTableEdit::KexiDateTimeTableEdit(KexiTableViewColumn &column, QWidget *parent) + : KexiInputTableEdit(column, parent) +{ + setName("KexiDateTimeTableEdit"); + +//! @todo add QValidator so time like "99:88:77" cannot be even entered + + m_lineedit->setInputMask( + dateTimeInputMask( m_dateFormatter, m_timeFormatter ) ); +} + +KexiDateTimeTableEdit::~KexiDateTimeTableEdit() +{ +} + +void KexiDateTimeTableEdit::setValueInInternalEditor(const QVariant &value) +{ + if (value.isValid() && value.toDateTime().isValid()) + m_lineedit->setText( + m_dateFormatter.dateToString( value.toDateTime().date() ) + " " + + m_timeFormatter.timeToString( value.toDateTime().time() ) ); + else + m_lineedit->setText( QString::null ); +} + +void KexiDateTimeTableEdit::setValueInternal(const QVariant& add_, bool removeOld) +{ + if (removeOld) { + //new time entering... just fill the line edit +//! @todo cut string if too long.. + QString add(add_.toString()); + m_lineedit->setText(add); + m_lineedit->setCursorPosition(add.length()); + return; + } + setValueInInternalEditor( m_origValue ); + m_lineedit->setCursorPosition(0); //ok? +} + +void KexiDateTimeTableEdit::setupContents( QPainter *p, bool focused, const QVariant& val, + QString &txt, int &align, int &x, int &y_offset, int &w, int &h ) +{ + Q_UNUSED(p); + Q_UNUSED(focused); + Q_UNUSED(x); + Q_UNUSED(w); + Q_UNUSED(h); +#ifdef Q_WS_WIN + y_offset = -1; +#else + y_offset = 0; +#endif + if (val.toDateTime().isValid()) + txt = m_dateFormatter.dateToString(val.toDateTime().date()) + " " + + m_timeFormatter.timeToString(val.toDateTime().time()); + align |= AlignLeft; +} + +bool KexiDateTimeTableEdit::valueIsNull() +{ + if (textIsEmpty()) + return true; + return !stringToDateTime(m_dateFormatter, m_timeFormatter, m_lineedit->text()).isValid(); +} + +bool KexiDateTimeTableEdit::valueIsEmpty() +{ + return valueIsNull();//js OK? TODO (nonsense?) +} + +QVariant KexiDateTimeTableEdit::value() +{ + if (textIsEmpty()) + return QVariant(); + return stringToDateTime(m_dateFormatter, m_timeFormatter, m_lineedit->text()); +} + +bool KexiDateTimeTableEdit::valueIsValid() +{ + return dateTimeIsValid( m_dateFormatter, m_timeFormatter, m_lineedit->text() ); +} + +bool KexiDateTimeTableEdit::textIsEmpty() const +{ + return dateTimeIsEmpty( m_dateFormatter, m_timeFormatter, m_lineedit->text() ); +} + +void KexiDateTimeTableEdit::handleCopyAction(const QVariant& value, const QVariant& visibleValue) +{ + Q_UNUSED(visibleValue); + if (!value.isNull() && value.toDateTime().isValid()) + qApp->clipboard()->setText( m_dateFormatter.dateToString(value.toDateTime().date()) + " " + + m_timeFormatter.timeToString(value.toDateTime().time()) ); + else + qApp->clipboard()->setText( QString::null ); +} + +void KexiDateTimeTableEdit::handleAction(const QString& actionName) +{ + const bool alreadyVisible = m_lineedit->isVisible(); + + if (actionName=="edit_paste") { + const QVariant newValue( stringToDateTime(m_dateFormatter, m_timeFormatter, qApp->clipboard()->text()) ); + if (!alreadyVisible) { //paste as the entire text if the cell was not in edit mode + emit editRequested(); + m_lineedit->clear(); + } + setValueInInternalEditor( newValue ); + } + else + KexiInputTableEdit::handleAction(actionName); +} + +KEXI_CELLEDITOR_FACTORY_ITEM_IMPL(KexiDateTimeEditorFactoryItem, KexiDateTimeTableEdit) + +#include "kexidatetimetableedit.moc" diff --git a/kexi/widget/tableview/kexidatetimetableedit.h b/kexi/widget/tableview/kexidatetimetableedit.h new file mode 100644 index 00000000..c2f9eba8 --- /dev/null +++ b/kexi/widget/tableview/kexidatetimetableedit.h @@ -0,0 +1,69 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Lucijan Busch <lucijan@gmx.at> + Copyright (C) 2003 Daniel Molkentin <molkentin@kde.org> + Copyright (C) 2003-2004,2006 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef KEXIDATETIMETABLEEDIT_H +#define KEXIDATETIMETABLEEDIT_H + +#include "kexidatetableedit.h" +#include "kexitimetableedit.h" + +/*! @short Editor class for Date/Time type. + It is a replacement QDateTimeEdit due to usability problems: + people are accustomed to use single-character cursor. + Date and Time format is retrieved from the KDE global settings + and input/output is performed using KLineEdit (from KexiInputTableEdit). +*/ +class KexiDateTimeTableEdit : public KexiInputTableEdit +{ + Q_OBJECT + + public: + KexiDateTimeTableEdit(KexiTableViewColumn &column, QWidget *parent=0); + virtual ~KexiDateTimeTableEdit(); + virtual void setupContents( QPainter *p, bool focused, const QVariant& val, + QString &txt, int &align, int &x, int &y_offset, int &w, int &h ); + virtual QVariant value(); + virtual bool valueIsNull(); + virtual bool valueIsEmpty(); + virtual bool valueIsValid(); + + /*! Reimplemented after KexiInputTableEdit. */ + virtual void handleAction(const QString& actionName); + + /*! Reimplemented after KexiInputTableEdit. */ + virtual void handleCopyAction(const QVariant& value, const QVariant& visibleValue); + + protected: + //! helper + void setValueInInternalEditor(const QVariant &value); + virtual void setValueInternal(const QVariant& add, bool removeOld); + bool textIsEmpty() const; + + //! Used to format and convert date values + KexiDateFormatter m_dateFormatter; + + //! Used to format and convert time values + KexiTimeFormatter m_timeFormatter; +}; + +KEXI_DECLARE_CELLEDITOR_FACTORY_ITEM(KexiDateTimeEditorFactoryItem) + +#endif diff --git a/kexi/widget/tableview/kexiinputtableedit.cpp b/kexi/widget/tableview/kexiinputtableedit.cpp new file mode 100644 index 00000000..9af5c627 --- /dev/null +++ b/kexi/widget/tableview/kexiinputtableedit.cpp @@ -0,0 +1,395 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Lucijan Busch <lucijan@gmx.at> + Copyright (C) 2003-2007 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "kexiinputtableedit.h" + +#include <qregexp.h> +#include <qevent.h> +#include <qlayout.h> +#include <qtimer.h> +#include <qpainter.h> +#include <qapplication.h> +#include <qclipboard.h> +#include <qtooltip.h> + +#include <kglobal.h> +#include <klocale.h> +#include <kdebug.h> +#include <kglobalsettings.h> +#include <kcompletionbox.h> +#include <knumvalidator.h> +#include <kexiutils/longlongvalidator.h> +#include <kexidb/field.h> +#include <kexidb/fieldvalidator.h> + +//! @internal +class MyLineEdit : public KLineEdit +{ + public: + MyLineEdit(QWidget *parent, const char *name) : KLineEdit(parent,name) + {} + protected: + virtual void drawFrame ( QPainter * p ) { + p->setPen( QPen( colorGroup().text() ) ); + QRect r = rect(); + p->moveTo( r.topLeft() ); + p->lineTo( r.topRight() ); + p->lineTo( r.bottomRight() ); + p->lineTo( r.bottomLeft() ); + if (pos().x() == 0) //draw left side only when it is @ the edge + p->lineTo( r.topLeft() ); + } +}; + +//====================================================== + +KexiInputTableEdit::KexiInputTableEdit(KexiTableViewColumn &column, QWidget *parent) + : KexiTableEdit(column, parent) +{ + setName("KexiInputTableEdit"); +// m_type = f.type(); //copied because the rest of code uses m_type +// m_field = &f; +// m_origValue = value;//original value + init(); +} + +KexiInputTableEdit::~KexiInputTableEdit() +{ +} + +void KexiInputTableEdit::init() +{ +// kdDebug() << "KexiInputTableEdit: m_origValue.typeName()==" << m_origValue.typeName() << endl; +// kdDebug() << "KexiInputTableEdit: type== " << field()->typeName() << endl; +// kdDebug() << "KexiInputTableEdit: displayed type== " << displayedField()->typeName() << endl; + + m_textFormatter.setField( field() ); + + //init settings + m_decsym = KGlobal::locale()->decimalSymbol(); + if (m_decsym.isEmpty()) + m_decsym=".";//default + + const bool align_right = displayedField()->isNumericType(); + + if (!align_right) { + //create layer for internal editor + QHBoxLayout *lyr = new QHBoxLayout(this); + lyr->addSpacing(4); + lyr->setAutoAdd(true); + } + + //create internal editor + m_lineedit = new MyLineEdit(this, "KexiInputTableEdit-KLineEdit"); + setViewWidget(m_lineedit); + if (align_right) + m_lineedit->setAlignment(AlignRight); +// m_cview->setFrame(false); +// m_cview->setFrameStyle( QFrame::Plain | QFrame::Box ); +// m_cview->setLineWidth( 1 ); + m_calculatedCell = false; + +#if 0 //js TODO + connect(m_cview->completionBox(), SIGNAL(activated(const QString &)), + this, SLOT(completed(const QString &))); + connect(m_cview->completionBox(), SIGNAL(highlighted(const QString &)), + this, SLOT(completed(const QString &))); + m_cview->completionBox()->setTabHandling(true); +#endif + +} + +void KexiInputTableEdit::setValueInternal(const QVariant& add, bool removeOld) +{ + QString text( m_textFormatter.valueToText(removeOld ? QVariant() : m_origValue, add.toString()) ); + if (text.isEmpty()) { + if (m_origValue.toString().isEmpty()) { + //we have to set NULL initial value: + m_lineedit->setText(QString::null); + } + } + else { + m_lineedit->setText(text); + } + +#if 0 +//move to end is better by default + m_cview->selectAll(); +#else +//js TODO: by default we're moving to the end of editor, ADD OPTION allowing "select all chars" + m_lineedit->end(false); +#endif + + if (!m_lineedit->validator()) { + QValidator *validator = new KexiDB::FieldValidator( + *field(), m_lineedit, "KexiInputTableEdit-validator"); + m_lineedit->setValidator( validator ); + } +} + +#if 0 +//moved to KexiTextFormatter +QString KexiInputTableEdit::valueToText(KexiDB::Field* field, const QVariant& value, const QString& add) +{ + QString text; //result + + if (field->isFPNumericType()) { +//! @todo precision! +//! @todo support 'g' format + text = QString::number(value.toDouble(), 'f', + QMAX(field->visibleDecimalPlaces(), 10)); //<-- 10 is quite good maximum for fractional digits + //! @todo add command line settings? + if (value.toDouble() == 0.0) { + text = add.isEmpty() ? "0" : add; //eat 0 + } + else { +//! @todo (js): get decimal places settings here... + QStringList sl = QStringList::split(".", text); + if (text.isEmpty()) { + //nothing + } + else if (sl.count()==2) { +// kdDebug() << "sl.count()=="<<sl.count()<< " " <<sl[0] << " | " << sl[1] << endl; + const QString sl1 = sl[1]; + int pos = sl1.length()-1; + if (pos>=1) { + for (;pos>=0 && sl1[pos]=='0';pos--) + ; + pos++; + } + if (pos>0) + text = sl[0] + m_decsym + sl1.left(pos); + else + text = sl[0]; //no decimal point + } + text += add; + } +/*moved to KexiDB::FieldValidator + if (setValidator && !m_lineedit->validator()) { + QValidator *validator = new KDoubleValidator(m_lineedit); + m_lineedit->setValidator( validator ); + }*/ + } + else { + text = value.toString(); + if (field->isIntegerType()) { + if (value.toInt() == 0) { + text = add; //eat 0 + } + else { + text += add; + } +/*moved to KexiDB::FieldValidator +//! @todo implement ranges here! + if (setValidator && !m_lineedit->validator()) { + QValidator *validator; + if (KexiDB::Field::BigInteger == field()->type()) { +//! @todo use field->isUnsigned() for KexiUtils::ULongLongValidator + validator = new KexiUtils::LongLongValidator(m_lineedit); + } + else { + validator = new KIntValidator(m_lineedit); + } + m_lineedit->setValidator( validator ); + }*/ + } + else {//default: text + text += add; + } + } + + return text; +} +#endif + +void KexiInputTableEdit::paintEvent ( QPaintEvent * /*e*/ ) +{ + QPainter p(this); + p.setPen( QPen( colorGroup().text() ) ); + p.drawRect( rect() ); +} + +void +KexiInputTableEdit::setRestrictedCompletion() +{ +#if 0 //js TODO +kdDebug() << "KexiInputTableEdit::setRestrictedCompletion()" << endl; +// KLineEdit *content = static_cast<KLineEdit*>(m_view); + if(m_cview->text().isEmpty()) + return; + + kdDebug() << "KexiInputTableEdit::setRestrictedCompletion(): something to do" << endl; + + m_cview->useGlobalKeyBindings(); + + QStringList newC; + QStringList::ConstIterator it, end( m_comp.constEnd() ); + for( it = m_comp.constBegin(); it != end; ++it) + { + if((*it).startsWith(m_cview->text())) + newC.append(*it); + } + m_cview->setCompletedItems(newC); +#endif +} + +void +KexiInputTableEdit::completed(const QString &s) +{ +// kdDebug() << "KexiInputTableEdit::completed(): " << s << endl; + m_lineedit->setText(s); +} + +bool KexiInputTableEdit::valueChanged() +{ + //not needed? if (m_lineedit->text()!=m_origValue.toString()) + //not needed? return true; + return KexiTableEdit::valueChanged(); +} + +bool KexiInputTableEdit::valueIsNull() +{ + return m_lineedit->text().isNull(); +} + +bool KexiInputTableEdit::valueIsEmpty() +{ + return !m_lineedit->text().isNull() && m_lineedit->text().isEmpty(); +} + +QVariant KexiInputTableEdit::value() +{ + if (field()->isFPNumericType()) {//==KexiDB::Field::Double || m_type==KexiDB::Field::Float) { + //! js @todo PRESERVE PRECISION! + QString txt = m_lineedit->text(); + if (m_decsym!=".") + txt = txt.replace(m_decsym,".");//convert back + bool ok; + const double result = txt.toDouble(&ok); + return ok ? QVariant(result) : QVariant(); + } + else if (field()->isIntegerType()) { +//! @todo check constraints + bool ok; + if (KexiDB::Field::BigInteger == field()->type()) { + if (field()->isUnsigned()) { + const Q_ULLONG result = m_lineedit->text().toULongLong(&ok); + return ok ? QVariant(result) : QVariant(); + } + else { + const Q_LLONG result = m_lineedit->text().toLongLong(&ok); + return ok ? QVariant(result) : QVariant(); + } + } + if (KexiDB::Field::Integer == field()->type()) { + if (field()->isUnsigned()) { + const uint result = m_lineedit->text().toUInt(&ok); + return ok ? QVariant(result) : QVariant(); + } + } + //default: signed int + const int result = m_lineedit->text().toInt(&ok); + return ok ? QVariant(result) : QVariant(); + } + //default: text + return m_lineedit->text(); +} + +void +KexiInputTableEdit::clear() +{ + m_lineedit->clear(); +} + +bool KexiInputTableEdit::cursorAtStart() +{ + return m_lineedit->cursorPosition()==0; +} + +bool KexiInputTableEdit::cursorAtEnd() +{ + return m_lineedit->cursorPosition()==(int)m_lineedit->text().length(); +} + +QSize KexiInputTableEdit::totalSize() +{ + if (!m_lineedit) + return size(); + return m_lineedit->size(); +} + +void KexiInputTableEdit::handleCopyAction(const QVariant& value, const QVariant& visibleValue) +{ + Q_UNUSED(visibleValue); +//! @todo handle rich text? + qApp->clipboard()->setText( m_textFormatter.valueToText(value, QString::null) ); +} + +void KexiInputTableEdit::handleAction(const QString& actionName) +{ + const bool alreadyVisible = m_lineedit->isVisible(); + + if (actionName=="edit_paste") { + if (!alreadyVisible) { //paste as the entire text if the cell was not in edit mode + emit editRequested(); + m_lineedit->clear(); + } + m_lineedit->paste(); + } + else if (actionName=="edit_cut") { +//! @todo handle rich text? + if (!alreadyVisible) { //cut the entire text if the cell was not in edit mode + emit editRequested(); + m_lineedit->selectAll(); + } + m_lineedit->cut(); + } +} + +bool KexiInputTableEdit::showToolTipIfNeeded(const QVariant& value, const QRect& rect, + const QFontMetrics& fm, bool focused) +{ + QString text( value.type()==QVariant::String ? value.toString() + : m_textFormatter.valueToText(value, QString::null) ); + QRect internalRect(rect); + internalRect.setLeft(rect.x()+leftMargin()); + internalRect.setWidth(internalRect.width()-rightMargin(focused)-2*3); + kexidbg << rect << " " << internalRect << " " << fm.width(text) << endl; + return fm.width(text) > internalRect.width(); +} + +void KexiInputTableEdit::moveCursorToEnd() +{ + m_lineedit->end(false/*!mark*/); +} + +void KexiInputTableEdit::moveCursorToStart() +{ + m_lineedit->home(false/*!mark*/); +} + +void KexiInputTableEdit::selectAll() +{ + m_lineedit->selectAll(); +} + +KEXI_CELLEDITOR_FACTORY_ITEM_IMPL(KexiInputEditorFactoryItem, KexiInputTableEdit) + +#include "kexiinputtableedit.moc" diff --git a/kexi/widget/tableview/kexiinputtableedit.h b/kexi/widget/tableview/kexiinputtableedit.h new file mode 100644 index 00000000..df770287 --- /dev/null +++ b/kexi/widget/tableview/kexiinputtableedit.h @@ -0,0 +1,126 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Lucijan Busch <lucijan@gmx.at> + Copyright (C) 2003-2007 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef KEXIINPUTTABLEEDIT_H +#define KEXIINPUTTABLEEDIT_H + +#include <klineedit.h> +#include <qvariant.h> + +#include "kexitableedit.h" +#include "kexicelleditorfactory.h" +#include "kexitextformatter.h" + +/*! @short General purpose cell editor using line edit widget. +*/ +class KEXIDATATABLE_EXPORT KexiInputTableEdit : public KexiTableEdit +{ + Q_OBJECT + + public: + KexiInputTableEdit(KexiTableViewColumn &column, QWidget *parent=0); + + virtual ~KexiInputTableEdit(); + +#if 0 +//moved to KexiTextFormatter + /*! \return text for \a value and \a field. + \a add is a text that should be added to the value if possible. + Used in setValueInternal(), by form widgets and for reporting/printing. */ + static QString valueToText(KexiDB::Field* field, const QVariant& value, const QString& add); +#endif + + virtual bool valueChanged(); + + //! \return true if editor's value is null (not empty) + virtual bool valueIsNull(); + + //! \return true if editor's value is empty (not null). + //! Only few field types can accept "EMPTY" property + //! (check this with KexiDB::Field::hasEmptyProperty()), + virtual bool valueIsEmpty(); + + virtual QVariant value(); + + virtual bool cursorAtStart(); + virtual bool cursorAtEnd(); + +// virtual bool eventFilter(QObject* watched, QEvent* e); +//js void end(bool mark); +//js void backspace(); + virtual void clear(); + + /*! \return total size of this editor, including any buttons, etc. (if present). */ + virtual QSize totalSize(); + + /*! Handles action having standard name \a actionName. + Action could be: "edit_cut", "edit_paste", etc. */ + virtual void handleAction(const QString& actionName); + + /*! Handles copy action for value. The \a value is copied to clipboard in format appropriate + for the editor's impementation, e.g. for image cell it can be a pixmap. + \a visibleValue is unused here. Reimplemented after KexiTableEdit. */ + virtual void handleCopyAction(const QVariant& value, const QVariant& visibleValue); + + /*! Shows a special tooltip for \a value if needed, i.e. if the value could not fit inside \a rect + for a given font metrics \a fm. + \return true a normal tooltip should be displayed (using QToolTip,) and false if + no tooltip should be displayed or a custom tooltip was displayed internally (not yet supported). + This implementation converts the value to text using valueToText() if \a calue is not string to see + whether it can fit inside the cell's \a rect. + If the cell is currentl focused (selected), \a focused is true. */ + virtual bool showToolTipIfNeeded(const QVariant& value, const QRect& rect, const QFontMetrics& fm, + bool focused); + + public slots: + //! Implemented for KexiDataItemInterface + virtual void moveCursorToEnd(); + + //! Implemented for KexiDataItemInterface + virtual void moveCursorToStart(); + + //! Implemented for KexiDataItemInterface + virtual void selectAll(); + + protected slots: + void setRestrictedCompletion(); + void completed(const QString &); + + protected: + //! initializes this editor with \a add value + virtual void setValueInternal(const QVariant& add, bool removeOld); + + void showHintButton(); + void init(); + virtual void paintEvent( QPaintEvent *e ); + + KexiTextFormatter m_textFormatter; + bool m_calculatedCell; + QString m_decsym; //! decimal symbol + QString m_origText; //! orig. Line Edit's text after conversion - for easy comparing + KLineEdit *m_lineedit; + + signals: + void hintClicked(); +}; + +KEXI_DECLARE_CELLEDITOR_FACTORY_ITEM(KexiInputEditorFactoryItem) + +#endif diff --git a/kexi/widget/tableview/kexitableedit.cpp b/kexi/widget/tableview/kexitableedit.cpp new file mode 100644 index 00000000..8c3f5612 --- /dev/null +++ b/kexi/widget/tableview/kexitableedit.cpp @@ -0,0 +1,237 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Peter Simonsson <psn@linux.se> + Copyright (C) 2003-2006 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "kexitableedit.h" +#include "kexidataawareobjectiface.h" +#include <kexidb/field.h> +#include <kexidb/utils.h> + +#include <qpalette.h> +#include <qpainter.h> + +#include <kglobal.h> +#include <klocale.h> +#include <kdebug.h> + +KexiTableEdit::KexiTableEdit(KexiTableViewColumn &column, QWidget* parent) +: QWidget(dynamic_cast<QScrollView*>(parent) ? dynamic_cast<QScrollView*>(parent)->viewport() : parent) + ,m_column(&column) +// ,m_field(&f) +// ,m_type(f.type()) //copied because the rest of code uses m_type + ,m_scrollView(dynamic_cast<QScrollView*>(parent)) + ,m_usesSelectedTextColor(true) + ,m_view(0) +// ,m_hasFocusableWidget(true) +// ,m_acceptEditorAfterDeleteContents(false) +{ + setPaletteBackgroundColor( palette().color(QPalette::Active, QColorGroup::Base) ); + installEventFilter(this); + + //margins + if (displayedField()->isFPNumericType()) { +#ifdef Q_WS_WIN + m_leftMargin = 0; +#else + m_leftMargin = 0; +#endif + } + else if (displayedField()->isIntegerType()) { +#ifdef Q_WS_WIN + m_leftMargin = 1; +#else + m_leftMargin = 0; +#endif + } + else {//default +#ifdef Q_WS_WIN + m_leftMargin = 5; +#else + m_leftMargin = 5; +#endif + } + + m_rightMargin = 0; + m_rightMarginWhenFocused = 0; +} + +KexiTableEdit::~KexiTableEdit() +{ +} + +KexiDB::Field *KexiTableEdit::displayedField() const +{ + if (m_column->visibleLookupColumnInfo) + return m_column->visibleLookupColumnInfo->field; //mainly for lookup field in KexiComboBoxTableEdit: + + return m_column->field(); //typical case +} + +void KexiTableEdit::setViewWidget(QWidget *v) +{ + m_view = v; + m_view->move(0,0); + m_view->installEventFilter(this); + setFocusProxy(m_view); +} + +void KexiTableEdit::moveChild( QWidget * child, int x, int y ) +{ + if (m_scrollView) + m_scrollView->moveChild(child, x, y); +} + +void KexiTableEdit::resize(int w, int h) +{ + QWidget::resize(w, h); + if (m_view) { + if (!layout()) { //if there is layout (eg. KexiInputTableEdit), resize is automatic + m_view->move(0,0); + m_view->resize(w, h); + } + } +} + +bool +KexiTableEdit::eventFilter(QObject* watched, QEvent* e) +{ +/* if (watched == m_view) { + if(e->type() == QEvent::KeyPress) { + QKeyEvent* ev = static_cast<QKeyEvent*>(e); +// if (ev->key()==Key_Tab) { + +// } + } + }*/ + + if(watched == this) + { + if(e->type() == QEvent::KeyPress) + { + QKeyEvent* ev = static_cast<QKeyEvent*>(e); + + if(ev->key() == Key_Escape) + { + return false; + } + } + else + { + return false; + } + } + return false; +// return QWidget::eventFilter(watched, e); +} + +void KexiTableEdit::paintFocusBorders( QPainter *p, QVariant &, int x, int y, int w, int h ) +{ + p->drawRect(x, y, w, h); +} + +void KexiTableEdit::setupContents( QPainter *p, bool focused, const QVariant& val, + QString &txt, int &align, int &/*x*/, int &y_offset, int &w, int &h ) +{ + Q_UNUSED(p); + Q_UNUSED(focused); + Q_UNUSED(h); + KexiDB::Field *realField = displayedField(); + +#ifdef Q_WS_WIN +// x = 1; + y_offset = -1; +#else +// x = 1; + y_offset = 0; +#endif + + if (realField->isFPNumericType()) { +//! @todo ADD OPTION to displaying NULL VALUES as e.g. "(null)" + if (!val.isNull()) { + txt = KexiDB::formatNumberForVisibleDecimalPlaces( + val.toDouble(), realField->visibleDecimalPlaces()); + } + w -= 6; + align |= AlignRight; + } + else if (realField->isIntegerType()) { + Q_LLONG num = val.toLongLong(); + w -= 6; + align |= AlignRight; + if (!val.isNull()) + txt = QString::number(num); + } + else {//default: + if (!val.isNull()) { + txt = val.toString(); + } + align |= AlignLeft; + } +} + +void KexiTableEdit::paintSelectionBackground( QPainter *p, bool /*focused*/, + const QString& txt, int align, int x, int y_offset, int w, int h, const QColor& fillColor, + const QFontMetrics &fm, bool readOnly, bool fullRowSelection ) +{ + if (!readOnly && !fullRowSelection && !txt.isEmpty()) { + QRect bound=fm.boundingRect(x, y_offset, w - (x+x), h, align, txt); + bound.setY(0); + bound.setWidth( QMIN( bound.width()+2, w - (x+x)+1 ) ); + if (align & Qt::AlignLeft) { + bound.setX(bound.x()-1); + } + else if (align & Qt::AlignRight) { + bound.moveLeft( w - bound.width() ); //move to left, if too wide + } +//TODO align center + bound.setHeight(h-1); + p->fillRect(bound, fillColor); + } + else if (fullRowSelection) { + p->fillRect(0, 0, w, h, fillColor); + } +} + +int KexiTableEdit::widthForValue( QVariant &val, const QFontMetrics &fm ) +{ + return fm.width( val.toString() ); +} + +void KexiTableEdit::repaintRelatedCell() +{ + if (dynamic_cast<KexiDataAwareObjectInterface*>(m_scrollView)) + dynamic_cast<KexiDataAwareObjectInterface*>(m_scrollView)->updateCurrentCell(); +} + +bool KexiTableEdit::showToolTipIfNeeded(const QVariant& value, const QRect& rect, const QFontMetrics& fm, + bool focused) +{ + Q_UNUSED(value); + Q_UNUSED(rect); + Q_UNUSED(fm); + Q_UNUSED(focused); + return false; +} + +int KexiTableEdit::rightMargin(bool focused) const +{ + return focused ? m_rightMarginWhenFocused : m_rightMargin; +} + +#include "kexitableedit.moc" diff --git a/kexi/widget/tableview/kexitableedit.h b/kexi/widget/tableview/kexitableedit.h new file mode 100644 index 00000000..ef38a11f --- /dev/null +++ b/kexi/widget/tableview/kexitableedit.h @@ -0,0 +1,233 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Peter Simonsson <psn@linux.se> + Copyright (C) 2003-2006 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef _KEXITABLEEDIT_H_ +#define _KEXITABLEEDIT_H_ + +#include <kexidataiteminterface.h> + +#include <qvariant.h> +#include <qscrollview.h> + +#include "kexitableviewdata.h" + +namespace KexiDB { + class Field; + class QueryColumnInfo; +} + +/*! @short Abstract class for a cell editor. + Handles cell painting and displaying the editor widget. +*/ +class KEXIDATATABLE_EXPORT KexiTableEdit : public QWidget, public KexiDataItemInterface +{ + Q_OBJECT + + public: + KexiTableEdit(KexiTableViewColumn &column, QWidget* parent = 0); + + virtual ~KexiTableEdit(); + + //! Implemented for KexiDataItemInterface. + //! \return field information for this item + virtual KexiDB::Field *field() const { return m_column->field(); } + + /*! A rich field information for db-aware data. + For not-db-aware data it is always 0 (use field() instead. */ + virtual KexiDB::QueryColumnInfo *columnInfo() const { return m_column->columnInfo; } + + //! Implemented for KexiDataItemInterface. + //! Does nothing because instead KexiTableViewColumn is used to get field's schema. + virtual void setColumnInfo(KexiDB::QueryColumnInfo *) { } + + //! \return column information for this item + //! (extended information, comparing to field()). + inline KexiTableViewColumn *column() const { return m_column; } + + /*! \return displayed field. This is equal to field() in typical case but can return a different field + definition if the column contains a lookup field. This distiction is especially used for + displaying data dependent on the type and specifics of the field definition + (e.g. text type versus integer type). Note that to compute the editor's value + we still use field(). */ + KexiDB::Field *displayedField() const; + + /*! Reimplemented: resizes a view(). */ + virtual void resize(int w, int h); + + /*! \return the view widget of this editor, e.g. line edit widget. */ + virtual QWidget* widget() { return m_view; } + + /*! Hides item's widget, if available. */ + inline virtual void hideWidget() { hide(); } + + /*! Shows item's widget, if available. */ + inline virtual void showWidget() { show(); } + + /*! Paints a border for the cell described by \a x, \a y, \a w, \a h on \a p painter. + The cell's value is \a val (may be useful if you want to reimplement this method). + */ + virtual void paintFocusBorders( QPainter *p, QVariant &cal, int x, int y, int w, int h ); + + /*! For reimplementation. + Sets up and paints cell's contents using context of \a val value. + \a focused is true if the cell is focused. \a align is set using Qt::AlignmentFlags. + Some additional things may be painted using \a p, + but it is not needed to paint the text (this is done automatically outside of this method). + + Before calling, \a x, \a y_offset, \a w, \a h parameters are initialized, + but you can tune these values depending on the context. + You should set \a txt to a text representation of \a val, + otherwise no text will be painted. + + \a p can be 0 - in this case no painting should be performed, becasue caller only expects + that \a x, \a y_offset, \a w, \a h, \a txt parameters are tuned, if needed. + \a p painter's pen is set to foreground color (usually black) that should be used to paint + foreground information, if needed. For example boolean editor widget paints + a rectangle using this color. */ + virtual void setupContents( QPainter *p, bool focused, const QVariant& val, + QString &txt, int &align, int &x, int &y_offset, int &w, int &h ); + + /*! \return true if "selected text" color should be used to paint contents of the editor. + True by default. It's false e.g. in boolean editor, where no selection is painted + using paintSelectionBackground(). + This flag is set in editor's constructor and checked in KexiTableView::paintCell(). + Depending on it, appropriate ("text" or "selected text" color is set for painter) before + setupContents() is called. */ + bool usesSelectedTextColor() const { return m_usesSelectedTextColor; } + + /*! For reimplementation. + Paints selection's background using \a p. Most parameters are similar to these from + setupContents(). */ + virtual void paintSelectionBackground( QPainter *p, bool focused, const QString& txt, + int align, int x, int y_offset, int w, int h, const QColor& fillColor, + const QFontMetrics &fm, bool readOnly, bool fullRowSelection ); + + /*! Sometimes, editor can contain non-standard margin, for example combobox editor contains + dropdown button at the right side. \return left margin's size; + 0 by default. For reimplementation. */ + int leftMargin() const { return m_leftMargin; } + + /*! Sometimes, editor can contain non-standard margin, for example combobox editor contains + dropdown button at the right side. THe dropdown button's width is counted only if \a focused is true. + \return right margin's size; 0 by default. For reimplementation. */ + int rightMargin(bool focused) const; + + /*! Handles \a ke key event that came over the column that is bound to this editor. + For implementation: true should be returned if \a ke should be accepted. + If \a editorActive is true, this editor is currently active, i.e. the table view is in edit mode. + By default false is returned. */ + virtual bool handleKeyPress( QKeyEvent* ke, bool editorActive ) { + Q_UNUSED(ke); Q_UNUSED(editorActive); return false; } + + /*! Handles double click request coming from the table view. + \return true if it has been consumed. + Reimplemented in KexiBlobTableEdit (to execute "insert file" action. */ + virtual bool handleDoubleClick() { return false; } + + /*! Handles copy action for value. The \a value is copied to clipboard in format appropriate + for the editor's impementation, e.g. for image cell it can be a pixmap. + For a special case (combo box), \a visibleValue can be provided, + so it can be copied to the clipboard instead of unreadable \a value. + For reimplementation. */ + virtual void handleCopyAction(const QVariant& value, const QVariant& visibleValue) = 0; + + /*! \return width of \a value. For the default implementation \a val is converted to a string + and width of this string is returned. */ + virtual int widthForValue( QVariant &val, const QFontMetrics &fm ); + + /*! \return total size of this editor, including any buttons, etc. (if present). + Reimpelment this if you want to return more appropriate size. This impelmentation just + returns QWidget::size(). */ + virtual QSize totalSize() { return QWidget::size(); } + + /*! Shows a special tooltip for \a value if needed, i.e. if the value could not fit inside \a rect + for a given font metrics \a fm. + \return true a normal tooltip should be displayed (using QToolTip,) and false if + no tooltip should be displayed or a custom tooltip was displayed internally (not yet supported). + Default implementation does nothing and returns false. + If the cell is currentl focused (selected), \a focused is true. */ + virtual bool showToolTipIfNeeded(const QVariant& value, const QRect& rect, const QFontMetrics& fm, + bool focused); + + /*! Created internal editor for this editor is needed. This method is only implemented + in KexiComboBoxTableEdit since it's visible value differs from internal value, + so a different KexiTableEdit object is used to displaying the data. */ + virtual void createInternalEditor(KexiDB::QuerySchema& schema) { Q_UNUSED(schema); } + + signals: + void editRequested(); + void cancelRequested(); + void acceptRequested(); + + protected: + virtual bool eventFilter(QObject* watched, QEvent* e); + + /*! Sets \a v as view widget for this editor. The view will be assigned as focus proxy + for the editor, its events will be filtered, it will be resized when neede, and so on. */ + void setViewWidget(QWidget *v); + + /*! Moves child widget within the viewport if the parent is scrollview (otherwise does nothing). + Use this for child widgets that are outside of this editor widget, instead of calling QWidget::move(). */ + void moveChild( QWidget * child, int x, int y ); + + /*! Allows to force redrawing the related cell by the editor itself. Usable when the editor is not + displayed by a QWidget but rather by table view cell itself, for example KexiBlobTableEdit. */ + void repaintRelatedCell(); + + KexiTableViewColumn *m_column; + int m_leftMargin; + int m_rightMargin, m_rightMarginWhenFocused; + QScrollView* m_scrollView; //!< may be 0 if the parent is not a scrollview + bool m_usesSelectedTextColor : 1; //!< set in ctor, @see usesSelectedTextColor() + + private: + QWidget* m_view; +}; + +//! Declaration of cell editor factory +#define KEXI_DECLARE_CELLEDITOR_FACTORY_ITEM(factoryclassname) \ + class factoryclassname : public KexiCellEditorFactoryItem \ + { \ + public: \ + factoryclassname(); \ + virtual ~factoryclassname(); \ + \ + protected: \ + virtual KexiTableEdit* createEditor(KexiTableViewColumn &column, QWidget* parent = 0); \ + }; + +//! Implementation of cell editor factory +#define KEXI_CELLEDITOR_FACTORY_ITEM_IMPL(factoryclassname, itemclassname) \ +factoryclassname::factoryclassname() \ + : KexiCellEditorFactoryItem() \ +{ \ + m_className = "" #itemclassname ""; \ +} \ +\ +factoryclassname::~factoryclassname() \ +{} \ +\ +KexiTableEdit* factoryclassname::createEditor( \ + KexiTableViewColumn &column, QWidget* parent) \ +{ \ + return new itemclassname(column, parent); \ +} + +#endif diff --git a/kexi/widget/tableview/kexitableitem.cpp b/kexi/widget/tableview/kexitableitem.cpp new file mode 100644 index 00000000..e1669a35 --- /dev/null +++ b/kexi/widget/tableview/kexitableitem.cpp @@ -0,0 +1,62 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Lucijan Busch <lucijan@gmx.at> + Copyright (C) 2003 Daniel Molkentin <molkentin@kde.org> + Copyright (C) 2003 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + + Original Author: Till Busch <till@bux.at> + Original Project: buX (www.bux.at) +*/ + +#include "kexitableitem.h" + +#include <kdebug.h> + +KexiTableItem::KexiTableItem(int numCols) +: KexiTableItemBase(numCols) +{ +//js m_userData=0; +//js m_columns.resize(numCols); +//js m_insertItem = false; +//js m_pTable = 0; +} + +KexiTableItem::~KexiTableItem() +{ +} + +void +KexiTableItem::init(int numCols) +{ + clear(); + resize(numCols); +} + +void +KexiTableItem::clearValues() +{ + init(count()); +} + +void +KexiTableItem::debug() const +{ + QString s = QString("KexiTableItem (%1 items)").arg(size()); + for (uint i = 0; i < size(); i++) + s.append( QString::number(i)+":"+at(i).toString()+" " ); + kexidbg << s << endl; +} diff --git a/kexi/widget/tableview/kexitableitem.h b/kexi/widget/tableview/kexitableitem.h new file mode 100644 index 00000000..00074e82 --- /dev/null +++ b/kexi/widget/tableview/kexitableitem.h @@ -0,0 +1,58 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Lucijan Busch <lucijan@gmx.at> + Copyright (C) 2003 Daniel Molkentin <molkentin@kde.org> + Copyright (C) 2003 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + + Original Author: Till Busch <till@bux.at> + Original Project: buX (www.bux.at) +*/ + +#ifndef KEXITABLEITEM_H +#define KEXITABLEITEM_H + +#include <qstring.h> +#include <qdatetime.h> +#include <qvaluevector.h> +#include <qvariant.h> + +#include <kexidb/connection.h> + +typedef KexiDB::RowData KexiTableItemBase; + +class KEXIDATATABLE_EXPORT KexiTableItem : public KexiTableItemBase +{ + public: + ~KexiTableItem(); + + /*! Clears existing column values and inits new \a numCols + columns with empty values. ist of values is resized to \a numCols. */ + void init(int numCols); + + /*! Clears existing column values, current number of columns is preserved. */ + void clearValues(); + + /*! Prints debug string for this item. */ + void debug() const; + + protected: + KexiTableItem(int numCols); + + friend class KexiTableViewData; +}; + +#endif diff --git a/kexi/widget/tableview/kexitableview.cpp b/kexi/widget/tableview/kexitableview.cpp new file mode 100644 index 00000000..108b1696 --- /dev/null +++ b/kexi/widget/tableview/kexitableview.cpp @@ -0,0 +1,2607 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Till Busch <till@bux.at> + Copyright (C) 2003 Lucijan Busch <lucijan@gmx.at> + Copyright (C) 2003 Daniel Molkentin <molkentin@kde.org> + Copyright (C) 2003 Joseph Wenninger <jowenn@kde.org> + Copyright (C) 2003-2006 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + + Original Author: Till Busch <till@bux.at> + Original Project: buX (www.bux.at) +*/ + +#include <qpainter.h> +#include <qkeycode.h> +#include <qlineedit.h> +#include <qcombobox.h> +#include <qwmatrix.h> +#include <qtimer.h> +#include <qpopupmenu.h> +#include <qcursor.h> +#include <qstyle.h> +#include <qlayout.h> +#include <qlabel.h> +#include <qwhatsthis.h> + +#include <kglobal.h> +#include <klocale.h> +#include <kdebug.h> +#include <kapplication.h> +#include <kiconloader.h> +#include <kmessagebox.h> + +#ifndef KEXI_NO_PRINT +# include <kprinter.h> +#endif + +#include "kexitableview.h" +#include <kexiutils/utils.h> +#include <kexiutils/validator.h> + +#include "kexicelleditorfactory.h" +#include "kexitableviewheader.h" +#include "kexitableview_p.h" +#include <widget/utils/kexirecordmarker.h> +#include <widget/utils/kexidisplayutils.h> +#include <kexidb/cursor.h> + +KexiTableView::Appearance::Appearance(QWidget *widget) + : alternateBackgroundColor( KGlobalSettings::alternateBackgroundColor() ) +{ + //set defaults + if (qApp) { + QPalette p = widget ? widget->palette() : qApp->palette(); + baseColor = p.active().base(); + textColor = p.active().text(); + borderColor = QColor(200,200,200); + emptyAreaColor = p.active().color(QColorGroup::Base); + rowHighlightingColor = KexiUtils::blendedColors(p.active().highlight(), baseColor, 33, 66); + rowMouseOverHighlightingColor = KexiUtils::blendedColors(p.active().highlight(), baseColor, 10, 90); + rowMouseOverAlternateHighlightingColor = KexiUtils::blendedColors(p.active().highlight(), alternateBackgroundColor, 10, 90); + rowHighlightingTextColor = textColor; + rowMouseOverHighlightingTextColor = textColor; + } + backgroundAltering = true; + rowMouseOverHighlightingEnabled = true; + rowHighlightingEnabled = true; + persistentSelections = true; + navigatorEnabled = true; + fullRowSelection = false; + gridEnabled = true; +} + +//----------------------------------------- + +//! @internal A special What's This class displaying information about a given column +class KexiTableView::WhatsThis : public QWhatsThis +{ + public: + WhatsThis(KexiTableView* tv) : QWhatsThis(tv), m_tv(tv) + { + Q_ASSERT(tv); + } + virtual ~WhatsThis() + { + } + virtual QString text( const QPoint & pos) + { + const int leftMargin = m_tv->verticalHeaderVisible() ? m_tv->verticalHeader()->width() : 0; + //const int topMargin = m_tv->horizontalHeaderVisible() ? m_tv->d->pTopHeader->height() : 0; + //const int bottomMargin = m_tv->d->appearance.navigatorEnabled ? m_tv->m_navPanel->height() : 0; + if (KexiUtils::hasParent(m_tv->verticalHeader(), m_tv->childAt(pos))) { + return i18n("Contains a pointer to the currently selected row"); + } + else if (KexiUtils::hasParent(m_tv->m_navPanel, m_tv->childAt(pos))) { + return i18n("Row navigator"); +// return QWhatsThis::textFor(m_tv->m_navPanel, QPoint( pos.x(), pos.y() - m_tv->height() + bottomMargin )); + } + KexiDB::Field *f = m_tv->field( m_tv->columnAt(pos.x()-leftMargin) ); + if (!f) + return QString::null; + return f->description().isEmpty() ? f->captionOrName() : f->description(); + } + protected: + KexiTableView *m_tv; +}; + +//----------------------------------------- + +KexiTableViewCellToolTip::KexiTableViewCellToolTip( KexiTableView * tableView ) + : QToolTip(tableView->viewport()) + , m_tableView(tableView) +{ +} + +KexiTableViewCellToolTip::~KexiTableViewCellToolTip() +{ + remove(parentWidget()); +} + +void KexiTableViewCellToolTip::maybeTip( const QPoint & p ) +{ + const QPoint cp( m_tableView->viewportToContents( p ) ); + const int row = m_tableView->rowAt( cp.y(), true/*ignoreEnd*/ ); + const int col = m_tableView->columnAt( cp.x() ); + + //show tooltip if needed + if (col>=0 && row>=0) { + KexiTableEdit *editor = m_tableView->tableEditorWidget( col ); + const bool insertRowSelected = m_tableView->isInsertingEnabled() && row==m_tableView->rows(); + KexiTableItem *item = insertRowSelected ? m_tableView->m_insertItem : m_tableView->itemAt( row ); + if (editor && item && (col < (int)item->count())) { + int w = m_tableView->columnWidth( col ); + int h = m_tableView->rowHeight(); + int x = 0; + int y_offset = 0; + int align = Qt::SingleLine | Qt::AlignVCenter; + QString txtValue; + QVariant cellValue; + KexiTableViewColumn *tvcol = m_tableView->column(col); + if (!m_tableView->getVisibleLookupValue(cellValue, editor, item, tvcol)) + cellValue = insertRowSelected ? editor->displayedField()->defaultValue() : item->at(col); //display default value if available + const bool focused = m_tableView->selectedItem() == item && col == m_tableView->currentColumn(); + editor->setupContents( 0, focused, cellValue, txtValue, align, x, y_offset, w, h ); + QRect realRect(m_tableView->columnPos(col)-m_tableView->contentsX(), + m_tableView->rowPos(row)-m_tableView->contentsY(), w, h); //m_tableView->cellGeometry( row, col )); + if (editor->showToolTipIfNeeded( + txtValue.isEmpty() ? item->at(col) : QVariant(txtValue), + realRect, m_tableView->fontMetrics(), focused)) + { + QString squeezedTxtValue; + if (txtValue.length() > 50) + squeezedTxtValue = txtValue.left(100) + "..."; + else + squeezedTxtValue = txtValue; + tip( realRect, squeezedTxtValue ); + } + } + } +} + +//----------------------------------------- + +KexiTableView::KexiTableView(KexiTableViewData* data, QWidget* parent, const char* name) +: QScrollView(parent, name, /*Qt::WRepaintNoErase | */Qt::WStaticContents /*| Qt::WResizeNoErase*/) +, KexiRecordNavigatorHandler() +, KexiSharedActionClient() +, KexiDataAwareObjectInterface() +{ +//not needed KexiTableView::initCellEditorFactories(); + + d = new KexiTableViewPrivate(this); + + connect( kapp, SIGNAL( settingsChanged(int) ), SLOT( slotSettingsChanged(int) ) ); + slotSettingsChanged(KApplication::SETTINGS_SHORTCUTS); + + m_data = new KexiTableViewData(); //to prevent crash because m_data==0 + m_owner = true; //-this will be deleted if needed + + setResizePolicy(Manual); + viewport()->setBackgroundMode(Qt::NoBackground); +// viewport()->setFocusPolicy(StrongFocus); + viewport()->setFocusPolicy(WheelFocus); + setFocusPolicy(WheelFocus); //<--- !!!!! important (was NoFocus), + // otherwise QApplication::setActiveWindow() won't activate + // this widget when needed! +// setFocusProxy(viewport()); + viewport()->installEventFilter(this); + + //setup colors defaults + setBackgroundMode(Qt::PaletteBackground); +// setEmptyAreaColor(d->appearance.baseColor);//palette().active().color(QColorGroup::Base)); + +// d->baseColor = colorGroup().base(); +// d->textColor = colorGroup().text(); + +// d->altColor = KGlobalSettings::alternateBackgroundColor(); +// d->grayColor = QColor(200,200,200); + d->diagonalGrayPattern = QBrush(d->appearance.borderColor, Qt::BDiagPattern); + + setLineWidth(1); + horizontalScrollBar()->installEventFilter(this); + horizontalScrollBar()->raise(); + verticalScrollBar()->raise(); + + //context menu + m_popupMenu = new KPopupMenu(this, "contextMenu"); +#if 0 //moved to mainwindow's actions + d->menu_id_addRecord = m_popupMenu->insertItem(i18n("Add Record"), this, SLOT(addRecord()), Qt::CTRL+Qt::Key_Insert); + d->menu_id_removeRecord = m_popupMenu->insertItem( + kapp->iconLoader()->loadIcon("button_cancel", KIcon::Small), + i18n("Remove Record"), this, SLOT(removeRecord()), Qt::CTRL+Qt::Key_Delete); +#endif + +#ifdef Q_WS_WIN + d->rowHeight = fontMetrics().lineSpacing() + 4; +#else + d->rowHeight = fontMetrics().lineSpacing() + 1; +#endif + + if(d->rowHeight < 17) + d->rowHeight = 17; + + d->pUpdateTimer = new QTimer(this); + +// setMargins(14, fontMetrics().height() + 4, 0, 0); + + // Create headers + m_horizontalHeader = new KexiTableViewHeader(this, "topHeader"); + m_horizontalHeader->setSelectionBackgroundColor( palette().active().highlight() ); + m_horizontalHeader->setOrientation(Qt::Horizontal); + m_horizontalHeader->setTracking(false); + m_horizontalHeader->setMovingEnabled(false); + connect(m_horizontalHeader, SIGNAL(sizeChange(int,int,int)), this, SLOT(slotTopHeaderSizeChange(int,int,int))); + + m_verticalHeader = new KexiRecordMarker(this, "rm"); + m_verticalHeader->setSelectionBackgroundColor( palette().active().highlight() ); + m_verticalHeader->setCellHeight(d->rowHeight); +// m_verticalHeader->setFixedWidth(d->rowHeight); + m_verticalHeader->setCurrentRow(-1); + + setMargins( + QMIN(m_horizontalHeader->sizeHint().height(), d->rowHeight), + m_horizontalHeader->sizeHint().height(), 0, 0); + + setupNavigator(); + +// setMinimumHeight(horizontalScrollBar()->height() + d->rowHeight + topMargin()); + +// navPanelLyr->addStretch(25); +// enableClipper(true); + + if (data) + setData( data ); + +#if 0//(js) doesn't work! + d->scrollTimer = new QTimer(this); + connect(d->scrollTimer, SIGNAL(timeout()), this, SLOT(slotAutoScroll())); +#endif + +// setBackgroundAltering(true); +// setFullRowSelectionEnabled(false); + + setAcceptDrops(true); + viewport()->setAcceptDrops(true); + + // Connect header, table and scrollbars + connect(horizontalScrollBar(), SIGNAL(valueChanged(int)), m_horizontalHeader, SLOT(setOffset(int))); + connect(verticalScrollBar(), SIGNAL(valueChanged(int)), m_verticalHeader, SLOT(setOffset(int))); + connect(m_horizontalHeader, SIGNAL(sizeChange(int, int, int)), this, SLOT(slotColumnWidthChanged(int, int, int))); + connect(m_horizontalHeader, SIGNAL(sectionHandleDoubleClicked(int)), this, SLOT(slotSectionHandleDoubleClicked(int))); + connect(m_horizontalHeader, SIGNAL(clicked(int)), this, SLOT(sortColumnInternal(int))); + + connect(d->pUpdateTimer, SIGNAL(timeout()), this, SLOT(slotUpdate())); + +// horizontalScrollBar()->show(); + updateScrollBars(); +// resize(sizeHint()); +// updateContents(); +// setMinimumHeight(horizontalScrollBar()->height() + d->rowHeight + topMargin()); + +//TMP +//setVerticalHeaderVisible(false); +//setHorizontalHeaderVisible(false); + +//will be updated by setAppearance: updateFonts(); + setAppearance(d->appearance); //refresh + + d->cellToolTip = new KexiTableViewCellToolTip(this); + new WhatsThis(this); +} + +KexiTableView::~KexiTableView() +{ + cancelRowEdit(); + + KexiTableViewData *data = m_data; + m_data = 0; + if (m_owner) { + if (data) + data->deleteLater(); + } + delete d; +} + +void KexiTableView::clearVariables() +{ + KexiDataAwareObjectInterface::clearVariables(); + d->clearVariables(); +} + +/*void KexiTableView::initActions(KActionCollection *ac) +{ + emit reloadActions(ac); +}*/ + +void KexiTableView::setupNavigator() +{ + updateScrollBars(); + + m_navPanel = new KexiRecordNavigator(this, leftMargin(), "navPanel"); + m_navPanel->setRecordHandler(this); + m_navPanel->setSizePolicy(QSizePolicy::Minimum,QSizePolicy::Preferred); +} + +void KexiTableView::initDataContents() +{ + updateWidgetContentsSize(); + + KexiDataAwareObjectInterface::initDataContents(); + + m_navPanel->showEditingIndicator(false); +} + +void KexiTableView::addHeaderColumn(const QString& caption, const QString& description, + const QIconSet& icon, int width) +{ + const int nr = m_horizontalHeader->count(); + if (icon.isNull()) + m_horizontalHeader->addLabel(caption, width); + else + m_horizontalHeader->addLabel(icon, caption, width); + + if (!description.isEmpty()) + m_horizontalHeader->setToolTip(nr, description); +} + +void KexiTableView::updateWidgetContentsSize() +{ + QSize s(tableSize()); + resizeContents(s.width(), s.height()); +} + +void KexiTableView::slotRowsDeleted( const QValueList<int> &rows ) +{ + viewport()->repaint(); + updateWidgetContentsSize(); + setCursorPosition(QMAX(0, (int)m_curRow - (int)rows.count()), -1, true); +} + + +/*void KexiTableView::addDropFilter(const QString &filter) +{ + d->dropFilters.append(filter); + viewport()->setAcceptDrops(true); +}*/ + +void KexiTableView::setFont( const QFont &font ) +{ + QScrollView::setFont(font); + updateFonts(true); +} + +void KexiTableView::updateFonts(bool repaint) +{ +#ifdef Q_WS_WIN + d->rowHeight = fontMetrics().lineSpacing() + 4; +#else + d->rowHeight = fontMetrics().lineSpacing() + 1; +#endif + if (d->appearance.fullRowSelection) { + d->rowHeight -= 1; + } + if(d->rowHeight < 17) + d->rowHeight = 17; +// if(d->rowHeight < 22) +// d->rowHeight = 22; + setMargins( + QMIN(m_horizontalHeader->sizeHint().height(), d->rowHeight), + m_horizontalHeader->sizeHint().height(), 0, 0); +// setMargins(14, d->rowHeight, 0, 0); + m_verticalHeader->setCellHeight(d->rowHeight); + + KexiDisplayUtils::initDisplayForAutonumberSign(d->autonumberSignDisplayParameters, this); + KexiDisplayUtils::initDisplayForDefaultValue(d->defaultValueDisplayParameters, this); + + if (repaint) + updateContents(); +} + +void KexiTableView::updateAllVisibleRowsBelow(int row) +{ + //get last visible row + int r = rowAt(clipper()->height()+contentsY()); + if (r==-1) { + r = rows()+1+(isInsertingEnabled()?1:0); + } + //update all visible rows below + int leftcol = m_horizontalHeader->sectionAt( m_horizontalHeader->offset() ); +// int row = m_curRow; + updateContents( columnPos( leftcol ), rowPos(row), + clipper()->width(), clipper()->height() - (rowPos(row) - contentsY()) ); +} + +void KexiTableView::clearColumnsInternal(bool /*repaint*/) +{ + while(m_horizontalHeader->count()>0) + m_horizontalHeader->removeLabel(0); +} + +void KexiTableView::slotUpdate() +{ +// kdDebug(44021) << " KexiTableView::slotUpdate() -- " << endl; +// QSize s(tableSize()); +// viewport()->setUpdatesEnabled(false); +/// resizeContents(s.width(), s.height()); +// viewport()->setUpdatesEnabled(true); + + updateContents(); + updateScrollBars(); + if (m_navPanel) + m_navPanel->updateGeometry(leftMargin()); +// updateNavPanelGeometry(); + + updateWidgetContentsSize(); +// updateContents(0, contentsY()+clipper()->height()-2*d->rowHeight, clipper()->width(), d->rowHeight*3); + + //updateGeometries(); +// updateContents(0, 0, viewport()->width(), contentsHeight()); +// updateGeometries(); +} + +int KexiTableView::currentLocalSortingOrder() const +{ + if (m_horizontalHeader->sortIndicatorSection()==-1) + return 0; + return (m_horizontalHeader->sortIndicatorOrder() == Qt::Ascending) ? 1 : -1; +} + +void KexiTableView::setLocalSortingOrder(int col, int order) +{ + if (order == 0) + col = -1; + if (col>=0) + m_horizontalHeader->setSortIndicator(col, (order==1) ? Qt::Ascending : Qt::Descending); +} + +int KexiTableView::currentLocalSortColumn() const +{ + return m_horizontalHeader->sortIndicatorSection(); +} + +void KexiTableView::updateGUIAfterSorting() +{ + int cw = columnWidth(m_curCol); + int rh = rowHeight(); + +// m_verticalHeader->setCurrentRow(m_curRow); + center(columnPos(m_curCol) + cw / 2, rowPos(m_curRow) + rh / 2); +// updateCell(oldRow, m_curCol); +// updateCell(m_curRow, m_curCol); +// slotUpdate(); + + updateContents(); +// d->pUpdateTimer->start(1,true); +} + +QSizePolicy KexiTableView::sizePolicy() const +{ + // this widget is expandable + return QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); +} + +QSize KexiTableView::sizeHint() const +{ + const QSize &ts = tableSize(); + int w = QMAX( ts.width() + leftMargin()+ verticalScrollBar()->sizeHint().width() + 2*2, + (m_navPanel->isVisible() ? m_navPanel->width() : 0) ); + int h = QMAX( ts.height()+topMargin()+horizontalScrollBar()->sizeHint().height(), + minimumSizeHint().height() ); + w = QMIN( w, qApp->desktop()->width()*3/4 ); //stretch + h = QMIN( h, qApp->desktop()->height()*3/4 ); //stretch + +// kexidbg << "KexiTableView::sizeHint()= " <<w <<", " <<h << endl; + + return QSize(w, h); + /*QSize( + QMAX( ts.width() + leftMargin() + 2*2, (m_navPanel ? m_navPanel->width() : 0) ), + //+ QMIN(m_verticalHeader->width(),d->rowHeight) + margin()*2, + QMAX( ts.height()+topMargin()+horizontalScrollBar()->sizeHint().height(), + minimumSizeHint().height() ) + );*/ +// QMAX(ts.height() + topMargin(), minimumSizeHint().height()) ); +} + +QSize KexiTableView::minimumSizeHint() const +{ + return QSize( + leftMargin() + ((columns()>0)?columnWidth(0):KEXI_DEFAULT_DATA_COLUMN_WIDTH) + 2*2, + d->rowHeight*5/2 + topMargin() + (m_navPanel && m_navPanel->isVisible() ? m_navPanel->height() : 0) + ); +} + +void KexiTableView::createBuffer(int width, int height) +{ + if(!d->pBufferPm) + d->pBufferPm = new QPixmap(width, height); + else + if(d->pBufferPm->width() < width || d->pBufferPm->height() < height) + d->pBufferPm->resize(width, height); +// d->pBufferPm->fill(); +} + +//internal +inline void KexiTableView::paintRow(KexiTableItem *item, + QPainter *pb, int r, int rowp, int cx, int cy, + int colfirst, int collast, int maxwc) +{ + if (!item) + return; + // Go through the columns in the row r + // if we know from where to where, go through [colfirst, collast], + // else go through all of them + if (colfirst==-1) + colfirst=0; + if (collast==-1) + collast=columns()-1; + + int transly = rowp-cy; + + if (d->appearance.rowHighlightingEnabled && r == m_curRow && !d->appearance.fullRowSelection) { + pb->fillRect(0, transly, maxwc, d->rowHeight, d->appearance.rowHighlightingColor); + } + else if (d->appearance.rowMouseOverHighlightingEnabled && r == d->highlightedRow) { + if(d->appearance.backgroundAltering && (r%2 != 0)) + pb->fillRect(0, transly, maxwc, d->rowHeight, d->appearance.rowMouseOverAlternateHighlightingColor); + else + pb->fillRect(0, transly, maxwc, d->rowHeight, d->appearance.rowMouseOverHighlightingColor); + } + else { + if(d->appearance.backgroundAltering && (r%2 != 0)) + pb->fillRect(0, transly, maxwc, d->rowHeight, d->appearance.alternateBackgroundColor); + else + pb->fillRect(0, transly, maxwc, d->rowHeight, d->appearance.baseColor); + } + + for(int c = colfirst; c <= collast; c++) + { + // get position and width of column c + int colp = columnPos(c); + if (colp==-1) + continue; //invisible column? + int colw = columnWidth(c); + int translx = colp-cx; + + // Translate painter and draw the cell + pb->saveWorldMatrix(); + pb->translate(translx, transly); + paintCell( pb, item, c, r, QRect(colp, rowp, colw, d->rowHeight)); + pb->restoreWorldMatrix(); + } + + if (m_dragIndicatorLine>=0) { + int y_line = -1; + if (r==(rows()-1) && m_dragIndicatorLine==rows()) { + y_line = transly+d->rowHeight-3; //draw at last line + } + if (m_dragIndicatorLine==r) { + y_line = transly+1; + } + if (y_line>=0) { + RasterOp op = pb->rasterOp(); + pb->setRasterOp(XorROP); + pb->setPen( QPen(Qt::white, 3) ); + pb->drawLine(0, y_line, maxwc, y_line); + pb->setRasterOp(op); + } + } +} + +void KexiTableView::drawContents( QPainter *p, int cx, int cy, int cw, int ch) +{ + if (d->disableDrawContents) + return; + int colfirst = columnAt(cx); + int rowfirst = rowAt(cy); + int collast = columnAt(cx + cw-1); + int rowlast = rowAt(cy + ch-1); + bool inserting = isInsertingEnabled(); + bool plus1row = false; //true if we should show 'inserting' row at the end + bool paintOnlyInsertRow = false; + +/* kdDebug(44021) << QString(" KexiTableView::drawContents(cx:%1 cy:%2 cw:%3 ch:%4)") + .arg(cx).arg(cy).arg(cw).arg(ch) << endl;*/ + + if (rowlast == -1) { + rowlast = rows() - 1; + plus1row = inserting; + if (rowfirst == -1) { + if (rowAt(cy - d->rowHeight) != -1) { + paintOnlyInsertRow = true; +// kdDebug(44021) << "-- paintOnlyInsertRow --" << endl; + } + } + } +// kdDebug(44021) << "rowfirst="<<rowfirst<<" rowlast="<<rowlast<<" rows()="<<rows()<<endl; +// kdDebug(44021)<<" plus1row=" << plus1row<<endl; + + if ( collast == -1 ) + collast = columns() - 1; + + if (colfirst>collast) { + int tmp = colfirst; + colfirst = collast; + collast = tmp; + } + if (rowfirst>rowlast) { + int tmp = rowfirst; + rowfirst = rowlast; + rowlast = tmp; + } + +// qDebug("cx:%3d cy:%3d w:%3d h:%3d col:%2d..%2d row:%2d..%2d tsize:%4d,%4d", +// cx, cy, cw, ch, colfirst, collast, rowfirst, rowlast, tableSize().width(), tableSize().height()); +// triggerUpdate(); + + if (rowfirst == -1 || colfirst == -1) { + if (!paintOnlyInsertRow && !plus1row) { + paintEmptyArea(p, cx, cy, cw, ch); + return; + } + } + + createBuffer(cw, ch); + if(d->pBufferPm->isNull()) + return; + QPainter *pb = new QPainter(d->pBufferPm, this); +// pb->fillRect(0, 0, cw, ch, colorGroup().base()); + +// int maxwc = QMIN(cw, (columnPos(d->numCols - 1) + columnWidth(d->numCols - 1))); + int maxwc = columnPos(columns() - 1) + columnWidth(columns() - 1); +// kdDebug(44021) << "KexiTableView::drawContents(): maxwc: " << maxwc << endl; + + pb->fillRect(cx, cy, cw, ch, d->appearance.baseColor); + + int rowp; + int r; + if (paintOnlyInsertRow) { + r = rows(); + rowp = rowPos(r); // 'insert' row's position + } + else { + QPtrListIterator<KexiTableItem> it = m_data->iterator(); + it += rowfirst;//move to 1st row + rowp = rowPos(rowfirst); // row position + for (r = rowfirst;r <= rowlast; r++, ++it, rowp+=d->rowHeight) { + paintRow(it.current(), pb, r, rowp, cx, cy, colfirst, collast, maxwc); + } + } + + if (plus1row) { //additional - 'insert' row + paintRow(m_insertItem, pb, r, rowp, cx, cy, colfirst, collast, maxwc); + } + + delete pb; + + p->drawPixmap(cx,cy,*d->pBufferPm, 0,0,cw,ch); + + //(js) + paintEmptyArea(p, cx, cy, cw, ch); +} + +bool KexiTableView::isDefaultValueDisplayed(KexiTableItem *item, int col, QVariant* value) +{ + const bool cursorAtInsertRowOrEditingNewRow = (item == m_insertItem || (m_newRowEditing && m_currentItem == item)); + KexiTableViewColumn *tvcol; + if (cursorAtInsertRowOrEditingNewRow + && (tvcol = m_data->column(col)) + && hasDefaultValueAt(*tvcol) + && !tvcol->field()->isAutoIncrement()) + { + if (value) + *value = tvcol->field()->defaultValue(); + return true; + } + return false; +} + +void KexiTableView::paintCell(QPainter* p, KexiTableItem *item, int col, int row, const QRect &cr, bool print) +{ + p->save(); + Q_UNUSED(print); + int w = cr.width(); + int h = cr.height(); + int x2 = w - 1; + int y2 = h - 1; + +/* if (0==qstrcmp("KexiComboBoxPopup",parentWidget()->className())) { + kexidbg << parentWidget()->className() << " >>>>>> KexiTableView::paintCell(col=" << col <<"row="<<row<<") w="<<w<<endl; + }*/ + + // Draw our lines + QPen pen(p->pen()); + + if (d->appearance.gridEnabled) { + p->setPen(d->appearance.borderColor); + p->drawLine( x2, 0, x2, y2 ); // right + p->drawLine( 0, y2, x2, y2 ); // bottom + } + p->setPen(pen); + + if (m_editor && row == m_curRow && col == m_curCol //don't paint contents of edited cell + && m_editor->hasFocusableWidget() //..if it's visible + ) { + p->restore(); + return; + } + + KexiTableEdit *edit = tableEditorWidget( col, /*ignoreMissingEditor=*/true ); +// if (!edit) +// return; + + int x = edit ? edit->leftMargin() : 0; + int y_offset=0; + + int align = Qt::SingleLine | Qt::AlignVCenter; + QString txt; //text to draw + + KexiTableViewColumn *tvcol = m_data->column(col); + + QVariant cellValue; + if (col < (int)item->count()) { + if (m_currentItem == item) { + if (m_editor && row == m_curRow && col == m_curCol + && !m_editor->hasFocusableWidget()) + { + //we're over editing cell and the editor has no widget + // - we're displaying internal values, not buffered +// bool ok; + cellValue = m_editor->value(); + } + else { + //we're displaying values from edit buffer, if available + // this assignment will also get default value if there's no actual value set + cellValue = *bufferedValueAt(col); + } + } + else { + cellValue = item->at(col); + } + } + + bool defaultValueDisplayed = isDefaultValueDisplayed(item, col); + + if ((item == m_insertItem /*|| m_newRowEditing*/) && cellValue.isNull()) { + if (!tvcol->field()->isAutoIncrement() && !tvcol->field()->defaultValue().isNull()) { + //display default value in the "insert row", if available + //(but not if there is autoincrement flag set) + cellValue = tvcol->field()->defaultValue(); + defaultValueDisplayed = true; + } + } + + const bool columnReadOnly = tvcol->isReadOnly(); + const bool dontPaintNonpersistentSelectionBecauseDifferentRowHasBeenHighlighted + = d->appearance.rowHighlightingEnabled && !d->appearance.persistentSelections + && m_curRow /*d->highlightedRow*/ >= 0 && row != m_curRow; //d->highlightedRow; + + // setup default pen + QPen defaultPen; + const bool usesSelectedTextColor = edit && edit->usesSelectedTextColor(); + if (defaultValueDisplayed) { + if (col == m_curCol && row == m_curRow && usesSelectedTextColor) + defaultPen = d->defaultValueDisplayParameters.selectedTextColor; + else + defaultPen = d->defaultValueDisplayParameters.textColor; + } + else if (d->appearance.fullRowSelection + && (row == d->highlightedRow || (row == m_curRow && d->highlightedRow==-1)) + && usesSelectedTextColor ) + { + defaultPen = d->appearance.rowHighlightingTextColor; //special case: highlighted row + } + else if (d->appearance.fullRowSelection && row == m_curRow && usesSelectedTextColor) + { + defaultPen = d->appearance.textColor; //special case for full row selection + } + else if (m_currentItem == item && col == m_curCol && !columnReadOnly + && !dontPaintNonpersistentSelectionBecauseDifferentRowHasBeenHighlighted + && usesSelectedTextColor) + { + defaultPen = colorGroup().highlightedText(); //selected text + } + else if (d->appearance.rowHighlightingEnabled && row == m_curRow + && !dontPaintNonpersistentSelectionBecauseDifferentRowHasBeenHighlighted + && usesSelectedTextColor) + { + defaultPen = d->appearance.rowHighlightingTextColor; + } + else if (d->appearance.rowMouseOverHighlightingEnabled && row == d->highlightedRow + && !dontPaintNonpersistentSelectionBecauseDifferentRowHasBeenHighlighted + && usesSelectedTextColor) + { + defaultPen = d->appearance.rowMouseOverHighlightingTextColor; + } + else + defaultPen = d->appearance.textColor; + + if (edit) { + if (defaultValueDisplayed) + p->setFont( d->defaultValueDisplayParameters.font ); + p->setPen( defaultPen ); + + //get visible lookup value if available + getVisibleLookupValue(cellValue, edit, item, tvcol); + + edit->setupContents( p, m_currentItem == item && col == m_curCol, + cellValue, txt, align, x, y_offset, w, h ); + } + if (!d->appearance.gridEnabled) + y_offset++; //correction because we're not drawing cell borders + + if (d->appearance.fullRowSelection && d->appearance.fullRowSelection) { +// p->fillRect(x, y_offset, x+w-1, y_offset+h-1, red); + } + if (m_currentItem == item && (col == m_curCol || d->appearance.fullRowSelection)) { + if (edit && ((d->appearance.rowHighlightingEnabled && !d->appearance.fullRowSelection) || (row == m_curRow && d->highlightedRow==-1 && d->appearance.fullRowSelection))) //!dontPaintNonpersistentSelectionBecauseDifferentRowHasBeenHighlighted) + edit->paintSelectionBackground( p, isEnabled(), txt, align, x, y_offset, w, h, + isEnabled() ? colorGroup().highlight() : QColor(200,200,200),//d->grayColor, + p->fontMetrics(), columnReadOnly, d->appearance.fullRowSelection ); + } + + if (!edit) { + p->fillRect(0, 0, x2, y2, d->diagonalGrayPattern); + } + +// If we are in the focus cell, draw indication + if(m_currentItem == item && col == m_curCol //js: && !d->recordIndicator) + && !d->appearance.fullRowSelection) + { +// kexidbg << ">>> CURRENT CELL ("<<m_curCol<<"," << m_curRow<<") focus="<<has_focus<<endl; +// if (has_focus) { + if (isEnabled()) { + p->setPen(d->appearance.textColor); + } + else { + QPen gray_pen(p->pen()); + gray_pen.setColor(d->appearance.borderColor); + p->setPen(gray_pen); + } + if (edit) + edit->paintFocusBorders( p, cellValue, 0, 0, x2, y2 ); + else + p->drawRect(0, 0, x2, y2); + } + +/// bool autonumber = false; + if ((!m_newRowEditing && item == m_insertItem) + || (m_newRowEditing && item == m_currentItem && cellValue.isNull())) { + //we're in "insert row" + if (tvcol->field()->isAutoIncrement()) { + //"autonumber" column +// txt = i18n("(autonumber)"); +// autonumber = true; +// if (autonumber) { + KexiDisplayUtils::paintAutonumberSign(d->autonumberSignDisplayParameters, p, + x, y_offset, w - x - x - ((align & Qt::AlignLeft)?2:0), h, align); +// } + } + } + + // draw text + if (!txt.isEmpty()) { + if (defaultValueDisplayed) + p->setFont( d->defaultValueDisplayParameters.font ); + p->setPen( defaultPen ); + p->drawText(x, y_offset, w - (x + x)- ((align & Qt::AlignLeft)?2:0)/*right space*/, h, + align, txt); + } + p->restore(); +} + +QPoint KexiTableView::contentsToViewport2( const QPoint &p ) +{ + return QPoint( p.x() - contentsX(), p.y() - contentsY() ); +} + +void KexiTableView::contentsToViewport2( int x, int y, int& vx, int& vy ) +{ + const QPoint v = contentsToViewport2( QPoint( x, y ) ); + vx = v.x(); + vy = v.y(); +} + +QPoint KexiTableView::viewportToContents2( const QPoint& vp ) +{ + return QPoint( vp.x() + contentsX(), + vp.y() + contentsY() ); +} + +void KexiTableView::paintEmptyArea( QPainter *p, int cx, int cy, int cw, int ch ) +{ +// qDebug("%s: paintEmptyArea(x:%d y:%d w:%d h:%d)", (const char*)parentWidget()->caption(),cx,cy,cw,ch); + + // Regions work with shorts, so avoid an overflow and adjust the + // table size to the visible size + QSize ts( tableSize() ); +// ts.setWidth( QMIN( ts.width(), visibleWidth() ) ); +// ts.setHeight( QMIN( ts.height() - (m_navPanel ? m_navPanel->height() : 0), visibleHeight()) ); +/* kdDebug(44021) << QString(" (cx:%1 cy:%2 cw:%3 ch:%4)") + .arg(cx).arg(cy).arg(cw).arg(ch) << endl; + kdDebug(44021) << QString(" (w:%3 h:%4)") + .arg(ts.width()).arg(ts.height()) << endl;*/ + + // Region of the rect we should draw, calculated in viewport + // coordinates, as a region can't handle bigger coordinates + contentsToViewport2( cx, cy, cx, cy ); + QRegion reg( QRect( cx, cy, cw, ch ) ); + +//kexidbg << "---cy-- " << contentsY() << endl; + + // Subtract the table from it +// reg = reg.subtract( QRect( QPoint( 0, 0 ), ts-QSize(0,m_navPanel->isVisible() ? m_navPanel->height() : 0) ) ); + reg = reg.subtract( QRect( QPoint( 0, 0 ), ts + -QSize(0,QMAX((m_navPanel ? m_navPanel->height() : 0), horizontalScrollBar()->sizeHint().height()) + - (horizontalScrollBar()->isVisible() ? horizontalScrollBar()->sizeHint().height()/2 : 0) + + (horizontalScrollBar()->isVisible() ? 0 : + d->internal_bottomMargin +// horizontalScrollBar()->sizeHint().height()/2 + ) +//- /*d->bottomMargin */ horizontalScrollBar()->sizeHint().height()*3/2 + + contentsY() +// - (verticalScrollBar()->isVisible() ? horizontalScrollBar()->sizeHint().height()/2 : 0) + ) + ) ); +// reg = reg.subtract( QRect( QPoint( 0, 0 ), ts ) ); + + // And draw the rectangles (transformed inc contents coordinates as needed) + QMemArray<QRect> r = reg.rects(); + for ( int i = 0; i < (int)r.count(); i++ ) { + QRect rect( viewportToContents2(r[i].topLeft()), r[i].size() ); +/* kdDebug(44021) << QString("- pEA: p->fillRect(x:%1 y:%2 w:%3 h:%4)") + .arg(rect.x()).arg(rect.y()) + .arg(rect.width()).arg(rect.height()) << endl;*/ +// p->fillRect( QRect(viewportToContents2(r[i].topLeft()),r[i].size()), d->emptyAreaColor ); + p->fillRect( rect, d->appearance.emptyAreaColor ); +// p->fillRect( QRect(viewportToContents2(r[i].topLeft()),r[i].size()), viewport()->backgroundBrush() ); + } +} + +void KexiTableView::contentsMouseDoubleClickEvent(QMouseEvent *e) +{ +// kdDebug(44021) << "KexiTableView::contentsMouseDoubleClickEvent()" << endl; + m_contentsMousePressEvent_dblClick = true; + contentsMousePressEvent(e); + m_contentsMousePressEvent_dblClick = false; + + if(m_currentItem) + { + if(d->editOnDoubleClick && columnEditable(m_curCol) && columnType(m_curCol) != KexiDB::Field::Boolean) { + KexiTableEdit *edit = tableEditorWidget( m_curCol, /*ignoreMissingEditor=*/true ); + if (edit && edit->handleDoubleClick()) { + //nothing to do: editors like BLOB editor has custom handling of double clicking + } + else { + startEditCurrentCell(); + // createEditor(m_curRow, m_curCol, QString::null); + } + } + + emit itemDblClicked(m_currentItem, m_curRow, m_curCol); + } +} + +void KexiTableView::contentsMousePressEvent( QMouseEvent* e ) +{ +// kdDebug(44021) << "KexiTableView::contentsMousePressEvent() ??" << endl; + setFocus(); + if(m_data->count()==0 && !isInsertingEnabled()) { + QScrollView::contentsMousePressEvent( e ); + return; + } + + if (columnAt(e->pos().x())==-1) { //outside a colums + QScrollView::contentsMousePressEvent( e ); + return; + } +// d->contentsMousePressEvent_ev = *e; +// d->contentsMousePressEvent_enabled = true; +// QTimer::singleShot(2000, this, SLOT( contentsMousePressEvent_Internal() )); +// d->contentsMousePressEvent_timer.start(100,true); + +// if (!d->contentsMousePressEvent_enabled) +// return; +// d->contentsMousePressEvent_enabled=false; + + if (!d->moveCursorOnMouseRelease) { + if (!handleContentsMousePressOrRelease(e, false)) + return; + } + +// kdDebug(44021)<<"void KexiTableView::contentsMousePressEvent( QMouseEvent* e ) by now the current items should be set, if not -> error + crash"<<endl; + if(e->button() == Qt::RightButton) + { + showContextMenu(e->globalPos()); + } + else if(e->button() == Qt::LeftButton) + { + if(columnType(m_curCol) == KexiDB::Field::Boolean && columnEditable(m_curCol)) + { + //only accept clicking on the [x] rect (copied from KexiBoolTableEdit::setupContents()) + int s = QMAX(d->rowHeight - 5, 12); + s = QMIN( d->rowHeight-3, s ); + s = QMIN( columnWidth(m_curCol)-3, s ); //avoid too large box + const QRect r( columnPos(m_curCol) + QMAX( columnWidth(m_curCol)/2 - s/2, 0 ), rowPos(m_curRow) +d->rowHeight/2 - s/2 /*- 1*/, s, s); + //kexidbg << r << endl; + if (r.contains(e->pos())) { +// kexidbg << "e->x:" << e->x() << " e->y:" << e->y() << " " << rowPos(m_curRow) << +// " " << columnPos(m_curCol) << endl; + boolToggled(); + } + } +#if 0 //js: TODO + else if(columnType(m_curCol) == QVariant::StringList && columnEditable(m_curCol)) + { + createEditor(m_curRow, m_curCol); + } +#endif + } +//ScrollView::contentsMousePressEvent( e ); +} + +void KexiTableView::contentsMouseReleaseEvent( QMouseEvent* e ) +{ +// kdDebug(44021) << "KexiTableView::contentsMousePressEvent() ??" << endl; + if(m_data->count()==0 && !isInsertingEnabled()) + return; + + if (d->moveCursorOnMouseRelease) + handleContentsMousePressOrRelease(e, true); + + int col = columnAt(e->pos().x()); + int row = rowAt(e->pos().y()); + + if (!m_currentItem || col==-1 || row==-1 || col!=m_curCol || row!=m_curRow)//outside a current cell + return; + + QScrollView::contentsMouseReleaseEvent( e ); + + emit itemMouseReleased(m_currentItem, m_curRow, m_curCol); +} + +bool KexiTableView::handleContentsMousePressOrRelease(QMouseEvent* e, bool release) +{ + // remember old focus cell + int oldRow = m_curRow; + int oldCol = m_curCol; + kdDebug(44021) << "oldRow=" << oldRow <<" oldCol=" << oldCol <<endl; + bool onInsertItem = false; + + int newrow, newcol; + //compute clicked row nr + if (isInsertingEnabled()) { + if (rowAt(e->pos().y())==-1) { + newrow = rowAt(e->pos().y() - d->rowHeight); + if (newrow==-1 && m_data->count()>0) { + if (release) + QScrollView::contentsMouseReleaseEvent( e ); + else + QScrollView::contentsMousePressEvent( e ); + return false; + } + newrow++; + kdDebug(44021) << "Clicked just on 'insert' row." << endl; + onInsertItem=true; + } + else { + // get new focus cell + newrow = rowAt(e->pos().y()); + } + } + else { + if (rowAt(e->pos().y())==-1 || columnAt(e->pos().x())==-1) { + if (release) + QScrollView::contentsMouseReleaseEvent( e ); + else + QScrollView::contentsMousePressEvent( e ); + return false; //clicked outside a grid + } + // get new focus cell + newrow = rowAt(e->pos().y()); + } + newcol = columnAt(e->pos().x()); + + if(e->button() != Qt::NoButton) { + setCursorPosition(newrow,newcol); + } + return true; +} + +void KexiTableView::showContextMenu(const QPoint& _pos) +{ + if (!d->contextMenuEnabled || m_popupMenu->count()<1) + return; + QPoint pos(_pos); + if (pos==QPoint(-1,-1)) { + pos = viewport()->mapToGlobal( QPoint( columnPos(m_curCol), rowPos(m_curRow) + d->rowHeight ) ); + } + //show own context menu if configured +// if (updateContextMenu()) { + selectRow(m_curRow); + m_popupMenu->exec(pos); +/* } + else { + //request other context menu + emit contextMenuRequested(m_currentItem, m_curCol, pos); + }*/ +} + +void KexiTableView::contentsMouseMoveEvent( QMouseEvent *e ) +{ + int row; + const int col = columnAt(e->x()); + if (col < 0) { + row = -1; + } else { + row = rowAt( e->y(), true /*ignoreEnd*/ ); + if (row > (rows() - 1 + (isInsertingEnabled()?1:0))) + row = -1; //no row to paint + } +// kexidbg << " row="<<row<< " col="<<col<<endl; + //update row highlight if needed + if (d->appearance.rowMouseOverHighlightingEnabled) { + if (row != d->highlightedRow) { + const int oldRow = d->highlightedRow; + d->highlightedRow = row; + updateRow(oldRow); + updateRow(d->highlightedRow); + //currently selected (not necessary highlighted) row needs to be repainted + updateRow(m_curRow); + m_verticalHeader->setHighlightedRow(d->highlightedRow); + } + } + +#if 0//(js) doesn't work! + + // do the same as in mouse press + int x,y; + contentsToViewport(e->x(), e->y(), x, y); + + if(y > visibleHeight()) + { + d->needAutoScroll = true; + d->scrollTimer->start(70, false); + d->scrollDirection = ScrollDown; + } + else if(y < 0) + { + d->needAutoScroll = true; + d->scrollTimer->start(70, false); + d->scrollDirection = ScrollUp; + } + else if(x > visibleWidth()) + { + d->needAutoScroll = true; + d->scrollTimer->start(70, false); + d->scrollDirection = ScrollRight; + } + else if(x < 0) + { + d->needAutoScroll = true; + d->scrollTimer->start(70, false); + d->scrollDirection = ScrollLeft; + } + else + { + d->needAutoScroll = false; + d->scrollTimer->stop(); + contentsMousePressEvent(e); + } +#endif + QScrollView::contentsMouseMoveEvent(e); +} + +#if 0//(js) doesn't work! +void KexiTableView::contentsMouseReleaseEvent(QMouseEvent *) +{ + if(d->needAutoScroll) + { + d->scrollTimer->stop(); + } +} +#endif + +static bool overrideEditorShortcutNeeded(QKeyEvent *e) +{ + //perhaps more to come... + return e->key() == Qt::Key_Delete && e->state()==Qt::ControlButton; +} + +bool KexiTableView::shortCutPressed( QKeyEvent *e, const QCString &action_name ) +{ + const int k = e->key(); + KAction *action = m_sharedActions[action_name]; + if (action) { + if (!action->isEnabled())//this action is disabled - don't process it! + return false; + if (action->shortcut() == KShortcut( KKey(e) )) { + //special cases when we need to override editor's shortcut + if (overrideEditorShortcutNeeded(e)) { + return true; + } + return false;//this shortcut is owned by shared action - don't process it! + } + } + + //check default shortcut (when user app has no action shortcuts defined + // but we want these shortcuts to still work) + if (action_name=="data_save_row") + return (k == Qt::Key_Return || k == Qt::Key_Enter) && e->state()==Qt::ShiftButton; + if (action_name=="edit_delete_row") + return k == Qt::Key_Delete && e->state()==Qt::ControlButton; + if (action_name=="edit_delete") + return k == Qt::Key_Delete && e->state()==Qt::NoButton; + if (action_name=="edit_edititem") + return k == Qt::Key_F2 && e->state()==Qt::NoButton; + if (action_name=="edit_insert_empty_row") + return k == Qt::Key_Insert && e->state()==(Qt::ShiftButton | Qt::ControlButton); + + return false; +} + +void KexiTableView::keyPressEvent(QKeyEvent* e) +{ + if (!hasData()) + return; +// kexidbg << "KexiTableView::keyPressEvent: key=" <<e->key() << " txt=" <<e->text()<<endl; + + const int k = e->key(); + const bool ro = isReadOnly(); + QWidget *w = focusWidget(); +// if (!w || w!=viewport() && w!=this && (!m_editor || w!=m_editor->view() && w!=m_editor)) { +// if (!w || w!=viewport() && w!=this && (!m_editor || w!=m_editor->view())) { + if (!w || w!=viewport() && w!=this && (!m_editor || !KexiUtils::hasParent(dynamic_cast<QObject*>(m_editor), w))) { + //don't process stranger's events + e->ignore(); + return; + } + if (d->skipKeyPress) { + d->skipKeyPress=false; + e->ignore(); + return; + } + + if(m_currentItem == 0 && (m_data->count() > 0 || isInsertingEnabled())) + { + setCursorPosition(0,0); + } + else if(m_data->count() == 0 && !isInsertingEnabled()) + { + e->accept(); + return; + } + + if(m_editor) {// if a cell is edited, do some special stuff + if (k == Qt::Key_Escape) { + cancelEditor(); + e->accept(); + return; + } else if (k == Qt::Key_Return || k == Qt::Key_Enter) { + if (columnType(m_curCol) == KexiDB::Field::Boolean) { + boolToggled(); + } + else { + acceptEditor(); + } + e->accept(); + return; + } + } + else if (m_rowEditing) {// if a row is in edit mode, do some special stuff + if (shortCutPressed( e, "data_save_row")) { + kexidbg << "shortCutPressed!!!" <<endl; + acceptRowEdit(); + return; + } + } + + if(k == Qt::Key_Return || k == Qt::Key_Enter) + { + emit itemReturnPressed(m_currentItem, m_curRow, m_curCol); + } + + int curRow = m_curRow; + int curCol = m_curCol; + + const bool nobtn = e->state()==NoButton; + bool printable = false; + + //check shared shortcuts + if (!ro) { + if (shortCutPressed(e, "edit_delete_row")) { + deleteCurrentRow(); + e->accept(); + return; + } else if (shortCutPressed(e, "edit_delete")) { + deleteAndStartEditCurrentCell(); + e->accept(); + return; + } + else if (shortCutPressed(e, "edit_insert_empty_row")) { + insertEmptyRow(); + e->accept(); + return; + } + } + +/* case Qt::Key_Delete: + if (e->state()==Qt::ControlButton) {//remove current row + deleteCurrentRow(); + } + else if (nobtn) {//remove contents of the current cell + deleteAndStartEditCurrentCell(); + } + break;*/ + +// bool _return; + if (k == Qt::Key_Shift || k == Qt::Key_Alt || k == Qt::Key_Control || k == Qt::Key_Meta) { + e->ignore(); + } + else if (KexiDataAwareObjectInterface::handleKeyPress(e, curRow, curCol, d->appearance.fullRowSelection)) { + if (e->isAccepted()) + return; + } + else if (k == Qt::Key_Backspace && nobtn) { + if (!ro && columnType(curCol) != KexiDB::Field::Boolean && columnEditable(curCol)) + createEditor(curRow, curCol, QString::null, true); + } + else if (k == Qt::Key_Space) { + if (nobtn && !ro && columnEditable(curCol)) { + if (columnType(curCol) == KexiDB::Field::Boolean) { + boolToggled(); + } + else + printable = true; //just space key + } + } + else if (k == Qt::Key_Escape) { + if (nobtn && m_rowEditing) { + cancelRowEdit(); + return; + } + } + else { + //others: + if (nobtn && (k==Qt::Key_Tab || k==Qt::Key_Right)) { +//! \todo add option for stopping at 1st column for Qt::Key_left + //tab + if (acceptEditor()) { + if (curCol == (columns() - 1)) { + if (curRow < (rows()-1+(isInsertingEnabled()?1:0))) {//skip to next row + curRow++; + curCol = 0; + } + } + else + curCol++; + } + } + else if ((e->state()==Qt::ShiftButton && k==Qt::Key_Tab) + || (nobtn && k==Qt::Key_Backtab) + || (e->state()==Qt::ShiftButton && k==Qt::Key_Backtab) + || (nobtn && k==Qt::Key_Left) + ) { +//! \todo add option for stopping at last column + //backward tab + if (acceptEditor()) { + if (curCol == 0) { + if (curRow>0) {//skip to previous row + curRow--; + curCol = columns() - 1; + } + } + else + curCol--; + } + } + else if (nobtn && k==d->contextMenuKey) { //Qt::Key_Menu: + showContextMenu(); + } + else { + KexiTableEdit *edit = tableEditorWidget( m_curCol ); + if (edit && edit->handleKeyPress(e, m_editor==edit)) { + //try to handle the event @ editor's level + e->accept(); + return; + } + else if ( nobtn && (k==Qt::Key_Enter || k==Qt::Key_Return || shortCutPressed(e, "edit_edititem")) ) { + //this condition is moved after handleKeyPress() to allow to everride enter key as well + startEditOrToggleValue(); + } + else { + kexidbg << "KexiTableView::KeyPressEvent(): default" << endl; + if (e->text().isEmpty() || !e->text().isEmpty() && !e->text()[0].isPrint() ) { + kdDebug(44021) << "NOT PRINTABLE: 0x0" << QString("%1").arg(k,0,16) <<endl; + // e->ignore(); + QScrollView::keyPressEvent(e); + return; + } + printable = true; + } + } + } + //finally: we've printable char: + if (printable && !ro) { + KexiTableViewColumn *tvcol = m_data->column(curCol); + if (tvcol->acceptsFirstChar(e->text()[0])) { + kdDebug(44021) << "KexiTableView::KeyPressEvent(): ev pressed: acceptsFirstChar()==true" << endl; + // if (e->text()[0].isPrint()) + createEditor(curRow, curCol, e->text(), true); + } + else { +//TODO show message "key not allowed eg. on a statusbar" + kdDebug(44021) << "KexiTableView::KeyPressEvent(): ev pressed: acceptsFirstChar()==false" << endl; + } + } + + m_vScrollBarValueChanged_enabled=false; + + // if focus cell changes, repaint + setCursorPosition(curRow, curCol); + + m_vScrollBarValueChanged_enabled=true; + + e->accept(); +} + +void KexiTableView::emitSelected() +{ + if(m_currentItem) + emit itemSelected(m_currentItem); +} + +int KexiTableView::rowsPerPage() const +{ + return visibleHeight() / d->rowHeight; +} + +KexiDataItemInterface *KexiTableView::editor( int col, bool ignoreMissingEditor ) +{ + if (!m_data || col<0 || col>=columns()) + return 0; + KexiTableViewColumn *tvcol = m_data->column(col); +// int t = tvcol->field->type(); + + //find the editor for this column + KexiTableEdit *editor = d->editors[ tvcol ]; + if (editor) + return editor; + + //not found: create +// editor = KexiCellEditorFactory::createEditor(*m_data->column(col)->field, this); + editor = KexiCellEditorFactory::createEditor(*tvcol, this); + if (!editor) {//create error! + if (!ignoreMissingEditor) { + //js TODO: show error??? + cancelRowEdit(); + } + return 0; + } + editor->hide(); + if (m_data->cursor() && m_data->cursor()->query()) + editor->createInternalEditor(*m_data->cursor()->query()); + + connect(editor,SIGNAL(editRequested()),this,SLOT(slotEditRequested())); + connect(editor,SIGNAL(cancelRequested()),this,SLOT(cancelEditor())); + connect(editor,SIGNAL(acceptRequested()),this,SLOT(acceptEditor())); + + editor->resize(columnWidth(col)-1, rowHeight()-1); + editor->installEventFilter(this); + if (editor->widget()) + editor->widget()->installEventFilter(this); + //store + d->editors.insert( tvcol, editor ); + return editor; +} + +void KexiTableView::editorShowFocus( int /*row*/, int col ) +{ + KexiDataItemInterface *edit = editor( col ); + /*nt p = rowPos(row); + (!edit || (p < contentsY()) || (p > (contentsY()+clipper()->height()))) { + kexidbg<< "KexiTableView::editorShowFocus() : OUT" << endl; + return; + }*/ + if (edit) { + kexidbg<< "KexiTableView::editorShowFocus() : IN" << endl; + QRect rect = cellGeometry( m_curRow, m_curCol ); +// rect.moveBy( -contentsX(), -contentsY() ); + edit->showFocus( rect, isReadOnly() || m_data->column(col)->isReadOnly() ); + } +} + +void KexiTableView::slotEditRequested() +{ + createEditor(m_curRow, m_curCol); +} + +void KexiTableView::reloadData() { + KexiDataAwareObjectInterface::reloadData(); + updateContents(); +} + +void KexiTableView::createEditor(int row, int col, const QString& addText, bool removeOld) +{ + kdDebug(44021) << "KexiTableView::createEditor('"<<addText<<"',"<<removeOld<<")"<<endl; + if (isReadOnly()) { + kdDebug(44021) << "KexiTableView::createEditor(): DATA IS READ ONLY!"<<endl; + return; + } + + if (m_data->column(col)->isReadOnly()) {//d->pColumnModes.at(d->numCols-1) & ColumnReadOnly) + kdDebug(44021) << "KexiTableView::createEditor(): COL IS READ ONLY!"<<endl; + return; + } + + const bool startRowEdit = !m_rowEditing; //remember if we're starting row edit + + if (!m_rowEditing) { + //we're starting row editing session + m_data->clearRowEditBuffer(); + + m_rowEditing = true; + //indicate on the vheader that we are editing: + m_verticalHeader->setEditRow(m_curRow); + if (isInsertingEnabled() && m_currentItem==m_insertItem) { + //we should know that we are in state "new row editing" + m_newRowEditing = true; + //'insert' row editing: show another row after that: + m_data->append( m_insertItem ); + //new empty 'inserting' item + m_insertItem = m_data->createItem(); + m_verticalHeader->addLabel(); + m_verticalHeaderAlreadyAdded = true; + updateWidgetContentsSize(); + //refr. current and next row + updateContents(columnPos(0), rowPos(row), viewport()->width(), d->rowHeight*2); +// updateContents(columnPos(0), rowPos(row+1), viewport()->width(), d->rowHeight); +//js: warning this breaks behaviour (cursor is skipping, etc.): qApp->processEvents(500); + ensureVisible(columnPos(m_curCol), rowPos(row+1)+d->rowHeight-1, columnWidth(m_curCol), d->rowHeight); + + m_verticalHeader->setOffset(contentsY()); + } + } + + KexiTableEdit *editorWidget = tableEditorWidget( col ); + m_editor = editorWidget; + if (!editorWidget) + return; + + m_editor->setValue(*bufferedValueAt(col, !removeOld/*useDefaultValueIfPossible*/), addText, removeOld); + if (m_editor->hasFocusableWidget()) { + moveChild(editorWidget, columnPos(m_curCol), rowPos(m_curRow)); + + editorWidget->resize(columnWidth(m_curCol)-1, rowHeight()-1); + editorWidget->show(); + + m_editor->setFocus(); + } + + if (startRowEdit) { + m_navPanel->showEditingIndicator(true); //this will allow to enable 'next' btn +// m_navPanel->updateButtons(rows()); //refresh 'next' btn + emit rowEditStarted(m_curRow); + } +} + +void KexiTableView::focusInEvent(QFocusEvent* e) +{ + Q_UNUSED(e); + updateCell(m_curRow, m_curCol); +} + +void KexiTableView::focusOutEvent(QFocusEvent* e) +{ + KexiDataAwareObjectInterface::focusOutEvent(e); +} + +bool KexiTableView::focusNextPrevChild(bool /*next*/) +{ + return false; //special Tab/BackTab meaning +/* if (m_editor) + return true; + return QScrollView::focusNextPrevChild(next);*/ +} + +void KexiTableView::resizeEvent(QResizeEvent *e) +{ + QScrollView::resizeEvent(e); + //updateGeometries(); + + if (m_navPanel) + m_navPanel->updateGeometry(leftMargin()); +// updateNavPanelGeometry(); + + if ((contentsHeight() - e->size().height()) <= d->rowHeight) { + slotUpdate(); + triggerUpdate(); + } +// d->pTopHeader->repaint(); + + +/* m_navPanel->setGeometry( + frameWidth(), + viewport()->height() +d->pTopHeader->height() + -(horizontalScrollBar()->isVisible() ? 0 : horizontalScrollBar()->sizeHint().height()) + +frameWidth(), + m_navPanel->sizeHint().width(), // - verticalScrollBar()->sizeHint().width() - horizontalScrollBar()->sizeHint().width(), + horizontalScrollBar()->sizeHint().height() + );*/ +// updateContents(); +// m_navPanel->setGeometry(1,horizontalScrollBar()->pos().y(), + // m_navPanel->width(), horizontalScrollBar()->height()); +// updateContents(0,0,2000,2000);//js +// erase(); repaint(); +} + +void KexiTableView::viewportResizeEvent( QResizeEvent *e ) +{ + QScrollView::viewportResizeEvent( e ); + updateGeometries(); +// erase(); repaint(); +} + +void KexiTableView::showEvent(QShowEvent *e) +{ + QScrollView::showEvent(e); + if (!d->maximizeColumnsWidthOnShow.isEmpty()) { + maximizeColumnsWidth(d->maximizeColumnsWidthOnShow); + d->maximizeColumnsWidthOnShow.clear(); + } + + if (m_initDataContentsOnShow) { + //full init + m_initDataContentsOnShow = false; + initDataContents(); + } + else { + //just update size + QSize s(tableSize()); +// QRect r(cellGeometry(rows() - 1 + (isInsertingEnabled()?1:0), columns() - 1 )); +// resizeContents(r.right() + 1, r.bottom() + 1); + resizeContents(s.width(),s.height()); + } + updateGeometries(); + + //now we can ensure cell's visibility ( if there was such a call before show() ) + if (d->ensureCellVisibleOnShow!=QPoint(-1,-1)) { + ensureCellVisible( d->ensureCellVisibleOnShow.x(), d->ensureCellVisibleOnShow.y() ); + d->ensureCellVisibleOnShow = QPoint(-1,-1); //reset the flag + } + if (m_navPanel) + m_navPanel->updateGeometry(leftMargin()); +// updateNavPanelGeometry(); +} + +void KexiTableView::contentsDragMoveEvent(QDragMoveEvent *e) +{ + if (!hasData()) + return; + if (m_dropsAtRowEnabled) { + QPoint p = e->pos(); + int row = rowAt(p.y()); + KexiTableItem *item = 0; +// if (row==(rows()-1) && (p.y() % d->rowHeight) > (d->rowHeight*2/3) ) { + if ((p.y() % d->rowHeight) > (d->rowHeight*2/3) ) { + row++; + } + item = m_data->at(row); + emit dragOverRow(item, row, e); + if (e->isAccepted()) { + if (m_dragIndicatorLine>=0 && m_dragIndicatorLine != row) { + //erase old indicator + updateRow(m_dragIndicatorLine); + } + if (m_dragIndicatorLine != row) { + m_dragIndicatorLine = row; + updateRow(m_dragIndicatorLine); + } + } + else { + if (m_dragIndicatorLine>=0) { + //erase old indicator + updateRow(m_dragIndicatorLine); + } + m_dragIndicatorLine = -1; + } + } + else + e->acceptAction(false); +/* QStringList::ConstIterator it, end( d->dropFilters.constEnd() ); + for( it = d->dropFilters.constBegin(); it != end; it++) + { + if(e->provides((*it).latin1())) + { + e->acceptAction(true); + return; + } + }*/ +// e->acceptAction(false); +} + +void KexiTableView::contentsDropEvent(QDropEvent *e) +{ + if (!hasData()) + return; + if (m_dropsAtRowEnabled) { + //we're no longer dragging over the table + if (m_dragIndicatorLine>=0) { + int row2update = m_dragIndicatorLine; + m_dragIndicatorLine = -1; + updateRow(row2update); + } + QPoint p = e->pos(); + int row = rowAt(p.y()); + if ((p.y() % d->rowHeight) > (d->rowHeight*2/3) ) { + row++; + } + KexiTableItem *item = m_data->at(row); + KexiTableItem *newItem = 0; + emit droppedAtRow(item, row, e, newItem); + if (newItem) { + const int realRow = (row==m_curRow ? -1 : row); + insertItem(newItem, realRow); + setCursorPosition(row, 0); +// m_currentItem = newItem; + } + } +} + +void KexiTableView::viewportDragLeaveEvent( QDragLeaveEvent *e ) +{ + Q_UNUSED(e); + if (!hasData()) + return; + if (m_dropsAtRowEnabled) { + //we're no longer dragging over the table + if (m_dragIndicatorLine>=0) { + int row2update = m_dragIndicatorLine; + m_dragIndicatorLine = -1; + updateRow(row2update); + } + } +} + +void KexiTableView::updateCell(int row, int col) +{ +// kdDebug(44021) << "updateCell("<<row<<", "<<col<<")"<<endl; + updateContents(cellGeometry(row, col)); +/* QRect r = cellGeometry(row, col); + r.setHeight(r.height()+6); + r.setTop(r.top()-3); + updateContents();*/ +} + +void KexiTableView::updateCurrentCell() +{ + updateCell(m_curRow, m_curCol); +} + +void KexiTableView::updateRow(int row) +{ +// kdDebug(44021) << "updateRow("<<row<<")"<<endl; + if (row < 0 || row >= (rows() + 2/* sometimes we want to refresh the row after last*/ )) + return; + //int leftcol = d->pTopHeader->sectionAt( d->pTopHeader->offset() ); + + //kexidbg << contentsX() << " " << contentsY() << endl; + //kexidbg << QRect( columnPos( leftcol ), rowPos(row), clipper()->width(), rowHeight() ) << endl; + // updateContents( QRect( columnPos( leftcol ), rowPos(row), clipper()->width(), rowHeight() ) ); //columnPos(rightcol)+columnWidth(rightcol), rowHeight() ) ); + updateContents( QRect( contentsX(), rowPos(row), clipper()->width(), rowHeight() ) ); //columnPos(rightcol)+columnWidth(rightcol), rowHeight() ) ); +} + +void KexiTableView::slotColumnWidthChanged( int, int, int ) +{ + QSize s(tableSize()); + int w = contentsWidth(); + viewport()->setUpdatesEnabled(false); + resizeContents( s.width(), s.height() ); + viewport()->setUpdatesEnabled(true); + if (contentsWidth() < w) { + updateContents(contentsX(), 0, viewport()->width(), contentsHeight()); +// repaintContents( s.width(), 0, w - s.width() + 1, contentsHeight(), true ); + } + else { + // updateContents( columnPos(col), 0, contentsWidth(), contentsHeight() ); + updateContents(contentsX(), 0, viewport()->width(), contentsHeight()); + // viewport()->repaint(); + } + +// updateContents(0, 0, d->pBufferPm->width(), d->pBufferPm->height()); + QWidget *editorWidget = dynamic_cast<QWidget*>(m_editor); + if (editorWidget) + { + editorWidget->resize(columnWidth(m_curCol)-1, rowHeight()-1); + moveChild(editorWidget, columnPos(m_curCol), rowPos(m_curRow)); + } + updateGeometries(); + updateScrollBars(); + if (m_navPanel) + m_navPanel->updateGeometry(leftMargin()); +// updateNavPanelGeometry(); +} + +void KexiTableView::slotSectionHandleDoubleClicked( int section ) +{ + adjustColumnWidthToContents(section); + slotColumnWidthChanged(0,0,0); //to update contents and redraw +} + + +void KexiTableView::updateGeometries() +{ + QSize ts = tableSize(); + if (m_horizontalHeader->offset() && ts.width() < (m_horizontalHeader->offset() + m_horizontalHeader->width())) + horizontalScrollBar()->setValue(ts.width() - m_horizontalHeader->width()); + +// m_verticalHeader->setGeometry(1, topMargin() + 1, leftMargin(), visibleHeight()); + m_horizontalHeader->setGeometry(leftMargin() + 1, 1, visibleWidth(), topMargin()); + m_verticalHeader->setGeometry(1, topMargin() + 1, leftMargin(), visibleHeight()); +} + +int KexiTableView::columnWidth(int col) const +{ + if (!hasData()) + return 0; + int vcID = m_data->visibleColumnID( col ); + return (vcID==-1) ? 0 : m_horizontalHeader->sectionSize( vcID ); +} + +int KexiTableView::rowHeight() const +{ + return d->rowHeight; +} + +int KexiTableView::columnPos(int col) const +{ + if (!hasData()) + return 0; + //if this column is hidden, find first column before that is visible + int c = QMIN(col, (int)m_data->columnsCount()-1), vcID = 0; + while (c>=0 && (vcID=m_data->visibleColumnID( c ))==-1) + c--; + if (c<0) + return 0; + if (c==col) + return m_horizontalHeader->sectionPos(vcID); + return m_horizontalHeader->sectionPos(vcID)+m_horizontalHeader->sectionSize(vcID); +} + +int KexiTableView::rowPos(int row) const +{ + return d->rowHeight*row; +} + +int KexiTableView::columnAt(int pos) const +{ + if (!hasData()) + return -1; + int r = m_horizontalHeader->sectionAt(pos); + if (r<0) + return r; + return m_data->globalColumnID( r ); + +// if (r==-1) +// kexidbg << "columnAt("<<pos<<")==-1 !!!" << endl; +// return r; +} + +int KexiTableView::rowAt(int pos, bool ignoreEnd) const +{ + if (!hasData()) + return -1; + pos /=d->rowHeight; + if (pos < 0) + return 0; + if ((pos >= (int)m_data->count()) && !ignoreEnd) + return -1; + return pos; +} + +QRect KexiTableView::cellGeometry(int row, int col) const +{ + return QRect(columnPos(col), rowPos(row), + columnWidth(col), rowHeight()); +} + +QSize KexiTableView::tableSize() const +{ + if ((rows()+ (isInsertingEnabled()?1:0) ) > 0 && columns() > 0) { +/* kexidbg << "tableSize()= " << columnPos( columns() - 1 ) + columnWidth( columns() - 1 ) + << ", " << rowPos( rows()-1+(isInsertingEnabled()?1:0)) + d->rowHeight +// + QMAX(m_navPanel ? m_navPanel->height() : 0, horizontalScrollBar()->sizeHint().height()) + + (m_navPanel->isVisible() ? QMAX( m_navPanel->height(), horizontalScrollBar()->sizeHint().height() ) :0 ) + + margin() << endl; +*/ +// kexidbg<< m_navPanel->isVisible() <<" "<<m_navPanel->height()<<" " +// <<horizontalScrollBar()->sizeHint().height()<<" "<<rowPos( rows()-1+(isInsertingEnabled()?1:0))<<endl; + + //int xx = horizontalScrollBar()->sizeHint().height()/2; + + QSize s( + columnPos( columns() - 1 ) + columnWidth( columns() - 1 ), +// + verticalScrollBar()->sizeHint().width(), + rowPos( rows()-1+(isInsertingEnabled()?1:0) ) + d->rowHeight + + (horizontalScrollBar()->isVisible() ? 0 : horizontalScrollBar()->sizeHint().height()) + + d->internal_bottomMargin +// horizontalScrollBar()->sizeHint().height()/2 +// - /*d->bottomMargin */ horizontalScrollBar()->sizeHint().height()*3/2 + +// + ( (m_navPanel && m_navPanel->isVisible() && verticalScrollBar()->isVisible() + // && !horizontalScrollBar()->isVisible()) + // ? horizontalScrollBar()->sizeHint().height() : 0) + +// + QMAX( (m_navPanel && m_navPanel->isVisible()) ? m_navPanel->height() : 0, +// horizontalScrollBar()->isVisible() ? horizontalScrollBar()->sizeHint().height() : 0) + +// + (m_navPanel->isVisible() +// ? QMAX( m_navPanel->height(), horizontalScrollBar()->sizeHint().height() ) :0 ) + +// - (horizontalScrollBar()->isVisible() ? horizontalScrollBar()->sizeHint().height() :0 ) + + margin() +//-2*d->rowHeight + ); + +// kexidbg << rows()-1 <<" "<< (isInsertingEnabled()?1:0) <<" "<< (m_rowEditing?1:0) << " " << s << endl; + return s; +// +horizontalScrollBar()->sizeHint().height() + margin() ); + } + return QSize(0,0); +} + +void KexiTableView::ensureCellVisible(int row, int col/*=-1*/) +{ + if (!isVisible()) { + //the table is invisible: we can't ensure visibility now + d->ensureCellVisibleOnShow = QPoint(row,col); + return; + } + + //quite clever: ensure the cell is visible: + QRect r( columnPos(col==-1 ? m_curCol : col), rowPos(row) +(d->appearance.fullRowSelection?1:0), + columnWidth(col==-1 ? m_curCol : col), rowHeight()); + +/* if (m_navPanel && horizontalScrollBar()->isHidden() && row == rows()-1) { + //when cursor is moved down and navigator covers the cursor's area, + //area is scrolled up + if ((viewport()->height() - m_navPanel->height()) < r.bottom()) { + scrollBy(0,r.bottom() - (viewport()->height() - m_navPanel->height())); + } + }*/ + + if (m_navPanel && m_navPanel->isVisible() && horizontalScrollBar()->isHidden()) { + //a hack: for visible navigator: increase height of the visible rect 'r' + r.setBottom(r.bottom()+m_navPanel->height()); + } + + QPoint pcenter = r.center(); + ensureVisible(pcenter.x(), pcenter.y(), r.width()/2, r.height()/2); +// updateContents(); +// updateNavPanelGeometry(); +// slotUpdate(); +} + +void KexiTableView::updateAfterCancelRowEdit() +{ + KexiDataAwareObjectInterface::updateAfterCancelRowEdit(); + m_navPanel->showEditingIndicator(false); +} + +void KexiTableView::updateAfterAcceptRowEdit() +{ + KexiDataAwareObjectInterface::updateAfterAcceptRowEdit(); + m_navPanel->showEditingIndicator(false); +} + +bool KexiTableView::getVisibleLookupValue(QVariant& cellValue, KexiTableEdit *edit, + KexiTableItem *item, KexiTableViewColumn *tvcol) const +{ + if (edit->columnInfo() && edit->columnInfo()->indexForVisibleLookupValue()!=-1 + && edit->columnInfo()->indexForVisibleLookupValue() < (int)item->count()) + { + const QVariant *visibleFieldValue = 0; + if (m_currentItem == item && m_data->rowEditBuffer()) { + visibleFieldValue = m_data->rowEditBuffer()->at( + *tvcol->visibleLookupColumnInfo, false/*!useDefaultValueIfPossible*/ ); + } + + if (visibleFieldValue) + //(use bufferedValueAt() - try to get buffered visible value for lookup field) + cellValue = *visibleFieldValue; //txt = visibleFieldValue->toString(); + else + cellValue /*txt*/ = item->at( edit->columnInfo()->indexForVisibleLookupValue() ); //.toString(); + return true; + } + return false; +} + +//reimpl. +void KexiTableView::removeEditor() +{ + if (!m_editor) + return; + KexiDataAwareObjectInterface::removeEditor(); + viewport()->setFocus(); +} + +void KexiTableView::slotRowRepaintRequested(KexiTableItem& item) +{ + updateRow( m_data->findRef(&item) ); +} + +//(js) unused +void KexiTableView::slotAutoScroll() +{ + kdDebug(44021) << "KexiTableView::slotAutoScroll()" <<endl; + if (!d->needAutoScroll) + return; + + switch(d->scrollDirection) + { + case ScrollDown: + setCursorPosition(m_curRow + 1, m_curCol); + break; + + case ScrollUp: + setCursorPosition(m_curRow - 1, m_curCol); + break; + case ScrollLeft: + setCursorPosition(m_curRow, m_curCol - 1); + break; + + case ScrollRight: + setCursorPosition(m_curRow, m_curCol + 1); + break; + } +} + +#ifndef KEXI_NO_PRINT +void +KexiTableView::print(KPrinter &/*printer*/) +{ +// printer.setFullPage(true); +#if 0 + int leftMargin = printer.margins().width() + 2 + d->rowHeight; + int topMargin = printer.margins().height() + 2; +// int bottomMargin = topMargin + ( printer.realPageSize()->height() * printer.resolution() + 36 ) / 72; + int bottomMargin = 0; + kdDebug(44021) << "KexiTableView::print: bottom = " << bottomMargin << endl; + + QPainter p(&printer); + + KexiTableItem *i; + int width = leftMargin; + for(int col=0; col < columns(); col++) + { + p.fillRect(width, topMargin - d->rowHeight, columnWidth(col), d->rowHeight, QBrush(Qt::gray)); + p.drawRect(width, topMargin - d->rowHeight, columnWidth(col), d->rowHeight); + p.drawText(width, topMargin - d->rowHeight, columnWidth(col), d->rowHeight, Qt::AlignLeft | Qt::AlignVCenter, + m_horizontalHeader->label(col)); + width = width + columnWidth(col); + } + + int yOffset = topMargin; + int row = 0; + int right = 0; + for(i = m_data->first(); i; i = m_data->next()) + { + if(!i->isInsertItem()) + { kdDebug(44021) << "KexiTableView::print: row = " << row << " y = " << yOffset << endl; + int xOffset = leftMargin; + for(int col=0; col < columns(); col++) + { + kdDebug(44021) << "KexiTableView::print: col = " << col << " x = " << xOffset << endl; + p.saveWorldMatrix(); + p.translate(xOffset, yOffset); + paintCell(&p, i, col, QRect(0, 0, columnWidth(col) + 1, d->rowHeight), true); + p.restoreWorldMatrix(); +// p.drawRect(xOffset, yOffset, columnWidth(col), d->rowHeight); + xOffset = xOffset + columnWidth(col); + right = xOffset; + } + + row++; + yOffset = topMargin + row * d->rowHeight; + } + + if(yOffset > 900) + { + p.drawLine(leftMargin, topMargin, leftMargin, yOffset); + p.drawLine(leftMargin, topMargin, right - 1, topMargin); + printer.newPage(); + yOffset = topMargin; + row = 0; + } + } + p.drawLine(leftMargin, topMargin, leftMargin, yOffset); + p.drawLine(leftMargin, topMargin, right - 1, topMargin); + +// p.drawLine(60,60,120,150); + p.end(); +#endif +} +#endif + +QString KexiTableView::columnCaption(int colNum) const +{ + return m_horizontalHeader->label(colNum); +} + +KexiDB::Field* KexiTableView::field(int colNum) const +{ + if (!m_data || !m_data->column(colNum)) + return 0; + return m_data->column(colNum)->field(); +} + +void KexiTableView::adjustColumnWidthToContents(int colNum) +{ + if (!hasData()) + return; + if (colNum==-1) { + const int cols = columns(); + for (int i=0; i<cols; i++) + adjustColumnWidthToContents(i); + return; + } + + int indexOfVisibleColumn = (m_data->column(colNum) && m_data->column(colNum)->columnInfo) + ? m_data->column(colNum)->columnInfo->indexForVisibleLookupValue() : -1; + if (-1==indexOfVisibleColumn) + indexOfVisibleColumn = colNum; + + if (indexOfVisibleColumn < 0) + return; + + QPtrListIterator<KexiTableItem> it = m_data->iterator(); + if (it.current() && it.current()->count()<=(uint)indexOfVisibleColumn) + return; + + KexiCellEditorFactoryItem *item = KexiCellEditorFactory::item( columnType(indexOfVisibleColumn) ); + if (!item) + return; + QFontMetrics fm(fontMetrics()); + int maxw = horizontalHeaderVisible() + ? fm.width( m_horizontalHeader->label( colNum/* not indexOfVisibleColumn*/ ) ) : 0; + if (maxw == 0 && m_data->isEmpty()) + return; //nothing to adjust + +//! \todo js: this is NOT EFFECTIVE for big data sets!!!! + + KexiTableEdit *ed = tableEditorWidget( colNum/* not indexOfVisibleColumn*/ ); + if (ed) { + for (it = m_data->iterator(); it.current(); ++it) { + const int wfw = ed->widthForValue( it.current()->at( indexOfVisibleColumn ), fm ); + maxw = QMAX( maxw, wfw ); + } + const bool focused = currentColumn() == colNum; + maxw += (fm.width(" ") + ed->leftMargin() + ed->rightMargin(focused)); + } + if (maxw < KEXITV_MINIMUM_COLUMN_WIDTH ) + maxw = KEXITV_MINIMUM_COLUMN_WIDTH; //not too small + kexidbg << "KexiTableView: setColumnWidth(colNum=" << colNum + << ", indexOfVisibleColumn=" << indexOfVisibleColumn << ", width=" << maxw <<" )" << endl; + setColumnWidth( colNum/* not indexOfVisibleColumn*/, maxw ); +} + +void KexiTableView::setColumnWidth(int colNum, int width) +{ + if (columns()<=colNum || colNum < 0) + return; + const int oldWidth = m_horizontalHeader->sectionSize( colNum ); + m_horizontalHeader->resizeSection( colNum, width ); + slotTopHeaderSizeChange( colNum, oldWidth, m_horizontalHeader->sectionSize( colNum ) ); +} + +void KexiTableView::maximizeColumnsWidth( const QValueList<int> &columnList ) +{ + if (!isVisible()) { + d->maximizeColumnsWidthOnShow += columnList; + return; + } + if (width() <= m_horizontalHeader->headerWidth()) + return; + //sort the list and make it unique + QValueList<int> cl, sortedList = columnList; + qHeapSort(sortedList); + int i=-999; + + QValueList<int>::ConstIterator it, end( sortedList.constEnd() ); + for ( it = sortedList.constBegin(); it != end; ++it) { + if (i != (*it)) { + cl += (*it); + i = (*it); + } + } + //resize + int sizeToAdd = (width() - m_horizontalHeader->headerWidth()) / cl.count() - verticalHeader()->width(); + if (sizeToAdd<=0) + return; + end = cl.constEnd(); + for ( it = cl.constBegin(); it != end; ++it) { + int w = m_horizontalHeader->sectionSize(*it); + if (w>0) { + m_horizontalHeader->resizeSection(*it, w+sizeToAdd); + } + } + updateContents(); + editorShowFocus( m_curRow, m_curCol ); +} + +void KexiTableView::adjustHorizontalHeaderSize() +{ + m_horizontalHeader->adjustHeaderSize(); +} + +void KexiTableView::setColumnStretchEnabled( bool set, int colNum ) +{ + m_horizontalHeader->setStretchEnabled( set, colNum ); +} + +void KexiTableView::setEditableOnDoubleClick(bool set) +{ + d->editOnDoubleClick = set; +} +bool KexiTableView::editableOnDoubleClick() const +{ + return d->editOnDoubleClick; +} + +bool KexiTableView::verticalHeaderVisible() const +{ + return m_verticalHeader->isVisible(); +} + +void KexiTableView::setVerticalHeaderVisible(bool set) +{ + int left_width; + if (set) { + m_verticalHeader->show(); + left_width = QMIN(m_horizontalHeader->sizeHint().height(), d->rowHeight); + } + else { + m_verticalHeader->hide(); + left_width = 0; + } + setMargins( left_width, horizontalHeaderVisible() ? m_horizontalHeader->sizeHint().height() : 0, 0, 0); +} + +bool KexiTableView::horizontalHeaderVisible() const +{ + return d->horizontalHeaderVisible; +} + +void KexiTableView::setHorizontalHeaderVisible(bool set) +{ + int top_height; + d->horizontalHeaderVisible = set; //needed because isVisible() is not always accurate + if (set) { + m_horizontalHeader->show(); + top_height = m_horizontalHeader->sizeHint().height(); + } + else { + m_horizontalHeader->hide(); + top_height = 0; + } + setMargins( verticalHeaderVisible() ? m_verticalHeader->width() : 0, top_height, 0, 0); +} + +void KexiTableView::triggerUpdate() +{ +// kdDebug(44021) << "KexiTableView::triggerUpdate()" << endl; +// if (!d->pUpdateTimer->isActive()) + d->pUpdateTimer->start(20, true); +// d->pUpdateTimer->start(200, true); +} + +void KexiTableView::setHBarGeometry( QScrollBar & hbar, int x, int y, int w, int h ) +{ +/*todo*/ + kdDebug(44021)<<"KexiTableView::setHBarGeometry"<<endl; + if (d->appearance.navigatorEnabled) { + m_navPanel->setHBarGeometry( hbar, x, y, w, h ); + } + else { + hbar.setGeometry( x , y, w, h ); + } +} + +void KexiTableView::setSpreadSheetMode() +{ + KexiDataAwareObjectInterface::setSpreadSheetMode(); + //copy m_navPanelEnabled flag + Appearance a = d->appearance; + a.navigatorEnabled = m_navPanelEnabled; + setAppearance( a ); +} + +int KexiTableView::validRowNumber(const QString& text) +{ + bool ok=true; + int r = text.toInt(&ok); + if (!ok || r<1) + r = 1; + else if (r > (rows()+(isInsertingEnabled()?1:0))) + r = rows()+(isInsertingEnabled()?1:0); + return r-1; +} + +void KexiTableView::moveToRecordRequested( uint r ) +{ + if (r > uint(rows()+(isInsertingEnabled()?1:0))) + r = rows()+(isInsertingEnabled()?1:0); + setFocus(); + selectRow( r ); +} + +void KexiTableView::moveToLastRecordRequested() +{ + setFocus(); + selectRow(rows()>0 ? (rows()-1) : 0); +} + +void KexiTableView::moveToPreviousRecordRequested() +{ + setFocus(); + selectPrevRow(); +} + +void KexiTableView::moveToNextRecordRequested() +{ + setFocus(); + selectNextRow(); +} + +void KexiTableView::moveToFirstRecordRequested() +{ + setFocus(); + selectFirstRow(); +} + +void KexiTableView::copySelection() +{ + if (m_currentItem && m_curCol!=-1) { + KexiTableEdit *edit = tableEditorWidget( m_curCol ); + QVariant defaultValue; + const bool defaultValueDisplayed + = isDefaultValueDisplayed(m_currentItem, m_curCol, &defaultValue); + if (edit) { + QVariant visibleValue; + getVisibleLookupValue(visibleValue, edit, m_currentItem, m_data->column(m_curCol)); + edit->handleCopyAction( + defaultValueDisplayed ? defaultValue : m_currentItem->at( m_curCol ), + visibleValue ); + } + } +} + +void KexiTableView::cutSelection() +{ + //try to handle @ editor's level + KexiTableEdit *edit = tableEditorWidget( m_curCol ); + if (edit) + edit->handleAction("edit_cut"); +} + +void KexiTableView::paste() +{ + //try to handle @ editor's level + KexiTableEdit *edit = tableEditorWidget( m_curCol ); + if (edit) + edit->handleAction("edit_paste"); +} + +bool KexiTableView::eventFilter( QObject *o, QEvent *e ) +{ + //don't allow to stole key my events by others: +// kexidbg << "spontaneous " << e->spontaneous() << " type=" << e->type() << endl; + + if (e->type()==QEvent::KeyPress) { + if (e->spontaneous() /*|| e->type()==QEvent::AccelOverride*/) { + QKeyEvent *ke = static_cast<QKeyEvent*>(e); + const int k = ke->key(); + int s = ke->state(); + //cell editor's events: + //try to handle the event @ editor's level + KexiTableEdit *edit = tableEditorWidget( m_curCol ); + if (edit && edit->handleKeyPress(ke, m_editor==edit)) { + ke->accept(); + return true; + } + else if (m_editor && (o==dynamic_cast<QObject*>(m_editor) || o==m_editor->widget())) { + if ( (k==Qt::Key_Tab && (s==Qt::NoButton || s==Qt::ShiftButton)) + || (overrideEditorShortcutNeeded(ke)) + || (k==Qt::Key_Enter || k==Qt::Key_Return || k==Qt::Key_Up || k==Qt::Key_Down) + || (k==Qt::Key_Left && m_editor->cursorAtStart()) + || (k==Qt::Key_Right && m_editor->cursorAtEnd()) + ) + { + //try to steal the key press from editor or it's internal widget... + keyPressEvent(ke); + if (ke->isAccepted()) + return true; + } + } + /* + else if (e->type()==QEvent::KeyPress && (o==this || (m_editor && o==m_editor->widget()))){//|| o==viewport()) + keyPressEvent(ke); + if (ke->isAccepted()) + return true; + }*/ +/*todo else if ((k==Qt::Key_Tab || k==(Qt::SHIFT|Qt::Key_Tab)) && o==d->navRowNumber) { + //tab key focuses tv + ke->accept(); + setFocus(); + return true; + }*/ + } + } + else if (o==horizontalScrollBar()) { + if ((e->type()==QEvent::Show && !horizontalScrollBar()->isVisible()) + || (e->type()==QEvent::Hide && horizontalScrollBar()->isVisible())) { + updateWidgetContentsSize(); + } + } + else if (e->type()==QEvent::Leave) { + if (o==viewport() && d->appearance.rowMouseOverHighlightingEnabled + && d->appearance.persistentSelections) + { + if (d->highlightedRow!=-1) { + int oldRow = d->highlightedRow; + d->highlightedRow = -1; + updateRow(oldRow); + const bool dontPaintNonpersistentSelectionBecauseDifferentRowHasBeenHighlighted + = d->appearance.rowHighlightingEnabled && !d->appearance.persistentSelections; + if (oldRow!=m_curRow && m_curRow>=0) { + if (!dontPaintNonpersistentSelectionBecauseDifferentRowHasBeenHighlighted) + //no highlight for now: show selection again + updateRow(m_curRow); + m_verticalHeader->setHighlightedRow(-1); + } + } + } + d->recentCellWithToolTip = QPoint(-1,-1); + } +/* else if (e->type()==QEvent::FocusOut && o->inherits("QWidget")) { + //hp==true if currently focused widget is a child of this table view + const bool hp = KexiUtils::hasParent( static_cast<QWidget*>(o), focusWidget()); + if (!hp && KexiUtils::hasParent( this, static_cast<QWidget*>(o))) { + //accept row editing if focus is moved to foreign widget + //(not a child, like eg. editor) from one of our table view's children + //or from table view itself + if (!acceptRowEdit()) { + static_cast<QWidget*>(o)->setFocus(); + return true; + } + } + }*/ + return QScrollView::eventFilter(o,e); +} + +void KexiTableView::slotTopHeaderSizeChange( + int /*section*/, int /*oldSize*/, int /*newSize*/ ) +{ + editorShowFocus( m_curRow, m_curCol ); +} + +void KexiTableView::setBottomMarginInternal(int pixels) +{ + d->internal_bottomMargin = pixels; +} + +void KexiTableView::paletteChange( const QPalette &oldPalette ) +{ + Q_UNUSED(oldPalette); + //update: + if (m_verticalHeader) + m_verticalHeader->setSelectionBackgroundColor( palette().active().highlight() ); + if (m_horizontalHeader) + m_horizontalHeader->setSelectionBackgroundColor( palette().active().highlight() ); +} + +const KexiTableView::Appearance& KexiTableView::appearance() const +{ + return d->appearance; +} + +void KexiTableView::setAppearance(const Appearance& a) +{ +// if (d->appearance.fullRowSelection != a.fullRowSelection) { + if (a.fullRowSelection) { + d->rowHeight -= 1; + } + else { + d->rowHeight += 1; + } + if (m_verticalHeader) + m_verticalHeader->setCellHeight(d->rowHeight); + if (m_horizontalHeader) { + setMargins( + QMIN(m_horizontalHeader->sizeHint().height(), d->rowHeight), + m_horizontalHeader->sizeHint().height(), 0, 0); + } +// } + if (a.rowHighlightingEnabled) + m_updateEntireRowWhenMovingToOtherRow = true; + + if(!a.navigatorEnabled) + m_navPanel->hide(); + else + m_navPanel->show(); +// } + + d->highlightedRow = -1; +//! @todo is setMouseTracking useful for other purposes? + viewport()->setMouseTracking(a.rowMouseOverHighlightingEnabled); + + d->appearance = a; + + setFont(font()); //this also updates contents +} + +int KexiTableView::highlightedRow() const +{ + return d->highlightedRow; +} + +void KexiTableView::setHighlightedRow(int row) +{ + if (row!=-1) { + row = QMIN(rows() - 1 + (isInsertingEnabled()?1:0), row); + row = QMAX(0, row); + ensureCellVisible(row, -1); + } + const int previouslyHighlightedRow = d->highlightedRow; + if (previouslyHighlightedRow == row) { + if (previouslyHighlightedRow!=-1) + updateRow(previouslyHighlightedRow); + return; + } + d->highlightedRow = row; + if (d->highlightedRow!=-1) + updateRow(d->highlightedRow); + + if (previouslyHighlightedRow!=-1) + updateRow(previouslyHighlightedRow); + + if (m_curRow>=0 && (previouslyHighlightedRow==-1 || previouslyHighlightedRow==m_curRow) + && d->highlightedRow!=m_curRow && !d->appearance.persistentSelections) + { + //currently selected row needs to be repainted + updateRow(m_curRow); + } +} + +KexiTableItem *KexiTableView::highlightedItem() const +{ + return d->highlightedRow == -1 ? 0 : m_data->at(d->highlightedRow); +} + +void KexiTableView::slotSettingsChanged(int category) +{ + if (category==KApplication::SETTINGS_SHORTCUTS) { + d->contextMenuKey = KGlobalSettings::contextMenuKey(); + } +} + +int KexiTableView::lastVisibleRow() const +{ + return rowAt( contentsY() ); +} + +#include "kexitableview.moc" + diff --git a/kexi/widget/tableview/kexitableview.h b/kexi/widget/tableview/kexitableview.h new file mode 100644 index 00000000..9f9c632e --- /dev/null +++ b/kexi/widget/tableview/kexitableview.h @@ -0,0 +1,639 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Till Busch <till@bux.at> + Copyright (C) 2003 Lucijan Busch <lucijan@gmx.at> + Copyright (C) 2003 Daniel Molkentin <molkentin@kde.org> + Copyright (C) 2003 Joseph Wenninger <jowenn@kde.org> + Copyright (C) 2003-2006 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + + Original Author: Till Busch <till@bux.at> + Original Project: buX (www.bux.at) +*/ + +#ifndef KEXITABLEVIEW_H +#define KEXITABLEVIEW_H + +#include <qscrollview.h> +#include <qvariant.h> +#include <qptrlist.h> +#include <qheader.h> +#include <qtooltip.h> + +#include <kdebug.h> + +#include "kexitableviewdata.h" +#include "kexitableedit.h" +#include <kexiutils/tristate.h> +#include <widget/utils/kexirecordnavigator.h> +#include <widget/utils/kexisharedactionclient.h> +#include "kexidataawareobjectiface.h" + +class KPopupMenu; +class KPrinter; +class KAction; + +class KexiTableHeader; +class KexiTableItem; +class KexiTableView; +class KexiTableEdit; +class KexiTableViewPrivate; +class KActionCollection; + +namespace KexiDB { + class RowEditBuffer; +} + +//! minimum column width in pixels +#define KEXITV_MINIMUM_COLUMN_WIDTH 10 + +//! @short KexiTableView class provides a widget for displaying data in a tabular view. +/*! @see KexiFormScrollView +*/ +class KEXIDATATABLE_EXPORT KexiTableView : + public QScrollView, + public KexiRecordNavigatorHandler, + public KexiSharedActionClient, + public KexiDataAwareObjectInterface +{ +Q_OBJECT +KEXI_DATAAWAREOBJECTINTERFACE +public: + + /*! Defines table view's detailed appearance settings. */ + class KEXIDATATABLE_EXPORT Appearance { + public: + Appearance(QWidget *widget = 0); + + /*! base color for cells, default is "Base" color for application's + current active palette */ + QColor baseColor; + + /*! text color for cells, default is "Text" color for application's + current active palette */ + QColor textColor; + + /*! border color for cells, default is QColor(200,200,200) */ + QColor borderColor; + + /*! empty area color, default is "Base" color for application's + current active palette */ + QColor emptyAreaColor; + + /*! alternate background color, default is KGlobalSettings::alternateBackgroundColor() */ + QColor alternateBackgroundColor; + + /*! true if background altering should be enabled, true by default */ + bool backgroundAltering : 1; + + /*! true if full-row-selection mode is set, + what means that all cells of the current row are always selected, instead of single cell. + This mode is usable for read-only table views, when we're interested only in navigating + by rows. False by default, even for read-only table views. + */ + bool fullRowSelection : 1; + + /*! true if fullgrid is enabled. True by default. + It is set to false for comboboxpopup table, to mimic original + combobox look and feel. */ + bool gridEnabled : 1; + + /*! \if the navigation panel is enabled (visible) for the view. + True by default. */ + bool navigatorEnabled : 1; + + /*! true if "row highlight" behaviour is enabled. False by default. */ + bool rowHighlightingEnabled : 1; + + /*! true if "row highlight over " behaviour is enabled. False by default. */ + bool rowMouseOverHighlightingEnabled : 1; + + /*! true if selection of a row should be kept when a user moved mouse + pointer over other rows. Makes only sense when rowMouseOverHighlightingEnabled is true. + True by default. It is set to false for comboboxpopup table, to mimic original + combobox look and feel. */ + bool persistentSelections : 1; + + /*! color for row highlight, default is intermediate (33%/60%) between + active highlight and base color. */ + QColor rowHighlightingColor; + + /*! color for text under row highlight, default is the same as textColor. + Used when rowHighlightingEnabled is true; */ + QColor rowHighlightingTextColor; + + /*! color for row highlight for mouseover, default is intermediate (20%/80%) between + active highlight and base color. Used when rowMouseOverHighlightingEnabled is true. */ + QColor rowMouseOverHighlightingColor; + + /*! color for text under row highlight for mouseover, default is the same as textColor. + Used when rowMouseOverHighlightingEnabled is true; */ + QColor rowMouseOverHighlightingTextColor; + + /*! Like rowMouseOverHighlightingColor but for areas painted with alternate color. + This is computed using active highlight color and alternateBackgroundColor. */ + QColor rowMouseOverAlternateHighlightingColor; + }; + + KexiTableView(KexiTableViewData* data=0, QWidget* parent=0, const char* name=0); + virtual ~KexiTableView(); + + /*! \return current appearance settings */ + const Appearance& appearance() const; + + /*! Sets appearance settings. Table view is updated automatically. */ + void setAppearance(const Appearance& a); + + /*! \return string displayed for column's header \a colNum */ + QString columnCaption(int colNum) const; + + /*! Convenience function. + \return field object that define column \a colNum or NULL if there is no such column */ + KexiDB::Field* field(int colNum) const; + + /*! Reimplementation for KexiDataAwareObjectInterface */ + virtual void setSpreadSheetMode(); + + /*! \return true if vertical scrollbar's tooltips are enabled (true by default). */ +//moved bool scrollbarToolTipsEnabled() const; + + /*! Enables or disables vertical scrollbar's. */ +//moved void setScrollbarToolTipsEnabled(bool set); + + /*! \return maximum number of rows that can be displayed per one "page" + for current table view's size. */ + virtual int rowsPerPage() const; + + QRect cellGeometry(int row, int col) const; + int columnWidth(int col) const; + int rowHeight() const; + int columnPos(int col) const; + int rowPos(int row) const; + int columnAt(int pos) const; + int rowAt(int pos, bool ignoreEnd=false) const; + + /*! \return last row visible on the screen (counting from 0). + The returned value is guaranteed to be smaller or equal to currentRow() or -1 + if there are no rows. */ + virtual int lastVisibleRow() const; + + /*! Redraws specified cell. */ + virtual void updateCell(int row, int col); + + /*! Redraws the current cell. Implemented after KexiDataAwareObjectInterface. */ + virtual void updateCurrentCell(); + + /*! Redraws all cells of specified row. */ + virtual void updateRow(int row); + + bool editableOnDoubleClick() const; + void setEditableOnDoubleClick(bool set); + + //! \return true if the vertical header is visible + bool verticalHeaderVisible() const; + + //! Sets vertical header's visibility + void setVerticalHeaderVisible(bool set); + + //! \return true if the horizontal header is visible + bool horizontalHeaderVisible() const; + + //! Sets horizontal header's visibility + void setHorizontalHeaderVisible(bool set); + +#ifndef KEXI_NO_PRINT + // printing +// void setupPrinter(KPrinter &printer); + void print(KPrinter &printer); +#endif + + // reimplemented for internal reasons + virtual QSizePolicy sizePolicy() const; + virtual QSize sizeHint() const; + virtual QSize minimumSizeHint() const; + + /*! Reimplemented to update cached fonts and row sizes for the painter. */ + void setFont(const QFont &f); + + virtual QSize tableSize() const; + + void emitSelected(); + + //! single shot after 1ms for contents updatinh + void triggerUpdate(); + + typedef enum ScrollDirection + { + ScrollUp, + ScrollDown, + ScrollLeft, + ScrollRight + }; + + virtual bool eventFilter( QObject *o, QEvent *e ); + + //! Initializes standard editor cell editor factories. This is called internally, once. + static void initCellEditorFactories(); + + /*! \return highlighted row number or -1 if no row is highlighted. + Makes sense if row highlighting is enabled. + @see Appearance::rowHighlightingEnabled setHighlightedRow() */ + int highlightedRow() const; + + KexiTableItem *highlightedItem() const; + + /*! \return vertical scrollbar. Implemented for KexiDataAwareObjectInterface. */ + virtual QScrollBar* verticalScrollBar() const { return QScrollView::verticalScrollBar(); } + +public slots: + virtual void setData( KexiTableViewData *data, bool owner = true ) + { KexiDataAwareObjectInterface::setData( data, owner ); } + + virtual void clearColumnsInternal(bool repaint); + + /*! Adjusts \a colNum column's width to its (current) contents. + If \a colNum == -1, all columns' width is adjusted. */ + void adjustColumnWidthToContents(int colNum = -1); + + //! Sets width of column width to \a width. + void setColumnWidth(int col, int width); + + /*! If \a set is true, \a colNum column is resized to take full possible width. + If \a set is false, no automatic resize will be performed. + If \a colNum is -1, all columns are equally resized, when needed, to take full possible width. + This method behaves like QHeader::setStretchEnabled ( bool b, int section ). */ + void setColumnStretchEnabled( bool set, int colNum ); + + /*! Maximizes widths of columns selected by \a columnList, so the horizontal + header has maximum overall width. Each selected column's width will be increased + by the same value. Does nothing if \a columnList is empty or there is no free space + to resize columns. If this table view is not visible, resizing will be performed on showing. */ + void maximizeColumnsWidth( const QValueList<int> &columnList ); + + /*! Adjusts the size of the sections to fit the size of the horizontal header + as completely as possible. Only sections for which column stretch is enabled will be resized. + \sa setColumnStretchEnabled() QHeader::adjustHeaderSize() */ + void adjustHorizontalHeaderSize(); + + /*! Sets highlighted row number or -1 if no row has to be highlighted. + Makes sense if row highlighting is enabled. + @see Appearance::rowHighlightingEnabled */ + void setHighlightedRow(int row); + + /*! Sets no row that will be highlighted. Equivalent to setHighlightedRow(-1). */ + inline void clearHighlightedRow() { setHighlightedRow(-1); } + + /*! Ensures that cell at \a row and \a col is visible. + If \a col is -1, current column number is used. \a row and \a col (if not -1) must + be between 0 and rows() (or cols() accordingly). */ + virtual void ensureCellVisible(int row, int col/*=-1*/); + +// void gotoNext(); +//js int findString(const QString &string); + + /*! Deletes currently selected record; does nothing if no record + is currently selected. If record is in edit mode, editing + is cancelled before deleting. */ + virtual void deleteCurrentRow() { KexiDataAwareObjectInterface::deleteCurrentRow(); } + + /*! Inserts one empty row above row \a row. If \a row is -1 (the default), + new row is inserted above the current row (or above 1st row if there is no current). + A new item becomes current if row is -1 or if row is equal currentRow(). + This method does nothing if: + -inserting flag is disabled (see isInsertingEnabled()) + -read-only flag is set (see isReadOnly()) + \ return inserted row's data + */ + virtual KexiTableItem *insertEmptyRow(int row = -1) + { return KexiDataAwareObjectInterface::insertEmptyRow(row); } + + /*! Used when Return key is pressed on cell or "+" nav. button is clicked. + Also used when we want to continue editing a cell after "invalid value" message + was displayed (in this case, \a setText is usually not empty, what means + that text will be set in the cell replacing previous value). + */ + virtual void startEditCurrentCell(const QString& setText = QString::null) + { KexiDataAwareObjectInterface::startEditCurrentCell(setText); } + + /*! Deletes currently selected cell's contents, if allowed. + In most cases delete is not accepted immediately but "row editing" mode is just started. */ + virtual void deleteAndStartEditCurrentCell() + { KexiDataAwareObjectInterface::deleteAndStartEditCurrentCell(); } + + /*! Cancels row editing All changes made to the editing + row during this current session will be undone. + \return true on success or false on failure (e.g. when editor does not exist) */ + virtual bool cancelRowEdit() { return KexiDataAwareObjectInterface::cancelRowEdit(); } + + /*! Accepts row editing. All changes made to the editing + row during this current session will be accepted (saved). + \return true if accepting was successful, false otherwise + (e.g. when current row contain data that does not meet given constraints). */ + virtual bool acceptRowEdit() { return KexiDataAwareObjectInterface::acceptRowEdit(); } + + /*! Specifies, if this table view automatically accepts + row editing (using acceptRowEdit()) on accepting any cell's edit + (i.e. after acceptEditor()). \sa acceptsRowEditAfterCellAccepting() */ + virtual void setAcceptsRowEditAfterCellAccepting(bool set) + { KexiDataAwareObjectInterface::setAcceptsRowEditAfterCellAccepting(set); } + + /*! Specifies, if this table accepts dropping data on the rows. + If enabled: + - dragging over row is indicated by drawing a line at bottom side of this row + - dragOverRow() signal will be emitted on dragging, + -droppedAtRow() will be emitted on dropping + By default this flag is set to false. */ + virtual void setDropsAtRowEnabled(bool set) { KexiDataAwareObjectInterface::setDropsAtRowEnabled(set); } + + virtual bool cancelEditor() { return KexiDataAwareObjectInterface::cancelEditor(); } + virtual bool acceptEditor() { return KexiDataAwareObjectInterface::acceptEditor(); } + +signals: + virtual void dataSet( KexiTableViewData *data ); + + virtual void itemSelected(KexiTableItem *); + virtual void cellSelected(int col, int row); + + void itemReturnPressed(KexiTableItem *, int row, int col); + void itemDblClicked(KexiTableItem *, int row, int col); + void itemMouseReleased(KexiTableItem *, int row, int col); + + void dragOverRow(KexiTableItem *item, int row, QDragMoveEvent* e); + void droppedAtRow(KexiTableItem *item, int row, QDropEvent *e, KexiTableItem*& newItem); + + /*! Data has been refreshed on-screen - emitted from initDataContents(). */ + virtual void dataRefreshed(); + + virtual void itemChanged(KexiTableItem *, int row, int col); + virtual void itemChanged(KexiTableItem *, int row, int col, QVariant oldValue); + virtual void itemDeleteRequest(KexiTableItem *, int row, int col); + virtual void currentItemDeleteRequest(); + //! Emitted for spreadsheet mode when an item was deleted and a new item has been appended + virtual void newItemAppendedForAfterDeletingInSpreadSheetMode(); +// void addRecordRequest(); +// void contextMenuRequested(KexiTableItem *, int row, int col, const QPoint &); + void sortedColumnChanged(int col); + + //! emmited when row editing is started (for updating or inserting) + void rowEditStarted(int row); + + //! emmited when row editing is terminated (for updating or inserting) + //! no matter if accepted or not + void rowEditTerminated(int row); + + //! Emitted in initActions() to force reload actions + //! You should remove existing actions and add them again. + void reloadActions(); + +protected slots: + void slotSettingsChanged(int category); + + virtual void slotDataDestroying() { KexiDataAwareObjectInterface::slotDataDestroying(); } + + virtual void slotRowsDeleted( const QValueList<int> & ); + + //! updates display after many rows deletion + void slotColumnWidthChanged( int col, int os, int ns ); + + void slotSectionHandleDoubleClicked( int section ); + + void slotUpdate(); + //! implemented because we needed this as slot + virtual void sortColumnInternal(int col, int order = 0) + { KexiDataAwareObjectInterface::sortColumnInternal(col, order); } + + void slotAutoScroll(); + + //! internal, used when top header's size changed + void slotTopHeaderSizeChange( int section, int oldSize, int newSize ); + + //! receives a signal from cell editors + void slotEditRequested(); + + /*! Reloads data for this widget. + Handles KexiTableViewData::reloadRequested() signal. */ + virtual void reloadData(); + + //! Handles KexiTableViewData::rowRepaintRequested() signal + virtual void slotRowRepaintRequested(KexiTableItem& item); + + //! Handles KexiTableViewData::aboutToDeleteRow() signal. Prepares info for slotRowDeleted(). + virtual void slotAboutToDeleteRow(KexiTableItem& item, KexiDB::ResultInfo* result, bool repaint) + { KexiDataAwareObjectInterface::slotAboutToDeleteRow(item, result, repaint); } + + //! Handles KexiTableViewData::rowDeleted() signal to repaint when needed. + virtual void slotRowDeleted() { KexiDataAwareObjectInterface::slotRowDeleted(); } + + //! Handles KexiTableViewData::rowInserted() signal to repaint when needed. + virtual void slotRowInserted(KexiTableItem *item, bool repaint) + { KexiDataAwareObjectInterface::slotRowInserted(item, repaint); } + + //! Like above, not db-aware version + virtual void slotRowInserted(KexiTableItem *item, uint row, bool repaint) + { KexiDataAwareObjectInterface::slotRowInserted(item, row, repaint); } + + /*! Handles verticalScrollBar()'s valueChanged(int) signal. + Called when vscrollbar's value has been changed. */ + virtual void vScrollBarValueChanged(int v) { KexiDataAwareObjectInterface::vScrollBarValueChanged(v); } + + /*! Handles sliderReleased() signal of the verticalScrollBar(). Used to hide the "row number" tooltip. */ + virtual void vScrollBarSliderReleased() { KexiDataAwareObjectInterface::vScrollBarSliderReleased(); } + + /*! Handles timeout() signal of the m_scrollBarTipTimer. If the tooltip is visible, + m_scrollBarTipTimerCnt is set to 0 and m_scrollBarTipTimerCnt is restarted; + else the m_scrollBarTipTimerCnt is just set to 0.*/ + virtual void scrollBarTipTimeout() { KexiDataAwareObjectInterface::scrollBarTipTimeout(); } + +protected: + /*! Reimplementation for KexiDataAwareObjectInterface + Initializes data contents (resizes it, sets cursor at 1st row). + Called on setData(). Also called once on show event after + reloadRequested() signal was received from KexiTableViewData object. */ + virtual void initDataContents(); + + /*! Implementation for KexiDataAwareObjectInterface. + Updates widget's contents size using QScrollView::resizeContents() + depending on tableSize(). */ + virtual void updateWidgetContentsSize(); + + /*! Reimplementation for KexiDataAwareObjectInterface */ + virtual void clearVariables(); + + /*! Implementation for KexiDataAwareObjectInterface */ + virtual int currentLocalSortingOrder() const; + + /*! Implementation for KexiDataAwareObjectInterface */ + virtual int currentLocalSortColumn() const; + + /*! Implementation for KexiDataAwareObjectInterface */ + virtual void setLocalSortingOrder(int col, int order); + + /*! Implementation for KexiDataAwareObjectInterface */ + virtual void updateGUIAfterSorting(); + + /*! Implementation for KexiDataAwareObjectInterface */ + virtual void updateWidgetScrollBars() { updateScrollBars(); } + +// /*! Implementation for KexiDataAwareObjectInterface */ +// virtual void emitSortedColumnChanged(int col) { emit sortedColumnChanged(col); } + +// /*! Implementation for KexiDataAwareObjectInterface */ +// virtual void emitRowEditTerminated(int row) { emit rowEditTerminated(row); } + + /*! Implementation for KexiDataAwareObjectInterface. + Adds another section within the horizontal header. */ + virtual void addHeaderColumn(const QString& caption, const QString& description, + const QIconSet& icon, int size); + + /*! @internal \return true if the row defined by \a item has default + value at column \a col. If this is the case and \a value is not NULL, + *value is set to the default value. */ + bool isDefaultValueDisplayed(KexiTableItem *item, int col, QVariant* value = 0); + + //! painting and layout + void drawContents(QPainter *p, int cx, int cy, int cw, int ch); + void createBuffer(int width, int height); + void paintCell(QPainter* p, KexiTableItem *item, int col, int row, const QRect &cr, bool print=false); + void paintEmptyArea(QPainter *p, int cx, int cy, int cw, int ch); + void updateGeometries(); + + QPoint contentsToViewport2( const QPoint &p ); + void contentsToViewport2( int x, int y, int& vx, int& vy ); + QPoint viewportToContents2( const QPoint& vp ); + + // event handling + virtual void contentsMousePressEvent(QMouseEvent* e); + virtual void contentsMouseReleaseEvent(QMouseEvent* e); + //! @internal called by contentsMouseOrEvent() contentsMouseReleaseEvent() to move cursor + bool handleContentsMousePressOrRelease(QMouseEvent* e, bool release); + virtual void contentsMouseMoveEvent(QMouseEvent* e); + virtual void contentsMouseDoubleClickEvent(QMouseEvent* e); + virtual void keyPressEvent(QKeyEvent* e); + virtual void focusInEvent(QFocusEvent* e); + virtual void focusOutEvent(QFocusEvent* e); + virtual void resizeEvent(QResizeEvent* e); + virtual void viewportResizeEvent(QResizeEvent *e); + virtual void showEvent(QShowEvent *e); + virtual void contentsDragMoveEvent(QDragMoveEvent *e); + virtual void contentsDropEvent(QDropEvent *e); + virtual void viewportDragLeaveEvent(QDragLeaveEvent *e); + virtual void paletteChange( const QPalette &oldPalette ); + + /*! Implementation for KexiDataAwareObjectInterface */ + virtual KexiDataItemInterface *editor( int col, bool ignoreMissingEditor = false ); + + inline KexiTableEdit *tableEditorWidget( int col, bool ignoreMissingEditor = false ) + { return dynamic_cast<KexiTableEdit*>( editor( col, ignoreMissingEditor ) ); } + + /*! Implementation for KexiDataAwareObjectInterface */ + virtual void editorShowFocus( int row, int col ); + + //! Creates editors and shows it, what usually means the beginning of a cell editing + virtual void createEditor(int row, int col, const QString& addText = QString::null, + bool removeOld = false); + + bool focusNextPrevChild(bool next); + + /*! Used in key event: \return true if event \a e should execute action \a action_name. + Action shortcuts defined by shortCutPressed() are reused, if present, and if \a e matches + given action's shortcut - false is returned (beause action is already performed at main + window's level). + */ + bool shortCutPressed( QKeyEvent *e, const QCString &action_name ); + +#if 0 //we have now KexiActionProxy + /*! Updates visibility/accesibility of popup menu items, + returns false if no items are visible after update. */ + bool updateContextMenu(); +#endif + + /*! Shows context menu at \a pos for selected cell + if menu is configured, + else: contextMenuRequested() signal is emmited. + Method used in contentsMousePressEvent() (for right button) + and keyPressEvent() for Qt::Key_Menu key. + If \a pos is QPoint(-1,-1) (the default), menu is positioned below the current cell. + */ + void showContextMenu( const QPoint& pos = QPoint(-1,-1) ); + + /*! internal */ + inline void paintRow(KexiTableItem *item, + QPainter *pb, int r, int rowp, int cx, int cy, + int colfirst, int collast, int maxwc); + + virtual void setHBarGeometry( QScrollBar & hbar, int x, int y, int w, int h ); + + //! Setups navigator widget + void setupNavigator(); + + //! internal, to determine valid row number when navigator text changed + int validRowNumber(const QString& text); + + /*! Reimplementation for KexiDataAwareObjectInterface + (viewport()->setFocus() is just added) */ + virtual void removeEditor(); + + //! Internal: updated sched fonts for painting. + void updateFonts(bool repaint = false); + + /*! @internal Changes bottom margin settings, in pixels. + At this time, it's used by KexiComboBoxPopup to decrease margin for popup's table. */ + void setBottomMarginInternal(int pixels); + + virtual void updateWidgetContents() { update(); } + + //! for navigator + virtual void moveToRecordRequested(uint r); + virtual void moveToLastRecordRequested(); + virtual void moveToPreviousRecordRequested(); + virtual void moveToNextRecordRequested(); + virtual void moveToFirstRecordRequested(); + virtual void addNewRecordRequested() { KexiDataAwareObjectInterface::addNewRecordRequested(); } + + //! Copy current selection to a clipboard (e.g. cell) + virtual void copySelection(); + + //! Cut current selection to a clipboard (e.g. cell) + virtual void cutSelection(); + + //! Paste current clipboard contents (e.g. to a cell) + virtual void paste(); + + /*! Used in KexiDataAwareObjectInterface::slotRowDeleted() + to repaint tow \a row and all visible below. */ + virtual void updateAllVisibleRowsBelow(int row); + + void updateAfterCancelRowEdit(); + void updateAfterAcceptRowEdit(); + + /*! Sets \a cellValue if there is a lookup value for the cell \a item. + Used in KexiTableView::paintCell() and KexiTableViewCellToolTip::maybeTip() + \return true is \a cellValue has been found. */ + bool getVisibleLookupValue(QVariant& cellValue, KexiTableEdit *edit, + KexiTableItem *item, KexiTableViewColumn *tvcol) const; + +// //! Called to repaint contents after a row is deleted. +// void repaintAfterDelete(); + + KexiTableViewPrivate *d; + + class WhatsThis; + friend class KexiTableItem; + friend class WhatsThis; + friend class KexiTableViewCellToolTip; +}; + +#endif diff --git a/kexi/widget/tableview/kexitableview_p.cpp b/kexi/widget/tableview/kexitableview_p.cpp new file mode 100644 index 00000000..7cf774db --- /dev/null +++ b/kexi/widget/tableview/kexitableview_p.cpp @@ -0,0 +1,67 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Till Busch <till@bux.at> + Copyright (C) 2003 Lucijan Busch <lucijan@gmx.at> + Copyright (C) 2003 Daniel Molkentin <molkentin@kde.org> + Copyright (C) 2003 Joseph Wenninger <jowenn@kde.org> + Copyright (C) 2003-2004 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + + Original Author: Till Busch <till@bux.at> + Original Project: buX (www.bux.at) +*/ + +#include "kexitableview_p.h" +#include "kexitableedit.h" + +#include <qlabel.h> + +#include <kglobalsettings.h> + + +KexiTableViewPrivate::KexiTableViewPrivate(KexiTableView* t) + : appearance(t) +{ + clearVariables(); + tv = t; + editOnDoubleClick = true; + pBufferPm = 0; + disableDrawContents = false; + navigatorEnabled = true; + contextMenuEnabled = true; + skipKeyPress = false; +//moved vScrollBarValueChanged_enabled = true; +//moved scrollbarToolTipsEnabled = true; +//moved scrollBarTipTimerCnt = 0; +//moved scrollBarTip = 0; + ensureCellVisibleOnShow = QPoint(-1,-1); + internal_bottomMargin = tv->horizontalScrollBar()->sizeHint().height()/2; + highlightedRow = -1; + moveCursorOnMouseRelease = false; + horizontalHeaderVisible = true; + recentCellWithToolTip = QPoint(-1,-1); +} + +KexiTableViewPrivate::~KexiTableViewPrivate() +{ + delete pBufferPm; +//moved delete scrollBarTip; +} + +void KexiTableViewPrivate::clearVariables() +{ + // Initialize variables +} diff --git a/kexi/widget/tableview/kexitableview_p.h b/kexi/widget/tableview/kexitableview_p.h new file mode 100644 index 00000000..58fe8574 --- /dev/null +++ b/kexi/widget/tableview/kexitableview_p.h @@ -0,0 +1,155 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Till Busch <till@bux.at> + Copyright (C) 2003 Lucijan Busch <lucijan@gmx.at> + Copyright (C) 2003 Daniel Molkentin <molkentin@kde.org> + Copyright (C) 2003 Joseph Wenninger <jowenn@kde.org> + Copyright (C) 2003-2006 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + + Original Author: Till Busch <till@bux.at> + Original Project: buX (www.bux.at) +*/ + +#ifndef KEXITABLEVIEW_P_H +#define KEXITABLEVIEW_P_H + +#include "kexitableview.h" + +#include <kexidb/roweditbuffer.h> +#include <widget/utils/kexidisplayutils.h> + +#include <qevent.h> +#include <qtimer.h> +#include <qvalidator.h> +#include <qasciidict.h> + +#include <kpushbutton.h> +#include <ktoolbarbutton.h> +#include <klineedit.h> +#include <kpopupmenu.h> +#include <kaction.h> + +class KexiTableItem; +class KexiTableRM; +class KexiTableEdit; +class QLabel; +class KexiTableViewHeader; + +//! @short a dynamic tooltip for table view cells +/*! @internal */ +class KexiTableViewCellToolTip : public QToolTip +{ + public: + KexiTableViewCellToolTip( KexiTableView * tableView ); + virtual ~KexiTableViewCellToolTip(); + protected: + virtual void maybeTip( const QPoint & p ); + + KexiTableView *m_tableView; +}; + +/*! KexiTableView's internal structures + @internal */ +class KexiTableViewPrivate +{ + public: + + KexiTableViewPrivate(KexiTableView* t); + ~KexiTableViewPrivate(); + + void clearVariables(); + + KexiTableView *tv; + + //! editors: one for each column (indexed by KexiTableViewColumn) + QPtrDict<KexiTableEdit> editors; + + int rowHeight; + + QPixmap *pBufferPm; + QTimer *pUpdateTimer; + int menu_id_addRecord; + int menu_id_removeRecord; + +#if 0//(js) doesn't work! + QTimer *scrollTimer; +#endif + + KexiTableView::ScrollDirection scrollDirection; + + bool editOnDoubleClick : 1; + + bool needAutoScroll : 1; + + bool disableDrawContents : 1; + + /*! true if the navigation panel is enabled (visible) for the view. + True by default. */ + bool navigatorEnabled : 1; + + /*! true if the context menu is enabled (visible) for the view. + True by default. */ + bool contextMenuEnabled : 1; + + /*! used to force single skip keyPress event. */ + bool skipKeyPress : 1; + + /*! Needed because m_horizontalHeader->isVisible() is not always accurate. True by default. */ + bool horizontalHeaderVisible : 1; + + /*! true if cursor should be moved on mouse release evenr rather than mouse press + in handleContentsMousePressOrRelease(). + False by default. Used by KeixComboBoxPopup. */ + bool moveCursorOnMouseRelease : 1; + + KexiTableView::Appearance appearance; + + //! brushes, fonts + QBrush diagonalGrayPattern; + + //! Parameters for displaying autonumbers + KexiDisplayUtils::DisplayParameters autonumberSignDisplayParameters; + + //! Parameters for displaying default values + KexiDisplayUtils::DisplayParameters defaultValueDisplayParameters; + + //! Used by delayed mode of maximizeColumnsWidth() + QValueList<int> maximizeColumnsWidthOnShow; + + /*! Used for delayed call of ensureCellVisible() after show(). + It's equal to (-1,-1) if ensureCellVisible() shouldn't e called. */ + QPoint ensureCellVisibleOnShow; + + /*! @internal Changes bottom margin settings, in pixels. + At this time, it's used by KexiComboBoxPopup to decrease margin for popup's table. */ + int internal_bottomMargin; + + /*! Helper for "highlighted row" effect. */ + int highlightedRow; + + /*! Id of context menu key (cached). */ + int contextMenuKey; + + /*! Specifies currently displayed cell tooltip. + Value of QPoint(-1,-1) means "no tooltip". */ + QPoint recentCellWithToolTip; + + /*! Table cell tooltip */ + KexiTableViewCellToolTip *cellToolTip; +}; + +#endif diff --git a/kexi/widget/tableview/kexitableviewdata.cpp b/kexi/widget/tableview/kexitableviewdata.cpp new file mode 100644 index 00000000..62259db3 --- /dev/null +++ b/kexi/widget/tableview/kexitableviewdata.cpp @@ -0,0 +1,886 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Lucijan Busch <lucijan@gmx.at> + Copyright (C) 2003 Daniel Molkentin <molkentin@kde.org> + Copyright (C) 2003-2006 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + + Original Author: Till Busch <till@bux.at> + Original Project: buX (www.bux.at) +*/ + +#include "kexitableviewdata.h" + +#include <kexiutils/validator.h> + +#include <kexidb/field.h> +#include <kexidb/queryschema.h> +#include <kexidb/roweditbuffer.h> +#include <kexidb/cursor.h> +#include <kexidb/utils.h> +#include <kexi.h> + +#include <kdebug.h> +#include <klocale.h> + +#include <qapplication.h> + +unsigned short KexiTableViewData::charTable[]= +{ + #include "chartable.txt" +}; + +KexiTableViewColumn::KexiTableViewColumn(KexiDB::Field& f, bool owner) +: columnInfo(0) +, visibleLookupColumnInfo(0) +, m_field(&f) +{ + isDBAware = false; + m_fieldOwned = owner; + m_captionAliasOrName = m_field->captionOrName(); + init(); +} + +KexiTableViewColumn::KexiTableViewColumn(const QString& name, KexiDB::Field::Type ctype, + uint cconst, + uint options, + uint length, uint precision, + QVariant defaultValue, + const QString& caption, const QString& description, uint width +) +: columnInfo(0) +, visibleLookupColumnInfo(0) +{ + m_field = new KexiDB::Field( + name, ctype, + cconst, + options, + length, precision, + defaultValue, + caption, description, width); + + isDBAware = false; + m_fieldOwned = true; + m_captionAliasOrName = m_field->captionOrName(); + init(); +} + +KexiTableViewColumn::KexiTableViewColumn(const QString& name, KexiDB::Field::Type ctype, + const QString& caption, const QString& description) +: columnInfo(0) +, visibleLookupColumnInfo(0) +{ + m_field = new KexiDB::Field( + name, ctype, + KexiDB::Field::NoConstraints, + KexiDB::Field::NoOptions, + 0, 0, + QVariant(), + caption, description); + + isDBAware = false; + m_fieldOwned = true; + m_captionAliasOrName = m_field->captionOrName(); + init(); +} + +// db-aware +KexiTableViewColumn::KexiTableViewColumn( + const KexiDB::QuerySchema &query, KexiDB::QueryColumnInfo& aColumnInfo, + KexiDB::QueryColumnInfo* aVisibleLookupColumnInfo) +: columnInfo(&aColumnInfo) +, visibleLookupColumnInfo(aVisibleLookupColumnInfo) +, m_field(aColumnInfo.field) +{ + isDBAware = true; + m_fieldOwned = false; + + //setup column's caption: + if (!columnInfo->field->caption().isEmpty()) { + m_captionAliasOrName = columnInfo->field->caption(); + } + else { + //reuse alias if available: + m_captionAliasOrName = columnInfo->alias; + //last hance: use field name + if (m_captionAliasOrName.isEmpty()) + m_captionAliasOrName = columnInfo->field->name(); + //todo: compute other auto-name? + } + init(); + //setup column's readonly flag: true, if + // - it's not from parent table's field, or + // - if the query itself is coming from read-only connection, or + // - if the query itself is stored (i.e. has connection) and lookup column is defined + const bool columnFromMasterTable = query.masterTable()==columnInfo->field->table(); + m_readOnly = !columnFromMasterTable + || (query.connection() && query.connection()->isReadOnly()); +// || (query.connection() && (query.connection()->isReadOnly() || visibleLookupColumnInfo)); +//! @todo 2.0: remove this when queries become editable ^^^^^^^^^^^^^^ +// kdDebug() << "KexiTableViewColumn: query.masterTable()==" +// << (query.masterTable() ? query.masterTable()->name() : "notable") << ", columnInfo->field->table()==" +// << (columnInfo->field->table() ? columnInfo->field->table()->name() : "notable") << endl; + +// m_visible = query.isFieldVisible(&f); +} + +KexiTableViewColumn::KexiTableViewColumn(bool) +: columnInfo(0) +, visibleLookupColumnInfo(0) +, m_field(0) +{ + isDBAware = false; + init(); +} + +KexiTableViewColumn::~KexiTableViewColumn() +{ + if (m_fieldOwned) + delete m_field; + setValidator( 0 ); + delete m_relatedData; +} + +void KexiTableViewColumn::init() +{ + m_relatedData = 0; + m_readOnly = false; + m_visible = true; + m_data = 0; + m_validator = 0; + m_relatedDataEditable = false; + m_headerTextVisible = true; +} + +void KexiTableViewColumn::setValidator( KexiUtils::Validator* v ) +{ + if (m_validator) {//remove old one + if (!m_validator->parent()) //destroy if has no parent + delete m_validator; + } + m_validator = v; +} + +void KexiTableViewColumn::setRelatedData(KexiTableViewData *data) +{ + if (isDBAware) + return; + if (m_relatedData) + delete m_relatedData; + m_relatedData = 0; + if (!data) + return; + //find a primary key + KexiTableViewColumn::ListIterator it( data->columns ); + for (int id = 0;it.current();++it, id++) { + if (it.current()->field()->isPrimaryKey()) { + //found, remember + m_relatedDataPKeyID = id; + m_relatedData = data; + return; + } + } +} + +void KexiTableViewColumn::setRelatedDataEditable(bool set) +{ + m_relatedDataEditable = set; +} + +bool KexiTableViewColumn::isReadOnly() const +{ + return m_readOnly || (m_data && m_data->isReadOnly()); +} + +bool KexiTableViewColumn::acceptsFirstChar(const QChar& ch) const +{ + // the field we're looking at can be related to "visible lookup column" + // if lookup column is present + KexiDB::Field *visibleField = visibleLookupColumnInfo + ? visibleLookupColumnInfo->field : m_field; + if (visibleField->isNumericType()) { + if (ch=='.' || ch==',') + return visibleField->isFPNumericType(); + if (ch=='-') + return !visibleField->isUnsigned(); + if (ch=='+' || (ch>='0' && ch<='9')) + return true; + return false; + } + + switch (visibleField->type()) { + case KexiDB::Field::Boolean: + return false; + case KexiDB::Field::Date: + case KexiDB::Field::DateTime: + case KexiDB::Field::Time: + return ch>='0' && ch<='9'; + default:; + } + return true; +} + + +//------------------------------------------------------ + +KexiTableViewData::KexiTableViewData() + : QObject() + , KexiTableViewDataBase() +{ + init(); +} + +// db-aware ctor +KexiTableViewData::KexiTableViewData(KexiDB::Cursor *c) + : QObject() + , KexiTableViewDataBase() +{ + init(); + m_cursor = c; + m_containsROWIDInfo = m_cursor->containsROWIDInfo(); + if (m_cursor && m_cursor->query()) { + const KexiDB::QuerySchema::FieldsExpandedOptions fieldsExpandedOptions + = m_containsROWIDInfo ? KexiDB::QuerySchema::WithInternalFieldsAndRowID + : KexiDB::QuerySchema::WithInternalFields; + m_itemSize = m_cursor->query()->fieldsExpanded( fieldsExpandedOptions ).count(); + } + else + m_itemSize = columns.count()+(m_containsROWIDInfo?1:0); + + // Allocate KexiTableViewColumn objects for each visible query column + const KexiDB::QueryColumnInfo::Vector fields = m_cursor->query()->fieldsExpanded(); + const uint fieldsCount = fields.count(); + for (uint i=0;i < fieldsCount;i++) { + KexiDB::QueryColumnInfo *ci = fields[i]; + if (ci->visible) { + KexiDB::QueryColumnInfo *visibleLookupColumnInfo = 0; + if (ci->indexForVisibleLookupValue() != -1) { + //Lookup field is defined + visibleLookupColumnInfo = m_cursor->query()->expandedOrInternalField( ci->indexForVisibleLookupValue() ); + /* not needed + if (visibleLookupColumnInfo) { + // 2. Create a KexiTableViewData object for each found lookup field + }*/ + } + KexiTableViewColumn* col = new KexiTableViewColumn(*m_cursor->query(), *ci, visibleLookupColumnInfo); + addColumn( col ); + } + } +} + +KexiTableViewData::KexiTableViewData( + const QValueList<QVariant> &keys, const QValueList<QVariant> &values, + KexiDB::Field::Type keyType, KexiDB::Field::Type valueType) + : QObject() + , KexiTableViewDataBase() +{ + init(keys, values, keyType, valueType); +} + +KexiTableViewData::KexiTableViewData( + KexiDB::Field::Type keyType, KexiDB::Field::Type valueType) +{ + const QValueList<QVariant> empty; + init(empty, empty, keyType, valueType); +} + +KexiTableViewData::~KexiTableViewData() +{ + emit destroying(); + clearInternal(); +} + +void KexiTableViewData::init( + const QValueList<QVariant> &keys, const QValueList<QVariant> &values, + KexiDB::Field::Type keyType, KexiDB::Field::Type valueType) +{ + init(); + KexiDB::Field *keyField = new KexiDB::Field("key", keyType); + keyField->setPrimaryKey(true); + KexiTableViewColumn *keyColumn = new KexiTableViewColumn(*keyField, true); + keyColumn->setVisible(false); + addColumn(keyColumn); + + KexiDB::Field *valueField = new KexiDB::Field("value", valueType); + KexiTableViewColumn *valueColumn = new KexiTableViewColumn(*valueField, true); + addColumn(valueColumn); + + uint cnt = QMIN(keys.count(), values.count()); + QValueList<QVariant>::ConstIterator it_keys = keys.constBegin(); + QValueList<QVariant>::ConstIterator it_values = values.constBegin(); + for (;cnt>0;++it_keys, ++it_values, cnt--) { + KexiTableItem *item = new KexiTableItem(2); + (*item)[0] = (*it_keys); + (*item)[1] = (*it_values); + append( item ); + } +} + +void KexiTableViewData::init() +{ + m_sortedColumn = 0; + m_realSortedColumn = 0; +// m_order = 1; + m_order = 0; + m_type = 1; + m_pRowEditBuffer = 0; + m_cursor = 0; + m_readOnly = false; + m_insertingEnabled = true; + + setAutoDelete(true); + columns.setAutoDelete(true); + m_visibleColumnsCount=0; + m_visibleColumnsIDs.resize(100); + m_globalColumnsIDs.resize(100); + + m_autoIncrementedColumn = -2; + m_containsROWIDInfo = false; + m_itemSize = 0; +} + +void KexiTableViewData::deleteLater() +{ + m_cursor = 0; + QObject::deleteLater(); +} + +void KexiTableViewData::addColumn( KexiTableViewColumn* col ) +{ +// if (!col->isDBAware) { +// if (!m_simpleColumnsByName) +// m_simpleColumnsByName = new QDict<KexiTableViewColumn>(101); +// m_simpleColumnsByName->insert(col->caption,col);//for faster lookup +// } + columns.append( col ); + col->m_data = this; + if (m_globalColumnsIDs.size() < columns.count()) {//sanity + m_globalColumnsIDs.resize( m_globalColumnsIDs.size()*2 ); + } + if (col->visible()) { + m_visibleColumnsCount++; + if (m_visibleColumnsIDs.size() < m_visibleColumnsCount) {//sanity + m_visibleColumnsIDs.resize( m_visibleColumnsIDs.size()*2 ); + } + m_visibleColumnsIDs[ columns.count()-1 ] = m_visibleColumnsCount-1; + m_globalColumnsIDs[ m_visibleColumnsCount-1 ] = columns.count()-1; + } + else { + m_visibleColumnsIDs[ columns.count()-1 ] = -1; + } + m_autoIncrementedColumn = -2; //clear cache; + if (!m_cursor || !m_cursor->query()) + m_itemSize = columns.count()+(m_containsROWIDInfo?1:0); +} + +QString KexiTableViewData::dbTableName() const +{ + if (m_cursor && m_cursor->query() && m_cursor->query()->masterTable()) + return m_cursor->query()->masterTable()->name(); + return QString::null; +} + +void KexiTableViewData::setSorting(int column, bool ascending) +{ + if (column>=0 && column<(int)columns.count()) { + m_order = (ascending ? 1 : -1); + } + else { + m_order = 0; + m_sortedColumn = -1; + m_realSortedColumn = -1; + return; + } + // find proper column information for sorting (lookup column points to alternate column with visible data) + const KexiTableViewColumn *tvcol = columns.at(column); + KexiDB::QueryColumnInfo* visibleLookupColumnInfo = tvcol->visibleLookupColumnInfo; + const KexiDB::Field *field = visibleLookupColumnInfo ? visibleLookupColumnInfo->field : tvcol->field(); + m_sortedColumn = column; + m_realSortedColumn = tvcol->columnInfo->indexForVisibleLookupValue()!=-1 + ? tvcol->columnInfo->indexForVisibleLookupValue() : m_sortedColumn; + + // setup compare function + const int t = field->type(); + if (field->isTextType()) + cmpFunc = &KexiTableViewData::cmpStr; + else if (KexiDB::Field::isFPNumericType(t)) + cmpFunc = &KexiTableViewData::cmpDouble; + else if (t==KexiDB::Field::BigInteger) { + if (field->isUnsigned()) + cmpFunc = &KexiTableViewData::cmpULongLong; + else + cmpFunc = &KexiTableViewData::cmpLongLong; + } + else if (t == KexiDB::Field::Integer && field->isUnsigned()) + cmpFunc = &KexiTableViewData::cmpUInt; + else if (t == KexiDB::Field::Boolean || KexiDB::Field::isNumericType(t)) + cmpFunc = &KexiTableViewData::cmpInt; //other integers + else if (t == KexiDB::Field::Date) + cmpFunc = &KexiTableViewData::cmpDate; + else if (t == KexiDB::Field::Time) + cmpFunc = &KexiTableViewData::cmpTime; + else if (t == KexiDB::Field::DateTime) + cmpFunc = &KexiTableViewData::cmpDateTime; + else if (t == KexiDB::Field::BLOB) +//! TODO allow users to define BLOB sorting function? + cmpFunc = &KexiTableViewData::cmpBLOB; + else + cmpFunc = &KexiTableViewData::cmpStr; //anything else +} + +int KexiTableViewData::compareItems(Item item1, Item item2) +{ + return ((this->*cmpFunc) (item1, item2)); +} + +//! compare NULLs : NULL is smaller than everything +#define CMP_NULLS(item1, item2) \ + m_leftTmp = ((KexiTableItem *)item1)->at(m_realSortedColumn); \ + if (m_leftTmp.isNull()) \ + return -m_order; \ + m_rightTmp = ((KexiTableItem *)item2)->at(m_realSortedColumn); \ + if (m_rightTmp.isNull()) \ + return m_order + +#define CAST_AND_COMPARE(casting, item1, item2) \ + CMP_NULLS(item1, item2); \ + if (m_leftTmp.casting() < m_rightTmp.casting()) \ + return -m_order; \ + if (m_leftTmp.casting() > m_rightTmp.casting()) \ + return m_order; \ + return 0 + +int KexiTableViewData::cmpInt(Item item1, Item item2) +{ + CAST_AND_COMPARE(toInt, item1, item2); +} + +int KexiTableViewData::cmpUInt(Item item1, Item item2) +{ + CAST_AND_COMPARE(toUInt, item1, item2); +} + +int KexiTableViewData::cmpLongLong(Item item1, Item item2) +{ + CAST_AND_COMPARE(toLongLong, item1, item2); +} + +int KexiTableViewData::cmpULongLong(Item item1, Item item2) +{ + CAST_AND_COMPARE(toULongLong, item1, item2); +} + +int KexiTableViewData::cmpDouble(Item item1, Item item2) +{ + CAST_AND_COMPARE(toDouble, item1, item2); +} + +int KexiTableViewData::cmpDate(Item item1, Item item2) +{ + CAST_AND_COMPARE(toDate, item1, item2); +} + +int KexiTableViewData::cmpDateTime(Item item1, Item item2) +{ + CAST_AND_COMPARE(toDateTime, item1, item2); +} + +int KexiTableViewData::cmpTime(Item item1, Item item2) +{ + CAST_AND_COMPARE(toDate, item1, item2); +} + +int KexiTableViewData::cmpStr(Item item1, Item item2) +{ + CMP_NULLS(item1, item2); + const QString &as = m_leftTmp.toString(); + const QString &bs = m_rightTmp.toString(); + + const QChar *a = as.unicode(); + const QChar *b = bs.unicode(); + + if ( a == b ) + return 0; + if ( a == 0 ) + return -1; + if ( b == 0 ) + return 1; + + unsigned short au; + unsigned short bu; + + int l=QMIN(as.length(),bs.length()); + + au = a->unicode(); + bu = b->unicode(); + au = (au <= 0x17e ? charTable[au] : 0xffff); + bu = (bu <= 0x17e ? charTable[bu] : 0xffff); + + while (l-- && au == bu) + { + a++,b++; + au = a->unicode(); + bu = b->unicode(); + au = (au <= 0x17e ? charTable[au] : 0xffff); + bu = (bu <= 0x17e ? charTable[bu] : 0xffff); + } + + if ( l==-1 ) + return m_order*(as.length()-bs.length()); + + return m_order*(au-bu); +} + +int KexiTableViewData::cmpBLOB(Item item1, Item item2) +{ + CMP_NULLS(item1, item2); + return m_leftTmp.toByteArray().size() - m_rightTmp.toByteArray().size(); +} + +void KexiTableViewData::setReadOnly(bool set) +{ + if (m_readOnly == set) + return; + m_readOnly = set; + if (m_readOnly) + setInsertingEnabled(false); +} + +void KexiTableViewData::setInsertingEnabled(bool set) +{ + if (m_insertingEnabled == set) + return; + m_insertingEnabled = set; + if (m_insertingEnabled) + setReadOnly(false); +} + +void KexiTableViewData::clearRowEditBuffer() +{ + //init row edit buffer + if (!m_pRowEditBuffer) + m_pRowEditBuffer = new KexiDB::RowEditBuffer(isDBAware()); + else + m_pRowEditBuffer->clear(); +} + +bool KexiTableViewData::updateRowEditBufferRef(KexiTableItem *item, + int colnum, KexiTableViewColumn* col, QVariant& newval, bool allowSignals, + QVariant *visibleValueForLookupField) +{ + m_result.clear(); + if (allowSignals) + emit aboutToChangeCell(item, colnum, newval, &m_result); + if (!m_result.success) + return false; + + kdDebug() << "KexiTableViewData::updateRowEditBufferRef() column #" + << colnum << " = " << newval.toString() << endl; + if (!col) { + kdWarning() << "KexiTableViewData::updateRowEditBufferRef(): column #" + << colnum << " not found! col==0" << endl; + return false; + } + if (!m_pRowEditBuffer) + m_pRowEditBuffer = new KexiDB::RowEditBuffer(isDBAware()); + if (m_pRowEditBuffer->isDBAware()) { + if (!(col->columnInfo)) { + kdWarning() << "KexiTableViewData::updateRowEditBufferRef(): column #" + << colnum << " not found!" << endl; + return false; + } + m_pRowEditBuffer->insert( *col->columnInfo, newval); + + if (col->visibleLookupColumnInfo && visibleValueForLookupField) { + //this is value for lookup table: update visible value as well + m_pRowEditBuffer->insert( *col->visibleLookupColumnInfo, *visibleValueForLookupField); + } + return true; + } + if (!(col->field())) { + kdDebug() << "KexiTableViewData::updateRowEditBufferRef(): column #" << colnum<<" not found!" << endl; + return false; + } + //not db-aware: + const QString colname = col->field()->name(); + if (colname.isEmpty()) { + kdDebug() << "KexiTableViewData::updateRowEditBufferRef(): column #" << colnum<<" not found!" << endl; + return false; + } + m_pRowEditBuffer->insert(colname, newval); + return true; +} + +//get a new value (if present in the buffer), or the old one, otherwise +//(taken here for optimization) +#define GET_VALUE if (!val) { \ + val = m_cursor \ + ? m_pRowEditBuffer->at( *it_f.current()->columnInfo, true /* useDefaultValueIfPossible */ ) \ + : m_pRowEditBuffer->at( *f ); \ + if (!val) \ + val = &(*it_r); /* get old value */ \ + } + +//! @todo if there're multiple views for this data, we need multiple buffers! +bool KexiTableViewData::saveRow(KexiTableItem& item, bool insert, bool repaint) +{ + if (!m_pRowEditBuffer) + return true; //nothing to do + + //check constraints: + //-check if every NOT NULL and NOT EMPTY field is filled + KexiTableViewColumn::ListIterator it_f(columns); + KexiDB::RowData::ConstIterator it_r = item.constBegin(); + int col = 0; + const QVariant *val; + for (;it_f.current() && it_r!=item.constEnd();++it_f,++it_r,col++) { + KexiDB::Field *f = it_f.current()->field(); + val = 0; + if (f->isNotNull()) { + GET_VALUE; + //check it + if (val->isNull() && !f->isAutoIncrement()) { + //NOT NULL violated + m_result.msg = i18n("\"%1\" column requires a value to be entered.") + .arg(f->captionOrName()) + "\n\n" + Kexi::msgYouCanImproveData(); + m_result.desc = i18n("The column's constraint is declared as NOT NULL."); + m_result.column = col; + return false; + } + } + if (f->isNotEmpty()) { + GET_VALUE; + if (!f->isAutoIncrement() && (val->isNull() || KexiDB::isEmptyValue( f, *val ))) { + //NOT EMPTY violated + m_result.msg = i18n("\"%1\" column requires a value to be entered.") + .arg(f->captionOrName()) + "\n\n" + Kexi::msgYouCanImproveData(); + m_result.desc = i18n("The column's constraint is declared as NOT EMPTY."); + m_result.column = col; + return false; + } + } + } + + if (m_cursor) {//db-aware + if (insert) { + if (!m_cursor->insertRow( static_cast<KexiDB::RowData&>(item), *m_pRowEditBuffer, + m_containsROWIDInfo/*also retrieve ROWID*/ )) + { + m_result.msg = i18n("Row inserting failed.") + "\n\n" + + Kexi::msgYouCanImproveData(); + KexiDB::getHTMLErrorMesage(m_cursor, &m_result); + +/* if (desc) + *desc = +js: TODO: use KexiMainWindowImpl::showErrorMessage(const QString &title, KexiDB::Object *obj) + after it will be moved somewhere to kexidb (this will require moving other + showErrorMessage() methods from KexiMainWindowImpl to libkexiutils....) + then: just call: *desc = KexiDB::errorMessage(m_cursor); +*/ + return false; + } + } + else { // row updating +// if (m_containsROWIDInfo) +// ROWID = item[columns.count()].toULongLong(); + if (!m_cursor->updateRow( static_cast<KexiDB::RowData&>(item), *m_pRowEditBuffer, + m_containsROWIDInfo/*use ROWID*/)) + { + m_result.msg = i18n("Row changing failed.") + "\n\n" + Kexi::msgYouCanImproveData(); +//! @todo set m_result.column if possible + KexiDB::getHTMLErrorMesage(m_cursor, m_result.desc); + return false; + } + } + } + else {//not db-aware version + KexiDB::RowEditBuffer::SimpleMap b = m_pRowEditBuffer->simpleBuffer(); + for (KexiDB::RowEditBuffer::SimpleMap::ConstIterator it = b.constBegin();it!=b.constEnd();++it) { + uint i=0; + for (KexiTableViewColumn::ListIterator it2(columns);it2.current();++it2, i++) { + if (it2.current()->field()->name()==it.key()) { + kdDebug() << it2.current()->field()->name()<< ": "<<item[i].toString()<<" -> "<<it.data().toString()<<endl; + item[i] = it.data(); + } + } + } + } + + m_pRowEditBuffer->clear(); + + if (repaint) + emit rowRepaintRequested(item); + return true; +} + +bool KexiTableViewData::saveRowChanges(KexiTableItem& item, bool repaint) +{ + kdDebug() << "KexiTableViewData::saveRowChanges()..." << endl; + m_result.clear(); + emit aboutToUpdateRow(&item, m_pRowEditBuffer, &m_result); + if (!m_result.success) + return false; + + if (saveRow(item, false /*update*/, repaint)) { + emit rowUpdated(&item); + return true; + } + return false; +} + +bool KexiTableViewData::saveNewRow(KexiTableItem& item, bool repaint) +{ + kdDebug() << "KexiTableViewData::saveNewRow()..." << endl; + m_result.clear(); + emit aboutToInsertRow(&item, &m_result, repaint); + if (!m_result.success) + return false; + + if (saveRow(item, true /*insert*/, repaint)) { + emit rowInserted(&item, repaint); + return true; + } + return false; +} + +bool KexiTableViewData::deleteRow(KexiTableItem& item, bool repaint) +{ + m_result.clear(); + emit aboutToDeleteRow(item, &m_result, repaint); + if (!m_result.success) + return false; + + if (m_cursor) {//db-aware + m_result.success = false; + if (!m_cursor->deleteRow( static_cast<KexiDB::RowData&>(item), m_containsROWIDInfo/*use ROWID*/ )) { + m_result.msg = i18n("Row deleting failed."); +/*js: TODO: use KexiDB::errorMessage() for description (desc) as in KexiTableViewData::saveRow() */ + KexiDB::getHTMLErrorMesage(m_cursor, &m_result); + m_result.success = false; + return false; + } + } + + if (!removeRef(&item)) { + //aah - this shouldn't be! + kdWarning() << "KexiTableViewData::deleteRow(): !removeRef() - IMPL. ERROR?" << endl; + m_result.success = false; + return false; + } + emit rowDeleted(); + return true; +} + +void KexiTableViewData::deleteRows( const QValueList<int> &rowsToDelete, bool repaint ) +{ + Q_UNUSED( repaint ); + + if (rowsToDelete.isEmpty()) + return; + int last_r=0; + first(); + for (QValueList<int>::ConstIterator r_it = rowsToDelete.constBegin(); r_it!=rowsToDelete.constEnd(); ++r_it) { + for (; last_r<(*r_it); last_r++) { + next(); + } + remove(); + last_r++; + } +//DON'T CLEAR BECAUSE KexiTableViewPropertyBuffer will clear BUFFERS! +//--> emit reloadRequested(); //! \todo more effective? + emit rowsDeleted( rowsToDelete ); +} + +void KexiTableViewData::insertRow(KexiTableItem& item, uint index, bool repaint) +{ + if (!insert( index = QMIN(index, count()), &item )) + return; + emit rowInserted(&item, index, repaint); +} + +void KexiTableViewData::clearInternal() +{ + clearRowEditBuffer(); +// qApp->processEvents( 1 ); +//TODO: this is time consuming: find better data model +// KexiTableViewDataBase::clear(); + const uint c = count(); + for (uint i=0; i<c; i++) { + removeLast(); +#ifndef KEXI_NO_PROCESS_EVENTS + if (i % 1000 == 0) + qApp->processEvents( 1 ); +#endif + } +} + +bool KexiTableViewData::deleteAllRows(bool repaint) +{ + clearInternal(); + + bool res = true; + if (m_cursor) { + //db-aware + res = m_cursor->deleteAllRows(); + } + + if (repaint) + emit reloadRequested(); + return res; +} + +int KexiTableViewData::autoIncrementedColumn() +{ + if (m_autoIncrementedColumn==-2) { + //find such a column + m_autoIncrementedColumn = 0; + KexiTableViewColumn::ListIterator it(columns); + for (; it.current(); ++it, m_autoIncrementedColumn++) { + if (it.current()->field()->isAutoIncrement()) + break; + } + if (!it.current()) + m_autoIncrementedColumn = -1; + } + return m_autoIncrementedColumn; +} + +void KexiTableViewData::preloadAllRows() +{ + if (!m_cursor) + return; + + //const uint fcount = m_cursor->fieldCount() + (m_containsROWIDInfo ? 1 : 0); + m_cursor->moveFirst(); + for (int i=0;!m_cursor->eof();i++) { + KexiTableItem *item = new KexiTableItem(0); + m_cursor->storeCurrentRow(*item); +// item->debug(); + append( item ); + m_cursor->moveNext(); +#ifndef KEXI_NO_PROCESS_EVENTS + if ((i % 1000) == 0) + qApp->processEvents( 1 ); +#endif + } +} + +bool KexiTableViewData::isReadOnly() const +{ + return m_readOnly || (m_cursor && m_cursor->connection()->isReadOnly()); +} + +#include "kexitableviewdata.moc" diff --git a/kexi/widget/tableview/kexitableviewdata.h b/kexi/widget/tableview/kexitableviewdata.h new file mode 100644 index 00000000..970d1d23 --- /dev/null +++ b/kexi/widget/tableview/kexitableviewdata.h @@ -0,0 +1,540 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Lucijan Busch <lucijan@gmx.at> + Copyright (C) 2003 Daniel Molkentin <molkentin@kde.org> + Copyright (C) 2003-2006 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + + Original Author: Till Busch <till@bux.at> + Original Project: buX (www.bux.at) +*/ + +#ifndef KEXITABLEVIEWDATA_H +#define KEXITABLEVIEWDATA_H + +#include <qptrlist.h> +#include <qvariant.h> +#include <qvaluevector.h> +#include <qstring.h> +#include <qobject.h> + +#include "kexitableitem.h" + +#include <kexidb/error.h> + +namespace KexiDB { +class Field; +class QuerySchema; +class RowEditBuffer; +class Cursor; +} + +namespace KexiUtils { +class Validator; +} +class KexiTableViewData; + + +/*! Single column definition. */ +class KEXIDATATABLE_EXPORT KexiTableViewColumn { + public: + typedef QPtrList<KexiTableViewColumn> List; + typedef QPtrListIterator<KexiTableViewColumn> ListIterator; + + /*! Not db-aware ctor. if \a owner is true, the field \a will be owned by this column, + so you shouldn't care about destroying this field. */ + KexiTableViewColumn(KexiDB::Field& f, bool owner = false); + + /*! Not db-aware, convenience ctor, like above. The field is created using specified parameters that are + equal to these accepted by KexiDB::Field ctor. The column will be the owner + of this automatically generated field. + */ + KexiTableViewColumn(const QString& name, KexiDB::Field::Type ctype, + uint cconst = KexiDB::Field::NoConstraints, + uint options = KexiDB::Field::NoOptions, + uint length=0, uint precision=0, + QVariant defaultValue=QVariant(), + const QString& caption = QString::null, + const QString& description = QString::null, + uint width = 0); + + /*! Not db-aware, convenience ctor, simplified version of the above. */ + KexiTableViewColumn(const QString& name, KexiDB::Field::Type ctype, const QString& caption, + const QString& description = QString::null); + + //! Db-aware version. + KexiTableViewColumn(const KexiDB::QuerySchema &query, KexiDB::QueryColumnInfo& aColumnInfo, + KexiDB::QueryColumnInfo* aVisibleLookupColumnInfo = 0); + + virtual ~KexiTableViewColumn(); + + virtual bool acceptsFirstChar(const QChar& ch) const; + + /*! \return true if the column is read-only + For db-aware column this can depend on whether the column + is in parent table of this query. \sa setReadOnly() */ + bool isReadOnly() const; + +//TODO: synchronize this with table view: + //! forces readOnly flag to be set to \a ro + inline void setReadOnly(bool ro) { m_readOnly=ro; } + + //! Column visibility. By default column is visible. + inline bool visible() const { return columnInfo ? columnInfo->visible : m_visible; } + + //! Changes column visibility. + inline void setVisible(bool v) { + if (columnInfo) + columnInfo->visible = v; + m_visible=v; + } + + /*! Sets icon for displaying in the caption area (header). */ + void setIcon(const QIconSet& icon) { m_icon = icon; } + + /*! \return bame of icon displayed in the caption area (header). */ + QIconSet icon() const { return m_icon; } + + /*! If \a visible is true, caption has to be displayed in the column's header, + (or field's name if caption is empty. True by default. */ + void setHeaderTextVisible(bool visible) { m_headerTextVisible = visible; } + + /*! \return true if caption has to be displayed in the column's header, + (or field's name if caption is empty. */ + bool isHeaderTextVisible() const { return m_headerTextVisible; } + + /*! \return whatever is available: + - field's caption + - or field's alias (from query) + - or finally - field's name */ + inline QString captionAliasOrName() const { return m_captionAliasOrName; } + + /*! Assigns validator \a v for this column. + If the validator has no parent object, it will be owned by the column, + so you shouldn't care about destroying it. */ + void setValidator( KexiUtils::Validator* v ); + + //! \return validator assigned for this column of 0 if there is no validator assigned. + inline KexiUtils::Validator* validator() const { return m_validator; } + + /*! For not-db-aware data only: + Sets related data \a data for this column, what defines simple one-field, + one-to-many relationship between this column and the primary key in \a data. + The relationship will be used to generate a popup editor instead of just regular editor. + This assignment has no result if \a data has no primary key defined. + \a data is owned, so is will be destroyed when needed. It is also destroyed + when another data (or NULL) is set for the same column. */ + void setRelatedData(KexiTableViewData *data); + + /*! For not-db-aware data only: + Related data \a data for this column, what defines simple one-field. + NULL by default. \sa setRelatedData() */ + inline KexiTableViewData *relatedData() const { return m_relatedData; } + + /*! \return field for this column. + For db-aware information is taken from \a columnInfo member. */ + inline KexiDB::Field* field() const { return m_field; } + + /*! Only usable if related data is set (ie. this is for combo boxes). + Sets 'editable' flag for this column, what means a new value can be entered + by hand. This is similar to QComboBox::setEditable(). */ + void setRelatedDataEditable(bool set); + + /*! Only usable if related data is set (ie. this is for combo boxes). + \return 'editable' flag for this column. + False by default. @see setRelatedDataEditable(bool). */ + inline bool relatedDataEditable() const { return m_relatedDataEditable; } + + /*! A rich field information for db-aware data. + For not-db-aware data it is always 0 (use field() instead). */ + KexiDB::QueryColumnInfo* columnInfo; + + /*! A rich field information for db-aware data. Specifies information for a column + that should be visible instead of columnInfo. For example case see + @ref KexiDB::QueryColumnInfo::Vector KexiDB::QuerySchema::fieldsExpanded(KexiDB::QuerySchema::FieldsExpandedOptions options = Default) + + For not-db-aware data it is always 0. */ + KexiDB::QueryColumnInfo* visibleLookupColumnInfo; + + bool isDBAware : 1; //!< true if data is stored in DB, not only in memeory + +/* QString caption; + int type; //!< one of KexiDB::Field::Type + uint width; +*/ +// bool isNull() const; + +/* virtual QString caption() const; + virtual void setCaption(const QString& c); + */ + protected: + //! special ctor that do not allocate d member; + KexiTableViewColumn(bool); + + void init(); + + QString m_captionAliasOrName; + + QIconSet m_icon; + + KexiUtils::Validator* m_validator; + + //! Data that this column is assigned to. + KexiTableViewData* m_data; + + KexiTableViewData* m_relatedData; + uint m_relatedDataPKeyID; + + KexiDB::Field* m_field; + + bool m_readOnly : 1; + bool m_fieldOwned : 1; + bool m_visible : 1; + bool m_relatedDataEditable : 1; + bool m_headerTextVisible : 1; + + friend class KexiTableViewData; +}; + + +/*! List of column definitions. */ +//typedef QValueVector<KexiTableViewColumn> KexiTableViewColumnList; + +typedef QPtrList<KexiTableItem> KexiTableViewDataBase; + +/*! Reimplements QPtrList to allow configurable sorting and more. + Original author: Till Busch. + Reimplemented by Jaroslaw Staniek. +*/ +class KEXIDATATABLE_EXPORT KexiTableViewData : public QObject, protected KexiTableViewDataBase +{ + Q_OBJECT + +public: + typedef QPtrListIterator<KexiTableItem> Iterator; + + //! not db-aware version + KexiTableViewData(); + + //! db-aware version + KexiTableViewData(KexiDB::Cursor *c); + +//TODO: make this more generic: allow to add more columns! + /*! Defines two-column table usually used with comboboxes. + First column is invisible and contains key values. + Second column and contains user-visible value. + @param keys a list of keys + @param values a list of text values (must be of the same length as keys list) + @param keyType a type for keys + @param valueType a type for values + */ + KexiTableViewData( + const QValueList<QVariant> &keys, const QValueList<QVariant> &values, + KexiDB::Field::Type keyType = KexiDB::Field::Text, + KexiDB::Field::Type valueType = KexiDB::Field::Text); + + /*! Like above constructor, but keys and values are not provided. + You can do this later by calling append(KexiTableItem*) method. + (KexiTableItem object must have exactly two columns) + */ + KexiTableViewData(KexiDB::Field::Type keyType, KexiDB::Field::Type valueType); + + virtual ~KexiTableViewData(); +//js void setSorting(int key, bool order=true, short type=1); + + /*! Preloads all rows provided by cursor (only for db-aware version). */ +//! @todo change to bool and return false on error! + void preloadAllRows(); + + /*! Sets sorting for \a column. If \a column is -1, sorting is disabled. */ + void setSorting(int column, bool ascending=true); + + /*! \return the column number by which the data is sorted, + or -1 if sorting is disabled. In this case sortingOrder() will return 0. + Initial sorted column number for data after instantiating object is -1. */ + inline int sortedColumn() const { return m_sortedColumn; } + + /*! \return 1 if ascending sort order is set, -1 id descending sort order is set, + or 0 if no sorting is set. This is independent of whether data is sorted now. + Initial sorting for data after instantiating object is 0. */ + inline int sortingOrder() const { return m_order; } + + /*! Adds column \a col. + Warning: \a col will be owned by this object, and deleted on its destruction. */ + void addColumn( KexiTableViewColumn* col ); + + inline int globalColumnID(int visibleID) { return m_globalColumnsIDs.at( visibleID ); } + inline int visibleColumnID(int globalID) { return m_visibleColumnsIDs.at( globalID ); } + + /*virtual?*/ + /*! \return true if this db-aware data set. */ + inline bool isDBAware() { return m_cursor; } + + /*! For db-aware data set only: table name is returned; + equivalent to cursor()->query()->parentTable()->name(). */ + QString dbTableName() const; + + inline KexiDB::Cursor* cursor() const { return m_cursor; } + + inline uint columnsCount() const { return columns.count(); } + + inline KexiTableViewColumn* column(uint c) { return columns.at(c); } + + /*! Columns information */ + KexiTableViewColumn::List columns; + + /*! \return true if data is not editable. Can be set using setReadOnly() + but it's still true if database cursor returned by cursor() + is not 0 and has read-only connection. */ + virtual bool isReadOnly() const; + + /*! Sets readOnly flag for this data. + If \a set is true, insertingEnabled flag will be cleared automatically. + \sa isInsertingEnabled() */ + virtual void setReadOnly(bool set); + + /*! \return true if data inserting is enabled (the default). */ + virtual bool isInsertingEnabled() const { return m_insertingEnabled; } + + /*! Sets insertingEnabled flag. If true, empty row is available + If \a set is true, read-only flag will be cleared automatically. + \sa setReadOnly() */ + virtual void setInsertingEnabled(bool set); + + /*! Clears and initializes internal row edit buffer for incoming editing. + Creates buffer using KexiDB::RowEditBuffer(false) (false means not db-aware type) + if our data is not db-aware, + or db-aware buffer if data is db-aware (isDBAware()==true). + \sa KexiDB::RowEditBuffer + */ + void clearRowEditBuffer(); + + /*! Updates internal row edit buffer: currently edited column \a col (number \a colnum) + has now assigned new value of \a newval. + Uses column's caption to address the column in buffer + if the buffer is of simple type, or db-aware buffer if (isDBAware()==true). + (then fields are addressed with KexiDB::Field, instead of caption strings). + If \a allowSignals is true (the default), aboutToChangeCell() signal is emitted. + \a visibleValueForLookupField allows to pass visible value (usually a text) + for a lookup field (only reasonable if col->visibleLookupColumnInfo != 0). + Note that \a newval may be changed in aboutToChangeCell() signal handler. + \sa KexiDB::RowEditBuffer */ + bool updateRowEditBufferRef(KexiTableItem *item, + int colnum, KexiTableViewColumn* col, QVariant& newval, bool allowSignals = true, + QVariant *visibleValueForLookupField = 0); + + /*! Added for convenience. Like above but \a newval is passed by value. */ + inline bool updateRowEditBuffer(KexiTableItem *item, int colnum, KexiTableViewColumn* col, + QVariant newval, bool allowSignals = true) + { + QVariant newv(newval); + return updateRowEditBufferRef(item, colnum, col, newv, allowSignals); + } + + /*! Added for convenience. Like above but it's assumed that \a item item's columns are ordered + like in table view, not like in form view. Don't use this with form views. */ + inline bool updateRowEditBuffer(KexiTableItem *item, int colnum, + QVariant newval, bool allowSignals = true) + { + KexiTableViewColumn* col = columns.at(colnum); + return col ? updateRowEditBufferRef(item, colnum, col, newval, allowSignals) : false; + } + + inline KexiDB::RowEditBuffer* rowEditBuffer() const { return m_pRowEditBuffer; } + + /*! \return last operation's result information (always not null). */ + inline KexiDB::ResultInfo* result() { return &m_result; } + + bool saveRowChanges(KexiTableItem& item, bool repaint = false); + + bool saveNewRow(KexiTableItem& item, bool repaint = false); + + bool deleteRow(KexiTableItem& item, bool repaint = false); + + /*! Deletes rows (by number) passed with \a rowsToDelete. + Currently, this method is only for non data-aware tables. */ + void deleteRows( const QValueList<int> &rowsToDelete, bool repaint = false ); + + /*! Deletes all rows. Works either for db-aware and non db-aware tables. + Column's definition is not changed. + For db-aware version, all rows are removed from a database. + Row-edit buffer is cleared. + + If \a repaint is true, reloadRequested() signal + is emitted after deleting (if at least one row was deleted), + so presenters can repaint their contents. + + \return true on success. */ + virtual bool deleteAllRows(bool repaint = false); + + /*! @internal method, used mostly by specialized classes like KexiTableView. + Clears internal row structures. Row-edit buffer is cleared. + Does not touch data @ database backend. + Use deleteAllRows() to safely delete all rows. */ + virtual void clearInternal(); + + /*! Inserts new \a item at index \a index. + \a item will be owned by this data object. + Note: Reasonable only for not not-db-aware version. */ + void insertRow(KexiTableItem& item, uint index, bool repaint = false); + +/*TODO: add this as well? + void insertRow(KexiTableItem& item, KexiTableItem& aboveItem); */ + + //! \return index of autoincremented column. The result is cached. +//! \todo what about multiple autoinc columns? +//! \todo what about changing column order? + int autoIncrementedColumn(); + + //! Emits reloadRequested() signal to reload presenters. + void reload() { emit reloadRequested(); } + + inline KexiTableItem* at( uint index ) { return KexiTableViewDataBase::at(index); } + inline virtual uint count() const { return KexiTableViewDataBase::count(); } + inline bool isEmpty () const { return KexiTableViewDataBase::isEmpty(); } + inline KexiTableItem* first() { return KexiTableViewDataBase::first(); } + inline KexiTableItem* last() { return KexiTableViewDataBase::last(); } + inline int findRef( const KexiTableItem* item ) { return KexiTableViewDataBase::findRef(item); } + inline void sort() { KexiTableViewDataBase::sort(); } + inline bool removeFirst() { return KexiTableViewDataBase::removeFirst(); } + inline bool removeLast() { return KexiTableViewDataBase::removeLast(); } + inline void append( const KexiTableItem* item ) { KexiTableViewDataBase::append(item); } + inline void prepend( const KexiTableItem* item ) { KexiTableViewDataBase::prepend(item); } + inline Iterator iterator() { return Iterator(*this); } + inline Iterator* createIterator() { return new Iterator(*this); } + + /*! \return true if ROWID information is stored within every row. + Only reasonable for db-aware version. ROWID information is available + if DriverBehaviour::ROW_ID_FIELD_RETURNS_LAST_AUTOINCREMENTED_VALUE == false + for a KexiDB database driver and a table has no primary key defined. + Phisically, ROWID information is stored after last KexiTableItem's element, + so every KexiTableItem's length is expanded by one. */ + inline bool containsROWIDInfo() const { return m_containsROWIDInfo; } + + inline KexiTableItem* createItem() const + { return new KexiTableItem(m_itemSize); } + +public slots: + //! @internal The same as QObject::deleteLater() but also sets smart pointer m_cursor to 0 to avoid crashes... + void deleteLater(); + +signals: + void destroying(); + + /*! Emitted before change of the single, currently edited cell. + Connect this signal to your slot and set \a result->success to false + to disallow this change. You can also change \a newValue to other value, + or change other columns in \a item row. */ + void aboutToChangeCell(KexiTableItem *item, int colnum, QVariant& newValue, + KexiDB::ResultInfo* result); + + /*! Emited before inserting of a new, current row. + Connect this signal to your slot and set \a result->success to false + to disallow this inserting. You can also change columns in \a item row. */ + void aboutToInsertRow(KexiTableItem *item, KexiDB::ResultInfo* result, bool repaint); + + /*! Emited before changing of an edited, current row. + Connect this signal to your slot and set \a result->success to false + to disallow this change. You can also change columns in \a item row. */ + void aboutToUpdateRow(KexiTableItem *item, KexiDB::RowEditBuffer* buffer, + KexiDB::ResultInfo* result); + + void rowUpdated(KexiTableItem*); //!< Current row has been updated + + void rowInserted(KexiTableItem*, bool repaint); //!< A row has been inserted + + //! A row has been inserted at \a index position (not db-aware data only) + void rowInserted(KexiTableItem*, uint index, bool repaint); + + /*! Emited before deleting of a current row. + Connect this signal to your slot and set \a result->success to false + to disallow this deleting. */ + void aboutToDeleteRow(KexiTableItem& item, KexiDB::ResultInfo* result, bool repaint); + + //! Current row has been deleted + void rowDeleted(); + + //! Rows have been deleted + void rowsDeleted( const QValueList<int> &rowsToDelete ); + + //! Displayed data needs to be reloaded in all presenters. + void reloadRequested(); + + void rowRepaintRequested(KexiTableItem&); + +protected: + void init(); + void init( + const QValueList<QVariant> &keys, const QValueList<QVariant> &values, + KexiDB::Field::Type keyType, KexiDB::Field::Type valueType); + + virtual int compareItems(Item item1, Item item2); + int cmpStr(Item item1, Item item2); + int cmpInt(Item item1, Item item2); + int cmpUInt(Item item1, Item item2); + int cmpLongLong(Item item1, Item item2); + int cmpULongLong(Item item1, Item item2); + int cmpDouble(Item item1, Item item2); + int cmpDate(Item item1, Item item2); + int cmpDateTime(Item item1, Item item2); + int cmpTime(Item item1, Item item2); + + //! Compare function for BLOB data (QByteArray). Uses size as the weight. + int cmpBLOB(Item item1, Item item2); + + //! internal: for saveRowChanges() and saveNewRow() + bool saveRow(KexiTableItem& item, bool insert, bool repaint); + + //! (logical) sorted column number, set by setSorting() + //! can differ from m_realSortedColumn if there's lookup column used + int m_sortedColumn; + //! real sorted column number, set by setSorting(), used by cmp*() methods + int m_realSortedColumn; + short m_order; + short m_type; + int m_itemSize; + static unsigned short charTable[]; + KexiDB::RowEditBuffer *m_pRowEditBuffer; + QGuardedPtr<KexiDB::Cursor> m_cursor; + + //! used to faster lookup columns of simple type (not dbaware) +// QDict<KexiTableViewColumn> *m_simpleColumnsByName; + + KexiDB::ResultInfo m_result; + + uint m_visibleColumnsCount; + QValueVector<int> m_visibleColumnsIDs, m_globalColumnsIDs; + + bool m_readOnly : 1; + bool m_insertingEnabled : 1; + + /*! Used in acceptEditor() to avoid infinite recursion, + eg. when we're calling acceptRowEdit() during cell accepting phase. */ + bool m_inside_acceptEditor : 1; + + //! @see containsROWIDInfo() + bool m_containsROWIDInfo : 1; + + int m_autoIncrementedColumn; + + int (KexiTableViewData::*cmpFunc)(void *, void *); + + //! Temporary, used in compare functions like cmpInt(), cmpString() + //! to avoid memory allocations. + QVariant m_leftTmp, m_rightTmp; +}; + +#endif diff --git a/kexi/widget/tableview/kexitableviewheader.cpp b/kexi/widget/tableview/kexitableviewheader.cpp new file mode 100644 index 00000000..3656a041 --- /dev/null +++ b/kexi/widget/tableview/kexitableviewheader.cpp @@ -0,0 +1,202 @@ +/* This file is part of the KDE project + Copyright (C) 2005-2006 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "kexitableviewheader.h" + +#include <qapplication.h> +#include <qtooltip.h> +#include <qstyle.h> + +#include <kexiutils/utils.h> +#include <kexiutils/styleproxy.h> + +//! @internal A style that allows to temporary change background color while +//! drawing header section primitive. Used in KexiTableViewHeader. +class KexiTableViewHeaderStyle : public KexiUtils::StyleProxy +{ + public: + KexiTableViewHeaderStyle(QStyle *parentStyle, QWidget *widget) + : KexiUtils::StyleProxy(parentStyle) + { + setBackgroundColor( widget->palette().active().background() ); + } + ~KexiTableViewHeaderStyle() {} + + virtual void drawPrimitive( PrimitiveElement pe, + QPainter *p, const QRect &r, const QColorGroup &cg, SFlags flags = Style_Default, + const QStyleOption& option = QStyleOption::Default ) const + { + if (pe==QStyle::PE_HeaderSection) { + QColorGroup newCg(cg); + newCg.setColor(QColorGroup::Button, m_backgroundColor); + newCg.setColor(QColorGroup::Background, m_backgroundColor); //set background color as well (e.g. for thinkeramik) + m_style->drawPrimitive( pe, p, r, newCg, flags, option ); + return; + } + m_style->drawPrimitive( pe, p, r, cg, flags, option ); + } + + void setBackgroundColor( const QColor& color ) { m_backgroundColor = color; } + + protected: + QColor m_backgroundColor; +}; + +KexiTableViewHeader::KexiTableViewHeader(QWidget * parent, const char * name) + : QHeader(parent, name) + , m_lastToolTipSection(-1) + , m_selectionBackgroundColor(qApp->palette().active().highlight()) + , m_selectedSection(-1) + , m_styleChangeEnabled(true) +{ + styleChange( style() ); + installEventFilter(this); + connect(this, SIGNAL(sizeChange(int,int,int)), + this, SLOT(slotSizeChange(int,int,int))); +} + +KexiTableViewHeader::~KexiTableViewHeader() +{ +} + +void KexiTableViewHeader::styleChange( QStyle& oldStyle ) +{ + QHeader::styleChange( oldStyle ); + if (!m_styleChangeEnabled) + return; + m_styleChangeEnabled = false; + setStyle( new KexiTableViewHeaderStyle(&qApp->style(), this) ); + m_styleChangeEnabled = true; +} + +int KexiTableViewHeader::addLabel ( const QString & s, int size ) +{ + m_toolTips += ""; + slotSizeChange(0,0,0);//refresh + return QHeader::addLabel(s, size); +} + +int KexiTableViewHeader::addLabel ( const QIconSet & iconset, const QString & s, int size ) +{ + m_toolTips += ""; + slotSizeChange(0,0,0);//refresh + return QHeader::addLabel(iconset, s, size); +} + +void KexiTableViewHeader::removeLabel( int section ) +{ + if (section < 0 || section >= count()) + return; + QStringList::Iterator it = m_toolTips.begin(); + it += section; + m_toolTips.remove(it); + slotSizeChange(0,0,0);//refresh + QHeader::removeLabel(section); +} + +void KexiTableViewHeader::setToolTip( int section, const QString & toolTip ) +{ + if (section < 0 || section >= (int)m_toolTips.count()) + return; + m_toolTips[ section ] = toolTip; +} + +bool KexiTableViewHeader::eventFilter(QObject * watched, QEvent * e) +{ + if (e->type()==QEvent::MouseMove) { + const int section = sectionAt( static_cast<QMouseEvent*>(e)->x() ); + if (section != m_lastToolTipSection && section >= 0 && section < (int)m_toolTips.count()) { + QToolTip::remove(this, m_toolTipRect); + QString tip = m_toolTips[ section ]; + if (tip.isEmpty()) { //try label + QFontMetrics fm(font()); + int minWidth = fm.width( label( section ) ) + style().pixelMetric( QStyle::PM_HeaderMargin ); + QIconSet *iset = iconSet( section ); + if (iset) + minWidth += (2+iset->pixmap( QIconSet::Small, QIconSet::Normal ).width()); //taken from QHeader::sectionSizeHint() + if (minWidth > sectionSize( section )) + tip = label( section ); + } + if (tip.isEmpty()) { + m_lastToolTipSection = -1; + } + else { + QToolTip::add(this, m_toolTipRect = sectionRect(section), tip); + m_lastToolTipSection = section; + } + } + } +// if (e->type()==QEvent::MouseButtonPress) { +// todo +// } + return QHeader::eventFilter(watched, e); +} + +void KexiTableViewHeader::slotSizeChange(int /*section*/, int /*oldSize*/, int /*newSize*/ ) +{ + if (m_lastToolTipSection>0) + QToolTip::remove(this, m_toolTipRect); + m_lastToolTipSection = -1; //tooltip's rect is now invalid +} + +void KexiTableViewHeader::setSelectionBackgroundColor(const QColor &color) +{ + m_selectionBackgroundColor = color; +} + +QColor KexiTableViewHeader::selectionBackgroundColor() const +{ + return m_selectionBackgroundColor; +} + +void KexiTableViewHeader::setSelectedSection(int section) +{ + if (m_selectedSection==section || (section!=-1 && section>=count())) + return; + const int oldSection = m_selectedSection; + m_selectedSection = section; + if (oldSection!=-1) + update(sRect(oldSection)); + if (m_selectedSection!=-1) + update(sRect(m_selectedSection)); +} + +int KexiTableViewHeader::selectedSection() const +{ + return m_selectedSection; +} + +void KexiTableViewHeader::paintSection( QPainter * p, int index, const QRect & fr ) +{ + const bool paintSelection = index==m_selectedSection && index != -1; + if (paintSelection) { + static_cast<KexiTableViewHeaderStyle&>(style()).setBackgroundColor( + KexiUtils::blendedColors( + palette().active().background(), m_selectionBackgroundColor, 2, 1) ); + } + + QHeader::paintSection( p, index, fr ); + + if (paintSelection) { //revert the color for subsequent paints + static_cast<KexiTableViewHeaderStyle&>(style()).setBackgroundColor( + palette().active().background()); + } +} + +#include "kexitableviewheader.moc" diff --git a/kexi/widget/tableview/kexitableviewheader.h b/kexi/widget/tableview/kexitableviewheader.h new file mode 100644 index 00000000..5da3fa7b --- /dev/null +++ b/kexi/widget/tableview/kexitableviewheader.h @@ -0,0 +1,75 @@ +/* This file is part of the KDE project + Copyright (C) 2005-2006 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXITABLEVIEWHEADER_H +#define KEXITABLEVIEWHEADER_H + +#include <qheader.h> + +class QStyle; + +//! @short A table view header with additional actions. +/*! Displays field description (Field::description()) text as tool tip, if available. + Displays tool tips if a pointed section is not wide enough to fit its label text. + + \todo react on indexChange ( int section, int fromIndex, int toIndex ) signal +*/ +class KEXIDATATABLE_EXPORT KexiTableViewHeader : public QHeader +{ + Q_OBJECT + + public: + KexiTableViewHeader(QWidget * parent = 0, const char * name = 0); + + virtual ~KexiTableViewHeader(); + + int addLabel( const QString & s, int size = -1 ); + + int addLabel( const QIconSet & iconset, const QString & s, int size = -1 ); + + void removeLabel( int section ); + + /*! Sets \a toolTip for \a section. */ + void setToolTip( int section, const QString & toolTip ); + + virtual bool eventFilter(QObject * watched, QEvent * e); + + void setSelectedSection(int section); + int selectedSection() const; + + QColor selectionBackgroundColor() const; + void setSelectionBackgroundColor(const QColor &color); + + protected slots: + void slotSizeChange(int section, int oldSize, int newSize ); + + protected: + virtual void paintSection ( QPainter * p, int index, const QRect & fr ); + virtual void styleChange( QStyle& oldStyle ); + + int m_lastToolTipSection; + QRect m_toolTipRect; + + QStringList m_toolTips; + QColor m_selectionBackgroundColor; + int m_selectedSection; + bool m_styleChangeEnabled : 1; +}; + +#endif diff --git a/kexi/widget/tableview/kexitextformatter.cpp b/kexi/widget/tableview/kexitextformatter.cpp new file mode 100644 index 00000000..f4e0b89d --- /dev/null +++ b/kexi/widget/tableview/kexitextformatter.cpp @@ -0,0 +1,237 @@ +/* This file is part of the KDE project + Copyright (C) 2007 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include <klocale.h> + +#include "kexitextformatter.h" +#include <widget/utils/kexidatetimeformatter.h> +#include <kexidb/utils.h> + +//! @internal +class KexiTextFormatter::Private +{ + public: + Private() : field(0), dateFormatter(0), timeFormatter(0) + { + } + + ~Private() + { + delete dateFormatter; + delete timeFormatter; + } + + KexiDB::Field* field; + KexiDateFormatter *dateFormatter; + KexiTimeFormatter *timeFormatter; +}; + +KexiTextFormatter::KexiTextFormatter() + : d( new Private ) +{ +} + +KexiTextFormatter::~KexiTextFormatter() +{ + delete d; +} + +void KexiTextFormatter::setField( KexiDB::Field* field ) +{ + d->field = field; + if (!d->field) + return; + if (d->field->type() == KexiDB::Field::Date || d->field->type() == KexiDB::Field::DateTime) + d->dateFormatter = new KexiDateFormatter(); + else { + delete d->dateFormatter; + d->dateFormatter = 0; + } + if (d->field->type() == KexiDB::Field::Time || d->field->type() == KexiDB::Field::DateTime) + d->timeFormatter = new KexiTimeFormatter(); + else { + delete d->timeFormatter; + d->timeFormatter = 0; + } +} + +QString KexiTextFormatter::valueToText(const QVariant& value, const QString& add) const +{ + //cases, in order of expected frequency + if (!d->field || d->field->isTextType()) + return value.toString() + add; + else if (d->field->isIntegerType()) { + if (value.toInt() == 0) + return add; //eat 0 + } + else if (d->field->isFPNumericType()) { +//! @todo precision! +//! @todo support 'g' format + if (value.toDouble() == 0.0) + return add.isEmpty() ? "0" : add; //eat 0 +#if 0 //moved to KexiDB::formatNumberForVisibleDecimalPlaces() + QString text( QString::number(value.toDouble(), 'f', + QMAX(d->field->visibleDecimalPlaces(), 10)) ); //!<-- 10 is quite good maximum for fractional digits + //!< @todo add command line settings? +//! @todo (js): get decimal places settings here... + QStringList sl = QStringList::split(".", text); + //nothing + } + else if (sl.count()==2) { +// kdDebug() << "sl.count()=="<<sl.count()<< " " <<sl[0] << " | " << sl[1] << endl; + const QString sl1 = sl[1]; + int pos = sl1.length()-1; + if (pos>=1) { + for (;pos>=0 && sl1[pos]=='0';pos--) + ; + pos++; + } + if (pos>0) + text = sl[0] + m_decsym + sl1.left(pos); + else + text = sl[0]; //no decimal point + } +#endif + return KexiDB::formatNumberForVisibleDecimalPlaces( + value.toDouble(), d->field->visibleDecimalPlaces() ) + add; + } + else if (d->field->type() == KexiDB::Field::Boolean) { +//! @todo temporary solution for booleans! + const bool boolValue = value.isNull() ? QVariant(add).toBool() : value.toBool(); + return boolValue ? "1" : "0"; + } + else if (d->field->type() == KexiDB::Field::Date) { + return d->dateFormatter->dateToString( value.toString().isEmpty() ? QDate() : value.toDate() ); + } + else if (d->field->type() == KexiDB::Field::Time) { + return d->timeFormatter->timeToString( + //hack to avoid converting null variant to valid QTime(0,0,0) + value.toString().isEmpty() ? value.toTime() : QTime(99,0,0) ); + } + else if (d->field->type() == KexiDB::Field::DateTime) { + if (value.toString().isEmpty() ) + return add; + return d->dateFormatter->dateToString( value.toDateTime().date() ) + " " + + d->timeFormatter->timeToString( value.toDateTime().time() ); + } + else if (d->field->type() == KexiDB::Field::BigInteger) { + if (value.toLongLong() == 0) + return add; //eat 0 + } + //default: text + return value.toString() + add; +} + +QVariant KexiTextFormatter::textToValue(const QString& text) const +{ + if (!d->field) + return QVariant(); + const KexiDB::Field::Type t = d->field->type(); + switch (t) { + case KexiDB::Field::Text: + case KexiDB::Field::LongText: + return text; + case KexiDB::Field::Byte: + case KexiDB::Field::ShortInteger: + return text.toShort(); +//! @todo uint, etc? + case KexiDB::Field::Integer: + return text.toInt(); + case KexiDB::Field::BigInteger: + return text.toLongLong(); + case KexiDB::Field::Boolean: +//! @todo temporary solution for booleans! + return text == "1" ? QVariant(true,1) : QVariant(false,0); + case KexiDB::Field::Date: + return d->dateFormatter->stringToVariant( text ); + case KexiDB::Field::Time: + return d->timeFormatter->stringToVariant( text ); + case KexiDB::Field::DateTime: + return stringToDateTime(*d->dateFormatter, *d->timeFormatter, text); + case KexiDB::Field::Float: + case KexiDB::Field::Double: { + // replace custom decimal symbol with '.' as required by to{Float|Double}() + QString fixedText( text ); + fixedText.replace(KGlobal::locale()->decimalSymbol(), "."); + if (t == KexiDB::Field::Double) + return fixedText.toDouble(); + return fixedText.toFloat(); + } + default: + return text; + } +//! @todo more data types! +} + +bool KexiTextFormatter::valueIsEmpty(const QString& text) const +{ + if (text.isEmpty()) + return true; + + if (d->field) { + const KexiDB::Field::Type t = d->field->type(); + if (t == KexiDB::Field::Date) + return d->dateFormatter->isEmpty( text ); + else if (t == KexiDB::Field::Time) + return d->timeFormatter->isEmpty( text ); + else if (t == KexiDB::Field::Time) + return dateTimeIsEmpty( *d->dateFormatter, *d->timeFormatter, text ); + } + +//! @todo + return text.isEmpty(); +} + +bool KexiTextFormatter::valueIsValid(const QString& text) const +{ + if (!d->field) + return true; +//! @todo fix for fields with "required" property = true + if (valueIsEmpty(text)/*ok?*/) + return true; + + const KexiDB::Field::Type t = d->field->type(); + if (t == KexiDB::Field::Date) + return d->dateFormatter->stringToVariant( text ).isValid(); + else if (t == KexiDB::Field::Time) + return d->timeFormatter->stringToVariant( text ).isValid(); + else if (t == KexiDB::Field::DateTime) + return dateTimeIsValid( *d->dateFormatter, *d->timeFormatter, text ); + +//! @todo + return true; +} + +QString KexiTextFormatter::inputMask() const +{ + const KexiDB::Field::Type t = d->field->type(); + if (t==KexiDB::Field::Date) { +//! @todo use KDateWidget? + return d->dateFormatter->inputMask(); + } + else if (t==KexiDB::Field::Time) { +//! @todo use KTimeWidget + d->timeFormatter->inputMask(); + } + else if (t==KexiDB::Field::DateTime) { + dateTimeInputMask( *d->dateFormatter, *d->timeFormatter ); + } + return QString::null; +} + diff --git a/kexi/widget/tableview/kexitextformatter.h b/kexi/widget/tableview/kexitextformatter.h new file mode 100644 index 00000000..3ea611a4 --- /dev/null +++ b/kexi/widget/tableview/kexitextformatter.h @@ -0,0 +1,64 @@ +/* This file is part of the KDE project + Copyright (C) 2007 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef KEXITEXTFORMATTER_H +#define KEXITEXTFORMATTER_H + +#include <kexidb/field.h> + +//! @short Text formatter used to format QVariant values to text for displaying and back to QVariant +/*! Used by KexiInputTableEdit, KexiDateTableEdit, KexiTimeTableEdit, KexiDateTimeTableEdit, + KexiDBLineEdit (forms), etc. */ +class KEXIDATATABLE_EXPORT KexiTextFormatter +{ + public: + KexiTextFormatter(); + ~KexiTextFormatter(); + + //! Assigns \a field to the formatter. This affects its behaviour. + void setField( KexiDB::Field* field ); + + /*! \return text for \a value. + A field schema set using setField() is used to perform the formatting. + \a add is a text that should be added to the value if possible. + Used in KexiInputTableEdit::setValueInternal(), by form widgets and for reporting/printing. */ + QString valueToText(const QVariant& value, const QString& add) const; + + /*! \return value cnverted from \a text + A field schema set using setField() is used to perform the formatting. + Used in KexiInputTableEdit::setValueInternal(), by form widgets and for reporting/printing. */ + QVariant textToValue(const QString& text) const; + + /*! \return true if value formatted as \a text is empty. + A field schema set using setField() is used to perform the calculation. */ + bool valueIsEmpty(const QString& text) const; + + /*! \return true if value formatted as \a text is valid. + A field schema set using setField() is used to perform the calculation. */ + bool valueIsValid(const QString& text) const; + + /*! \return input mask for intering values related to a field schema + which has been set using setField(). */ + QString inputMask() const; + + class Private; + Private *d; +}; + +#endif diff --git a/kexi/widget/tableview/kexitimetableedit.cpp b/kexi/widget/tableview/kexitimetableedit.cpp new file mode 100644 index 00000000..3238c58e --- /dev/null +++ b/kexi/widget/tableview/kexitimetableedit.cpp @@ -0,0 +1,158 @@ +/* This file is part of the KDE project + Copyright (C) 2004,2006 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#include "kexitimetableedit.h" + +#include <qapplication.h> +#include <qpainter.h> +#include <qvariant.h> +#include <qrect.h> +#include <qpalette.h> +#include <qcolor.h> +#include <qfontmetrics.h> +#include <qdatetime.h> +#include <qcursor.h> +#include <qpoint.h> +#include <qlayout.h> +#include <qtoolbutton.h> +#include <qdatetimeedit.h> +#include <qclipboard.h> + +#include <kdebug.h> +#include <klocale.h> +#include <kglobal.h> +#include <kdatepicker.h> +#include <kdatetbl.h> +#include <klineedit.h> +#include <kpopupmenu.h> +#include <kdatewidget.h> + +#include <kexiutils/utils.h> + +KexiTimeTableEdit::KexiTimeTableEdit(KexiTableViewColumn &column, QWidget *parent) + : KexiInputTableEdit(column, parent) +{ + setName("KexiTimeTableEdit"); + +//! @todo add QValidator so time like "99:88:77" cannot be even entered + + m_lineedit->setInputMask( m_formatter.inputMask() ); +} + +KexiTimeTableEdit::~KexiTimeTableEdit() +{ +} + +void KexiTimeTableEdit::setValueInInternalEditor(const QVariant &value) +{ + if (value.isValid() && value.toTime().isValid()) + m_lineedit->setText( m_formatter.timeToString( value.toTime() ) ); + else + m_lineedit->setText( QString::null ); +} + +void KexiTimeTableEdit::setValueInternal(const QVariant& add_, bool removeOld) +{ + if (removeOld) { + //new time entering... just fill the line edit +//! @todo cut string if too long.. + QString add(add_.toString()); + m_lineedit->setText(add); + m_lineedit->setCursorPosition(add.length()); + return; + } + setValueInInternalEditor( m_origValue ); + m_lineedit->setCursorPosition(0); //ok? +} + +void KexiTimeTableEdit::setupContents( QPainter *p, bool focused, const QVariant& val, + QString &txt, int &align, int &x, int &y_offset, int &w, int &h ) +{ + Q_UNUSED(p); + Q_UNUSED(focused); + Q_UNUSED(x); + Q_UNUSED(w); + Q_UNUSED(h); +#ifdef Q_WS_WIN + y_offset = -1; +#else + y_offset = 0; +#endif + if (!val.isNull() && val.canCast(QVariant::Time)) + txt = m_formatter.timeToString(val.toTime()); + align |= AlignLeft; +} + +bool KexiTimeTableEdit::valueIsNull() +{ + if (m_formatter.isEmpty( m_lineedit->text() )) //empty time is null + return true; + return !timeValue().isValid(); +} + +bool KexiTimeTableEdit::valueIsEmpty() +{ + return valueIsNull();// OK? TODO (nonsense?) +} + +QTime KexiTimeTableEdit::timeValue() +{ + return m_formatter.stringToTime( m_lineedit->text() ); +} + +QVariant KexiTimeTableEdit::value() +{ + return m_formatter.stringToVariant( m_lineedit->text() ); +} + +bool KexiTimeTableEdit::valueIsValid() +{ + if (m_formatter.isEmpty( m_lineedit->text() )) //empty time is valid + return true; + return m_formatter.stringToTime( m_lineedit->text() ).isValid(); +} + +void KexiTimeTableEdit::handleCopyAction(const QVariant& value, const QVariant& visibleValue) +{ + Q_UNUSED(visibleValue); + if (!value.isNull() && value.toTime().isValid()) + qApp->clipboard()->setText( m_formatter.timeToString(value.toTime()) ); + else + qApp->clipboard()->setText( QString::null ); +} + +void KexiTimeTableEdit::handleAction(const QString& actionName) +{ + const bool alreadyVisible = m_lineedit->isVisible(); + + if (actionName=="edit_paste") { + const QVariant newValue( m_formatter.stringToTime( qApp->clipboard()->text() ) ); + if (!alreadyVisible) { //paste as the entire text if the cell was not in edit mode + emit editRequested(); + m_lineedit->clear(); + } + setValueInInternalEditor( newValue ); + } + else + KexiInputTableEdit::handleAction(actionName); +} + +KEXI_CELLEDITOR_FACTORY_ITEM_IMPL(KexiTimeEditorFactoryItem, KexiTimeTableEdit) + +#include "kexitimetableedit.moc" diff --git a/kexi/widget/tableview/kexitimetableedit.h b/kexi/widget/tableview/kexitimetableedit.h new file mode 100644 index 00000000..4daa68ec --- /dev/null +++ b/kexi/widget/tableview/kexitimetableedit.h @@ -0,0 +1,64 @@ +/* This file is part of the KDE project + Copyright (C) 2004,2006 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef KEXITIMETABLEEDIT_H +#define KEXITIMETABLEEDIT_H + +#include "kexiinputtableedit.h" +#include <widget/utils/kexidatetimeformatter.h> + +/*! @short Editor class for Time type. + It is a replacement QTimeEdit due to usability problems: + people are accustomed to use single-character cursor. + Time format is retrieved from the KDE global settings + and input/output is performed using KLineEdit (from KexiInputTableEdit). +*/ +class KexiTimeTableEdit : public KexiInputTableEdit +{ + Q_OBJECT + + public: + KexiTimeTableEdit(KexiTableViewColumn &column, QWidget *parent=0); + virtual ~KexiTimeTableEdit(); + virtual void setupContents( QPainter *p, bool focused, const QVariant& val, + QString &txt, int &align, int &x, int &y_offset, int &w, int &h ); + virtual QVariant value(); + virtual bool valueIsNull(); + virtual bool valueIsEmpty(); + virtual bool valueIsValid(); + + /*! Reimplemented after KexiInputTableEdit. */ + virtual void handleAction(const QString& actionName); + + /*! Reimplemented after KexiInputTableEdit. */ + virtual void handleCopyAction(const QVariant& value, const QVariant& visibleValue); + + protected: + //! helper + void setValueInInternalEditor(const QVariant &value); + virtual void setValueInternal(const QVariant& add, bool removeOld); + QTime timeValue(); + + //! Used to format and convert time values + KexiTimeFormatter m_formatter; +}; + +KEXI_DECLARE_CELLEDITOR_FACTORY_ITEM(KexiTimeEditorFactoryItem) + +#endif diff --git a/kexi/widget/utils/Makefile.am b/kexi/widget/utils/Makefile.am new file mode 100644 index 00000000..5d210f1a --- /dev/null +++ b/kexi/widget/utils/Makefile.am @@ -0,0 +1,19 @@ +include $(top_srcdir)/kexi/Makefile.global + +lib_LTLIBRARIES = libkexiguiutils.la +libkexiguiutils_la_SOURCES = kexisharedactionclient.cpp kexirecordnavigator.cpp \ + kexigradientwidget.cpp kexirecordmarker.cpp kexidisplayutils.cpp \ + kexiflowlayout.cpp kexidatetimeformatter.cpp kexitooltip.cpp kexiarrowtip.cpp \ + kexidropdownbutton.cpp kexicomboboxdropdownbutton.cpp kexicontextmenuutils.cpp + +libkexiguiutils_la_LDFLAGS = $(all_libraries) $(VER_INFO) -Wnounresolved +libkexiguiutils_la_LIBADD = $(LIB_KDEUI) + +SUBDIRS = . + +# set the include path for X, qt and KDE - all_includes must remain last! +INCLUDES = -I$(top_srcdir)/kexi -I$(top_srcdir)/kexi/widget/utils $(all_includes) + +METASOURCES = AUTO + +noinst_HEADERS = kexigradientwidget.h diff --git a/kexi/widget/utils/kexiarrowtip.cpp b/kexi/widget/utils/kexiarrowtip.cpp new file mode 100644 index 00000000..cdffcb02 --- /dev/null +++ b/kexi/widget/utils/kexiarrowtip.cpp @@ -0,0 +1,164 @@ +/* This file is part of the KDE project + Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "kexiarrowtip.h" + +#include <qpixmap.h> +#include <qbitmap.h> +#include <qpainter.h> +#include <qimage.h> +#include <qtooltip.h> +#include <qfont.h> +#include <qfontmetrics.h> +#include <qtimer.h> + +#include <kexiutils/utils.h> + +KexiArrowTip::KexiArrowTip(const QString& text, QWidget* parent) + : KexiToolTip(text, parent) + , m_opacity(0.0) +{ + QPalette pal( palette() ); + QColorGroup cg(pal.active()); + cg.setColor(QColorGroup::Foreground, Qt::red); + pal.setActive(cg); + setPalette(pal); + + QFontMetrics fm(font()); + QSize sz(fm.boundingRect(m_value.toString()).size()); + sz += QSize(14, 10); //+margins + m_arrowHeight = sz.height()/2; + sz += QSize(0, m_arrowHeight); //+arrow height + resize(sz); + + setAutoMask( false ); + + //generate mask + QPixmap maskPm(size()); + maskPm.fill( black ); + QPainter maskPainter(&maskPm); + drawFrame(maskPainter); + QImage maskImg( maskPm.convertToImage() ); + QBitmap bm; + bm = maskImg.createHeuristicMask(); + setMask( bm ); +} + +KexiArrowTip::~KexiArrowTip() +{ +} + +void KexiArrowTip::show() +{ + if (isVisible()) + return; + + m_opacity = 0.0; + setWindowOpacity(0.0); + KexiToolTip::show(); + increaseOpacity(); +} + +void KexiArrowTip::hide() +{ + if (!isVisible()) + return; + + decreaseOpacity(); +} + +void KexiArrowTip::increaseOpacity() +{ + m_opacity += 0.10; + setWindowOpacity(m_opacity); + if (m_opacity < 1.0) + QTimer::singleShot(25, this, SLOT(increaseOpacity())); +} + +void KexiArrowTip::decreaseOpacity() +{ + if (m_opacity<=0.0) { + KexiToolTip::close(); + m_opacity = 0.0; + return; + } + m_opacity -= 0.10; + setWindowOpacity(m_opacity); + QTimer::singleShot(25, this, SLOT(decreaseOpacity())); +} + +bool KexiArrowTip::close ( bool alsoDelete ) +{ + if (!isVisible()) { + return KexiToolTip::close(alsoDelete); + } + if (m_opacity>0.0) + decreaseOpacity(); + else + return KexiToolTip::close(alsoDelete); + return m_opacity<=0.0; +} + +void KexiArrowTip::drawContents(QPainter& p) +{ + p.setPen( QPen(palette().active().foreground(), 1) ); + p.drawText(QRect(0,m_arrowHeight,width(),height()-m_arrowHeight), + Qt::AlignCenter, m_value.toString()); +} + +void KexiArrowTip::drawFrame(QPainter& p) +{ + QPen pen(palette().active().foreground(), 1, Qt::SolidLine, Qt::SquareCap, Qt::MiterJoin); + p.setPen( pen ); + /* + /\ + +- -----+ + | text | + +--------+ + */ + //1st line + const int arrowOffset = 5; //5 pixels to right + QPointArray pa(8); + pa.setPoint(0, 0, m_arrowHeight-1); + pa.setPoint(1, 0, height()-1); + pa.setPoint(2, width()-1, height()-1); + pa.setPoint(3, width()-1, m_arrowHeight-1); + pa.setPoint(4, arrowOffset+m_arrowHeight+m_arrowHeight-2, m_arrowHeight-1); + pa.setPoint(5, arrowOffset+m_arrowHeight-1, 0); + pa.setPoint(6, arrowOffset, m_arrowHeight-1); + pa.setPoint(7, 0, m_arrowHeight-1); + p.drawPolyline(pa); + //-2nd, internal line + pa.resize(12); + pa.setPoint(0, 1, m_arrowHeight); + pa.setPoint(1, 1, height()-2); + pa.setPoint(2, width()-2, height()-2); + pa.setPoint(3, width()-2, m_arrowHeight); + pa.setPoint(4, arrowOffset+m_arrowHeight+m_arrowHeight-2, m_arrowHeight); + pa.setPoint(5, arrowOffset+m_arrowHeight-1, 1); + pa.setPoint(6, arrowOffset, m_arrowHeight); + pa.setPoint(7, 0, m_arrowHeight); + pa.setPoint(8, arrowOffset+1, m_arrowHeight); + pa.setPoint(9, arrowOffset+m_arrowHeight-1, 2); + pa.setPoint(10, arrowOffset+m_arrowHeight+m_arrowHeight-3, m_arrowHeight); + pa.setPoint(11, width()-2, m_arrowHeight); + p.drawPolyline(pa); +} + +#include "kexiarrowtip.moc" diff --git a/kexi/widget/utils/kexiarrowtip.h b/kexi/widget/utils/kexiarrowtip.h new file mode 100644 index 00000000..d6de6186 --- /dev/null +++ b/kexi/widget/utils/kexiarrowtip.h @@ -0,0 +1,56 @@ +/* This file is part of the KDE project + Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIARROWTIP_H +#define KEXIARROWTIP_H + +#include "kexitooltip.h" + +//! \brief A tooltip-like widget with additional arrow +/*! The widget also suppors fade in and fade out effect, + if the underlying display system supports this. +*/ +class KEXIGUIUTILS_EXPORT KexiArrowTip : public KexiToolTip +{ + Q_OBJECT + public: + KexiArrowTip(const QString& text, QWidget* parent); + virtual ~KexiArrowTip(); + + inline QString text() const { return m_value.toString(); } + virtual bool close() { return close(false); } + virtual bool close( bool alsoDelete ); + + public slots: + virtual void show(); + virtual void hide(); + + protected slots: + void increaseOpacity(); + void decreaseOpacity(); + + protected: + virtual void drawFrame(QPainter& p); + virtual void drawContents(QPainter& p); + + int m_arrowHeight; + double m_opacity; +}; + +#endif diff --git a/kexi/widget/utils/kexicomboboxdropdownbutton.cpp b/kexi/widget/utils/kexicomboboxdropdownbutton.cpp new file mode 100644 index 00000000..407bc6fe --- /dev/null +++ b/kexi/widget/utils/kexicomboboxdropdownbutton.cpp @@ -0,0 +1,87 @@ +/* This file is part of the KDE project + Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "kexicomboboxdropdownbutton.h" + +#include <kpopupmenu.h> +#include <kdebug.h> +#include <kcombobox.h> + +#include <qstyle.h> +#include <qapplication.h> + +KexiComboBoxDropDownButton::KexiComboBoxDropDownButton( QWidget *parent ) + : KPushButton(parent) +{ + m_paintedCombo = new KComboBox(this); + m_paintedCombo->hide(); + m_paintedCombo->setEditable(true); + + setToggleButton(true); + styleChange(style()); + m_paintedCombo->move(0,0); + m_paintedCombo->setFixedSize(size()); +} + +KexiComboBoxDropDownButton::~KexiComboBoxDropDownButton() +{ +} + +void KexiComboBoxDropDownButton::drawButton(QPainter *p) +{ + int flags = QStyle::Style_Enabled | QStyle::Style_HasFocus; + if (isDown()) + flags |= QStyle::Style_Down; + + KPushButton::drawButton(p); + + QRect r = rect(); + r.setHeight(r.height()+m_fixForHeight); + if (m_drawComplexControl) { + if (m_fixForHeight>0 && m_paintedCombo->size()!=size()) { + m_paintedCombo->move(0,0); + m_paintedCombo->setFixedSize(size()+QSize(0, m_fixForHeight)); //last chance to fix size + } + style().drawComplexControl( QStyle::CC_ComboBox, p, + m_fixForHeight>0 ? (const QWidget*)m_paintedCombo : this, r, colorGroup(), + flags, (uint)(QStyle::SC_ComboBoxArrow), QStyle::SC_None ); + } + else { + r.setWidth(r.width()+2); + style().drawPrimitive( QStyle::PE_ArrowDown, p, r, colorGroup(), flags); + } +} + +void KexiComboBoxDropDownButton::styleChange( QStyle & oldStyle ) +{ + //<hack> + if (qstricmp(style().name(),"thinkeramik")==0) { + m_fixForHeight = 3; + } + else + m_fixForHeight = 0; + //</hack> + m_drawComplexControl = + (style().inherits("KStyle") && qstricmp(style().name(),"qtcurve")!=0) + || qstricmp(style().name(),"platinum")==0; + if (m_fixForHeight==0) + setFixedWidth( style().querySubControlMetrics( QStyle::CC_ComboBox, + (const QWidget*)m_paintedCombo, QStyle::SC_ComboBoxArrow ).width() +1 ); + KPushButton::styleChange(oldStyle); +} diff --git a/kexi/widget/utils/kexicomboboxdropdownbutton.h b/kexi/widget/utils/kexicomboboxdropdownbutton.h new file mode 100644 index 00000000..da53a7e2 --- /dev/null +++ b/kexi/widget/utils/kexicomboboxdropdownbutton.h @@ -0,0 +1,49 @@ +/* This file is part of the KDE project + Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KexiComboBoxDropDownButton_H +#define KexiComboBoxDropDownButton_H + +#include <kpushbutton.h> + +class KComboBox; + +//! @short A drop-down button for combo box widgets +/*! Used in KexiComboBoxTableEdit. +*/ +class KEXIGUIUTILS_EXPORT KexiComboBoxDropDownButton : public KPushButton +{ + public: + KexiComboBoxDropDownButton( QWidget *parent ); + virtual ~KexiComboBoxDropDownButton(); + + protected: + /*! Reimplemented after @ref KPushButton to draw drop-down arrow. */ + virtual void drawButton(QPainter *p); + + /*! Reimplemented after @ref KPushButton to adapt size to style changes. */ + virtual void styleChange( QStyle & oldStyle ); + + int m_fixForHeight; + bool m_drawComplexControl : 1; + KComboBox *m_paintedCombo; //!< fake combo used only to pass it as 'this' for QStyle + //!< (because styles use \<static_cast\>) +}; + +#endif diff --git a/kexi/widget/utils/kexicontextmenuutils.cpp b/kexi/widget/utils/kexicontextmenuutils.cpp new file mode 100644 index 00000000..727cef6f --- /dev/null +++ b/kexi/widget/utils/kexicontextmenuutils.cpp @@ -0,0 +1,283 @@ +/* This file is part of the KDE project + Copyright (C) 2006-2007 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "kexicontextmenuutils.h" + +#include <kactioncollection.h> +#include <klocale.h> +#include <kiconloader.h> +#include <kfiledialog.h> +#include <kimageio.h> +#include <kdebug.h> +#include <kmessagebox.h> + +#include <qfiledialog.h> +#include <qapplication.h> + +#ifdef Q_WS_WIN +#include <win32_utils.h> +#include <krecentdirs.h> +#endif + +//! @internal +class KexiImageContextMenu::Private +{ +public: + Private(QWidget *parent) + : actionCollection(parent) + { + } + + KActionCollection actionCollection; + KAction *insertFromFileAction, *saveAsAction, *cutAction, *copyAction, *pasteAction, + *deleteAction +#ifdef KEXI_NO_UNFINISHED + , *propertiesAction +#endif + ; +}; + +//------------ + +KexiImageContextMenu::KexiImageContextMenu(QWidget* parent) + : KPopupMenu(parent) + , d( new Private(this) ) +{ + setName("KexiImageContextMenu"); + insertTitle(QString::null); + + d->insertFromFileAction = new KAction(i18n("Insert From &File..."), SmallIconSet("fileopen"), 0, + this, SLOT(insertFromFile()), &d->actionCollection, "insert"); + d->insertFromFileAction->plug(this); + d->saveAsAction = KStdAction::saveAs(this, SLOT(saveAs()), &d->actionCollection); +// d->saveAsAction->setText(i18n("&Save &As...")); + d->saveAsAction->plug(this); + insertSeparator(); + d->cutAction = KStdAction::cut(this, SLOT(cut()), &d->actionCollection); + d->cutAction->plug(this); + d->copyAction = KStdAction::copy(this, SLOT(copy()), &d->actionCollection); + d->copyAction->plug(this); + d->pasteAction = KStdAction::paste(this, SLOT(paste()), &d->actionCollection); + d->pasteAction->plug(this); + d->deleteAction = new KAction(i18n("&Clear"), SmallIconSet("editdelete"), 0, + this, SLOT(clear()), &d->actionCollection, "delete"); + d->deleteAction->plug(this); +#ifdef KEXI_NO_UNFINISHED + d->propertiesAction = 0; +#else + insertSeparator(); + d->propertiesAction = new KAction(i18n("Properties"), 0, 0, + this, SLOT(showProperties()), &d->actionCollection, "properties"); + d->propertiesAction->plug(this); +#endif + connect(this, SIGNAL(aboutToShow()), this, SLOT(updateActionsAvailability())); +} + +KexiImageContextMenu::~KexiImageContextMenu() +{ + delete d; +} + +void KexiImageContextMenu::insertFromFile() +{ +// QWidget *focusWidget = qApp->focusWidget(); +#ifdef Q_WS_WIN + QString recentDir; + QString fileName = QFileDialog::getOpenFileName( + KFileDialog::getStartURL(":LastVisitedImagePath", recentDir).path(), + convertKFileDialogFilterToQFileDialogFilter(KImageIO::pattern(KImageIO::Reading)), + this, 0, i18n("Insert Image From File")); + KURL url; + if (!fileName.isEmpty()) + url.setPath( fileName ); +#else + KURL url( KFileDialog::getImageOpenURL( + ":LastVisitedImagePath", this, i18n("Insert Image From File")) ); +// QString fileName = url.isLocalFile() ? url.path() : url.prettyURL(); + + //! @todo download the file if remote, then set fileName properly +#endif + if (!url.isValid()) { + //focus the app again because to avoid annoying the user with unfocused main window + if (qApp->mainWidget()) { + //focusWidget->raise(); + //focusWidget->setFocus(); + qApp->mainWidget()->raise(); + } + return; + } + kexipluginsdbg << "fname=" << url.prettyURL() << endl; + +#ifdef Q_WS_WIN + //save last visited path +// KURL url(fileName); + if (url.isLocalFile()) + KRecentDirs::add(":LastVisitedImagePath", url.directory()); +#endif + + emit insertFromFileRequested(url); + if (qApp->mainWidget()) { +// focusWidget->raise(); +// focusWidget->setFocus(); + qApp->mainWidget()->raise(); + } +} + +void KexiImageContextMenu::saveAs() +{ + QString origFilename, fileExtension; + bool dataIsEmpty = false; + emit aboutToSaveAsRequested(origFilename, fileExtension, dataIsEmpty); + + if (dataIsEmpty) { + kdWarning() << "KexiImageContextMenu::saveAs(): no data!" << endl; + return; + } + if (!origFilename.isEmpty()) + origFilename = QString("/") + origFilename; + + if (fileExtension.isEmpty()) { + // PNG data is the default + fileExtension = "png"; + } + +#ifdef Q_WS_WIN + QString recentDir; + QString fileName = QFileDialog::getSaveFileName( + KFileDialog::getStartURL(":LastVisitedImagePath", recentDir).path() + origFilename, + convertKFileDialogFilterToQFileDialogFilter(KImageIO::pattern(KImageIO::Writing)), + this, 0, i18n("Save Image to File")); +#else + //! @todo add originalFileName! (requires access to KRecentDirs) + QString fileName = KFileDialog::getSaveFileName( + ":LastVisitedImagePath", KImageIO::pattern(KImageIO::Writing), this, i18n("Save Image to File")); +#endif + if (fileName.isEmpty()) + return; + + if (QFileInfo(fileName).extension().isEmpty()) + fileName += (QString(".")+fileExtension); + kdDebug() << fileName << endl; + KURL url; + url.setPath( fileName ); + +#ifdef Q_WS_WIN + //save last visited path + if (url.isLocalFile()) + KRecentDirs::add(":LastVisitedImagePath", url.directory()); +#endif + + QFile f(fileName); + if (f.exists() && KMessageBox::Yes != KMessageBox::warningYesNo(this, + "<qt>"+i18n("File \"%1\" already exists." + "<p>Do you want to replace it with a new one?") + .arg(QDir::convertSeparators(fileName))+"</qt>",0, + KGuiItem(i18n("&Replace")), KGuiItem(i18n("&Don't Replace")))) + { + return; + } + +//! @todo use KURL? + emit saveAsRequested(fileName); +} + +void KexiImageContextMenu::cut() +{ + emit cutRequested(); +} + +void KexiImageContextMenu::copy() +{ + emit copyRequested(); +} + +void KexiImageContextMenu::paste() +{ + emit pasteRequested(); +} + +void KexiImageContextMenu::clear() +{ + emit clearRequested(); +} + +void KexiImageContextMenu::showProperties() +{ + emit showPropertiesRequested(); +} + +void KexiImageContextMenu::updateActionsAvailability() +{ + bool valueIsNull = true; + bool valueIsReadOnly = true; + emit updateActionsAvailabilityRequested(valueIsNull, valueIsReadOnly); + + d->insertFromFileAction->setEnabled( !valueIsReadOnly ); + d->saveAsAction->setEnabled( !valueIsNull ); + d->cutAction->setEnabled( !valueIsNull && !valueIsReadOnly ); + d->copyAction->setEnabled( !valueIsNull ); + d->pasteAction->setEnabled( !valueIsReadOnly ); + d->deleteAction->setEnabled( !valueIsNull && !valueIsReadOnly ); + if (d->propertiesAction) + d->propertiesAction->setEnabled( !valueIsNull ); +} + +KActionCollection* KexiImageContextMenu::actionCollection() const +{ + return &d->actionCollection; +} + +//static +bool KexiImageContextMenu::updateTitle(QPopupMenu *menu, const QString& title, const QString& iconName) +{ + return KexiContextMenuUtils::updateTitle(menu, title, i18n("Image"), iconName); +} + +// ------------------------------------------- + +//static +bool KexiContextMenuUtils::updateTitle(QPopupMenu *menu, const QString& objectName, + const QString& objectTypeName, const QString& iconName) +{ + if (!menu || objectName.isEmpty() || objectTypeName.isEmpty()) + return false; + const int id = menu->idAt(0); + QMenuItem *item = menu->findItem(id); + if (!item) + return false; + KPopupTitle *title = dynamic_cast<KPopupTitle *>(item->widget()); + if (!title) + return false; + +/*! @todo look at makeFirstCharacterUpperCaseInCaptions setting [bool] + (see doc/dev/settings.txt) */ + QString realTitle( i18n("Object name : Object type", "%1 : %2") + .arg( objectName[0].upper() + objectName.mid(1) ) + .arg( objectTypeName )); + + if (iconName.isEmpty()) + title->setTitle(realTitle); + else { + QPixmap pixmap(SmallIcon( iconName )); + title->setTitle(realTitle, &pixmap); + } + return true; +} + +#include "kexicontextmenuutils.moc" diff --git a/kexi/widget/utils/kexicontextmenuutils.h b/kexi/widget/utils/kexicontextmenuutils.h new file mode 100644 index 00000000..95258e96 --- /dev/null +++ b/kexi/widget/utils/kexicontextmenuutils.h @@ -0,0 +1,112 @@ +/* This file is part of the KDE project + Copyright (C) 2006-2007 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KexiContextMenuUtils_H +#define KexiContextMenuUtils_H + +#include <kexidb/queryschema.h> +#include <kpopupmenu.h> +#include <kurl.h> + +class KActionCollection; +class KexiDataItemInterface; + +//! @short A set of helpers for updating popup menu titles +/*! The functions set meaningful titles like "Emploee : Image". +*/ +class KEXIGUIUTILS_EXPORT KexiContextMenuUtils +{ + public: + /*! Updates title for context menu. + \return true if the title has been updated. */ + static bool updateTitle(QPopupMenu *menu, const QString& objectName, + const QString& objectTypeName, const QString& iconName); +}; + +//! @short A context menu used for images within form and table views +/*! Used in KexiDBImageBox and KexiBlobTableEdit. + Contains actions like insert, save, copy, paste, clear. + + Signals like insertFromFileRequested() are all connected to + handlers in KexiDBImageBox and KexiBlobTableEdit so these objects can + respond on requests for data handling. +*/ +class KEXIGUIUTILS_EXPORT KexiImageContextMenu : public KPopupMenu +{ + Q_OBJECT + + public: + KexiImageContextMenu(QWidget *parent); + virtual ~KexiImageContextMenu(); + + KActionCollection* actionCollection() const; + + /*! Updates title for context menu. + Used in KexiDBWidgetContextMenuExtender::createTitle(QPopupMenu *menu) and KexiDBImageBox. + \return true if the title has been updated. */ + static bool updateTitle(QPopupMenu *menu, const QString& title, const QString& iconName = QString::null); + + public slots: + void updateActionsAvailability(); + + virtual void insertFromFile(); + virtual void saveAs(); + virtual void cut(); + virtual void copy(); + virtual void paste(); + virtual void clear(); + virtual void showProperties(); + + signals: + //! Emitted when actions availability should be performed. Just connect this signal + //! to a slot and set \a valueIsNull and \a valueIsReadOnly. + void updateActionsAvailabilityRequested(bool& valueIsNull, bool& valueIsReadOnly); + + /*! Emitted before "insertFromFile" action was requested. */ + void insertFromFileRequested(const KURL &url); + + /*! Emitted before "saveAs" action was requested. + You should fill \a origFilename, \a fileExtension and \a dataIsEmpty values. + If \a dataIsEmpty is false, saving will be cancelled. */ + void aboutToSaveAsRequested(QString& origFilename, QString& fileExtension, bool& dataIsEmpty); + + //! Emitted when "saveAs" action was requested + void saveAsRequested(const QString& fileName); + + //! Emitted when "cut" action was requested + void cutRequested(); + + //! Emitted when "copy" action was requested + void copyRequested(); + + //! Emitted when "paste" action was requested + void pasteRequested(); + + //! Emitted when "clear" action was requested + void clearRequested(); + + //! Emitted when "showProperties" action was requested + void showPropertiesRequested(); + + protected: + class Private; + Private *d; +}; + +#endif diff --git a/kexi/widget/utils/kexidatetimeformatter.cpp b/kexi/widget/utils/kexidatetimeformatter.cpp new file mode 100644 index 00000000..d8f642ca --- /dev/null +++ b/kexi/widget/utils/kexidatetimeformatter.cpp @@ -0,0 +1,367 @@ +/* This file is part of the KDE project + Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "kexidatetimeformatter.h" + +#include <kdebug.h> +#include <klocale.h> +#include <kglobal.h> +#include <kdatepicker.h> +#include <kdatetbl.h> +#include <klineedit.h> +#include <kpopupmenu.h> +#include <kdatewidget.h> + +KexiDateFormatter::KexiDateFormatter() +{ + // use "short date" format system settings +//! @todo allow to override the format using column property and/or global app settings + QString df( KGlobal::locale()->dateFormatShort() ); + if (df.length()>2) + m_separator = df.mid(2,1); + else + m_separator = "-"; + const int separatorLen = m_separator.length(); + QString yearMask("9999"); + QString yearDateFormat("yyyy"), + monthDateFormat("MM"), + dayDateFormat("dd"); //for setting up m_dateFormat + bool ok = df.length()>=8; + int yearpos, monthpos, daypos; //result of df.find() + if (ok) {//look at % variables +//! @todo more variables are possible here, see void KLocale::setDateFormatShort() docs +//! http://developer.kde.org/documentation/library/3.5-api/kdelibs-apidocs/kdecore/html/classKLocale.html#a59 + yearpos = df.find("%y", 0, false); //&y or %y + m_longYear = !(yearpos>=0 && df.mid(yearpos+1, 1)=="y"); + if (!m_longYear) { + yearMask = "99"; + yearDateFormat = "yy"; + } + monthpos = df.find("%m", 0, true); //%m or %n + m_monthWithLeadingZero = true; + if (monthpos<0) { + monthpos = df.find("%n", 0, false); + m_monthWithLeadingZero = false; + monthDateFormat = "M"; + } + daypos = df.find("%d", 0, true);//%d or %e + m_dayWithLeadingZero = true; + if (daypos<0) { + daypos = df.find("%e", 0, false); + m_dayWithLeadingZero = false; + dayDateFormat = "d"; + } + ok = (yearpos>=0 && monthpos>=0 && daypos>=0); + } + m_order = QDateEdit::YMD; //default + if (ok) { + if (yearpos<monthpos && monthpos<daypos) { + //will be set in "default: YMD" + } + else if (yearpos<daypos && daypos<monthpos) { + m_order = QDateEdit::YDM; +//! @todo use QRegExp (to replace %Y by %1, etc.) instead of hardcoded "%1%299%399" +//! because df may contain also other characters + m_inputMask = QString("%1%299%399").arg(yearMask).arg(m_separator).arg(m_separator); + m_qtFormat = yearDateFormat+m_separator+dayDateFormat+m_separator+monthDateFormat; + m_yearpos = 0; + m_daypos = yearMask.length()+separatorLen; + m_monthpos = m_daypos+2+separatorLen; + } + else if (daypos<monthpos && monthpos<yearpos) { + m_order = QDateEdit::DMY; + m_inputMask = QString("99%199%2%3").arg(m_separator).arg(m_separator).arg(yearMask); + m_qtFormat = dayDateFormat+m_separator+monthDateFormat+m_separator+yearDateFormat; + m_daypos = 0; + m_monthpos = 2+separatorLen; + m_yearpos = m_monthpos+2+separatorLen; + } + else if (monthpos<daypos && daypos<yearpos) { + m_order = QDateEdit::MDY; + m_inputMask = QString("99%199%2%3").arg(m_separator).arg(m_separator).arg(yearMask); + m_qtFormat = monthDateFormat+m_separator+dayDateFormat+m_separator+yearDateFormat; + m_monthpos = 0; + m_daypos = 2+separatorLen; + m_yearpos = m_daypos+2+separatorLen; + } + else + ok = false; + } + if (!ok || m_order == QDateEdit::YMD) {//default: YMD + m_inputMask = QString("%1%299%399").arg(yearMask).arg(m_separator).arg(m_separator); + m_qtFormat = yearDateFormat+m_separator+monthDateFormat+m_separator+dayDateFormat; + m_yearpos = 0; + m_monthpos = yearMask.length()+separatorLen; + m_daypos = m_monthpos+2+separatorLen; + } + m_inputMask += ";_"; +} + +KexiDateFormatter::~KexiDateFormatter() +{ +} + +QDate KexiDateFormatter::stringToDate( const QString& str ) const +{ + bool ok = true; + int year = str.mid(m_yearpos, m_longYear ? 4 : 2).toInt(&ok); + if (!ok) + return QDate(); + if (year < 30) {//2000..2029 + year = 2000 + year; + } + else if (year < 100) {//1930..1999 + year = 1900 + year; + } + + int month = str.mid(m_monthpos, 2).toInt(&ok); + if (!ok) + return QDate(); + + int day = str.mid(m_daypos, 2).toInt(&ok); + if (!ok) + return QDate(); + + QDate date(year, month, day); + if (!date.isValid()) + return QDate(); + return date; +} + +QVariant KexiDateFormatter::stringToVariant( const QString& str ) const +{ + if (isEmpty(str)) + return QVariant(); + const QDate date( stringToDate( str ) ); + if (date.isValid()) + return date; + return QVariant(); +} + +bool KexiDateFormatter::isEmpty( const QString& str ) const +{ + QString s(str); + return s.replace(m_separator,"").stripWhiteSpace().isEmpty(); +} + +QString KexiDateFormatter::dateToString( const QDate& date ) const +{ + return date.toString(m_qtFormat); +} + +//------------------------------------------------ + +KexiTimeFormatter::KexiTimeFormatter() +: m_hmsRegExp( new QRegExp("(\\d*):(\\d*):(\\d*).*( am| pm){,1}", false/*!CS*/) ) + , m_hmRegExp( new QRegExp("(\\d*):(\\d*).*( am| pm){,1}", false/*!CS*/) ) +{ + QString tf( KGlobal::locale()->timeFormat() ); + //m_hourpos, m_minpos, m_secpos; are result of tf.find() + QString hourVariable, minVariable, secVariable; + + //detect position of HOUR section: find %H or %k or %I or %l + m_24h = true; + m_hoursWithLeadingZero = true; + m_hourpos = tf.find("%H", 0, true); + if (m_hourpos>=0) { + m_24h = true; + m_hoursWithLeadingZero = true; + } + else { + m_hourpos = tf.find("%k", 0, true); + if (m_hourpos>=0) { + m_24h = true; + m_hoursWithLeadingZero = false; + } + else { + m_hourpos = tf.find("%I", 0, true); + if (m_hourpos>=0) { + m_24h = false; + m_hoursWithLeadingZero = true; + } + else { + m_hourpos = tf.find("%l", 0, true); + if (m_hourpos>=0) { + m_24h = false; + m_hoursWithLeadingZero = false; + } + } + } + } + m_minpos = tf.find("%M", 0, true); + m_secpos = tf.find("%S", 0, true); //can be -1 + m_ampmpos = tf.find("%p", 0, true); //can be -1 + + if (m_hourpos<0 || m_minpos<0) { + //set default: hr and min are needed, sec are optional + tf = "%H:%M:%S"; + m_24h = true; + m_hoursWithLeadingZero = false; + m_hourpos = 0; + m_minpos = 3; + m_secpos = m_minpos + 3; + m_ampmpos = -1; + } + hourVariable = tf.mid(m_hourpos, 2); + + m_inputMask = tf; +// m_inputMask.replace( hourVariable, "00" ); +// m_inputMask.replace( "%M", "00" ); +// m_inputMask.replace( "%S", "00" ); //optional + m_inputMask.replace( hourVariable, "99" ); + m_inputMask.replace( "%M", "99" ); + m_inputMask.replace( "%S", "00" ); //optional + m_inputMask.replace( "%p", "AA" ); //am or pm + m_inputMask += ";_"; + + m_outputFormat = tf; +} + +KexiTimeFormatter::~KexiTimeFormatter() +{ + delete m_hmsRegExp; + delete m_hmRegExp; +} + +QTime KexiTimeFormatter::stringToTime( const QString& str ) const +{ + int hour, min, sec; + bool pm = false; + + bool tryWithoutSeconds = true; + if (m_secpos>=0) { + if (-1 != m_hmsRegExp->search(str)) { + hour = m_hmsRegExp->cap(1).toInt(); + min = m_hmsRegExp->cap(2).toInt(); + sec = m_hmsRegExp->cap(3).toInt(); + if (m_ampmpos >= 0 && m_hmsRegExp->numCaptures()>3) + pm = m_hmsRegExp->cap(4).stripWhiteSpace().lower()=="pm"; + tryWithoutSeconds = false; + } + } + if (tryWithoutSeconds) { + if (-1 == m_hmRegExp->search(str)) + return QTime(99,0,0); + hour = m_hmRegExp->cap(1).toInt(); + min = m_hmRegExp->cap(2).toInt(); + sec = 0; + if (m_ampmpos >= 0 && m_hmRegExp->numCaptures()>2) + pm = m_hmsRegExp->cap(4).lower()=="pm"; + } + + if (pm && hour < 12) + hour += 12; //PM + return QTime(hour, min, sec); +} + +QVariant KexiTimeFormatter::stringToVariant( const QString& str ) +{ + if (isEmpty( str )) + return QVariant(); + const QTime time( stringToTime( str ) ); + if (time.isValid()) + return time; + return QVariant(); +} + +bool KexiTimeFormatter::isEmpty( const QString& str ) const +{ + QString s(str); + return s.replace(':',"").stripWhiteSpace().isEmpty(); +} + +QString KexiTimeFormatter::timeToString( const QTime& time ) const +{ + if (!time.isValid()) + return QString::null; + + QString s(m_outputFormat); + if (m_24h) { + if (m_hoursWithLeadingZero) + s.replace( "%H", QString::fromLatin1(time.hour()<10 ? "0" : "") + QString::number(time.hour()) ); + else + s.replace( "%k", QString::number(time.hour()) ); + } + else { + int time12 = (time.hour()>12) ? (time.hour()-12) : time.hour(); + if (m_hoursWithLeadingZero) + s.replace( "%I", QString::fromLatin1(time12<10 ? "0" : "") + QString::number(time12) ); + else + s.replace( "%l", QString::number(time12) ); + } + s.replace( "%M", QString::fromLatin1(time.minute()<10 ? "0" : "") + QString::number(time.minute()) ); + if (m_secpos>=0) + s.replace( "%S", QString::fromLatin1(time.second()<10 ? "0" : "") + QString::number(time.second()) ); + if (m_ampmpos>=0) + s.replace( "%p", KGlobal::locale()->translate( time.hour()>=12 ? "pm" : "am") ); + return s; +} + +//------------------------------------------------ + +QString dateTimeInputMask(const KexiDateFormatter& dateFormatter, const KexiTimeFormatter& timeFormatter) +{ + QString mask(dateFormatter.inputMask()); + mask.truncate(dateFormatter.inputMask().length()-2); + return mask + " " + timeFormatter.inputMask(); +} + +QDateTime stringToDateTime( + const KexiDateFormatter& dateFormatter, const KexiTimeFormatter& timeFormatter, const QString& str) +{ + QString s( str.stripWhiteSpace() ); + const int timepos = s.find(" "); + const bool emptyTime = timepos >= 0 && timeFormatter.isEmpty(s.mid(timepos+1)); //.replace(':',"").stripWhiteSpace().isEmpty(); + if (emptyTime) + s = s.left(timepos); + if (timepos>0 && !emptyTime) { + return QDateTime( + dateFormatter.stringToDate( s.left(timepos) ), + timeFormatter.stringToTime( s.mid(timepos+1) ) + ); + } + else { + return QDateTime( + dateFormatter.stringToDate( s ), + QTime(0,0,0) + ); + } +} + +bool dateTimeIsEmpty( const KexiDateFormatter& dateFormatter, const KexiTimeFormatter& timeFormatter, + const QString& str ) +{ + int timepos = str.find(" "); + const bool emptyTime = timepos >= 0 && timeFormatter.isEmpty(str.mid(timepos+1)); //s.mid(timepos+1).replace(':',"").stripWhiteSpace().isEmpty(); + return (timepos >= 0 && dateFormatter.isEmpty(str.left(timepos)) //s.left(timepos).replace(m_dateFormatter.separator(), "").stripWhiteSpace().isEmpty() + && emptyTime); +} + +bool dateTimeIsValid( const KexiDateFormatter& dateFormatter, + const KexiTimeFormatter& timeFormatter, const QString& str ) +{ + int timepos = str.find(" "); + const bool emptyTime = timepos >= 0 && timeFormatter.isEmpty(str.mid(timepos+1)); //s.mid(timepos+1).replace(':',"").stripWhiteSpace().isEmpty(); + if (timepos >= 0 && dateFormatter.isEmpty(str.left(timepos)) // s.left(timepos).replace(m_dateFormatter.separator(), "").stripWhiteSpace().isEmpty() + && emptyTime) + //empty date/time is valid + return true; + return timepos>=0 && dateFormatter.stringToDate( str.left(timepos) ).isValid() + && (emptyTime /*date without time is also valid*/ || timeFormatter.stringToTime( str.mid(timepos+1) ).isValid()); +} diff --git a/kexi/widget/utils/kexidatetimeformatter.h b/kexi/widget/utils/kexidatetimeformatter.h new file mode 100644 index 00000000..252bc535 --- /dev/null +++ b/kexi/widget/utils/kexidatetimeformatter.h @@ -0,0 +1,165 @@ +/* This file is part of the KDE project + Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIDATETIMEFORMATTER_H +#define KEXIDATETIMEFORMATTER_H + +#include <qdatetimeedit.h> +#include <qregexp.h> + +//! @short Date formatter used by KexiDateTableEdit and KexiDateTimeTableEdit +class KEXIGUIUTILS_EXPORT KexiDateFormatter +{ + public: + //! Creates new formatter with KDE setting for "short date" + KexiDateFormatter(); + + //! Creates new formatter with given settings +//! @todo KexiDateFormatter(... settings ...); + + ~KexiDateFormatter(); + + //! Converts string \a str to date using predefined settings. + //! \return invalid date if the conversion is impossible + QDate stringToDate( const QString& str ) const; + + /*! Converts string \a str to date using predefined settings + and returns QVariant containing the date value. + This method does the same as stringToDate() but if \a string + contains invalid date representation, e.g. contains only spaces + and separators, null QVariant() is returned. */ + QVariant stringToVariant( const QString& str ) const; + + //! Converts \a date to string using predefined settings. + //! \return null string if \a date is invalid + QString dateToString( const QDate& date ) const; + + //! \return Input mask generated using the formatter settings. + //! Can be used in QLineEdit::setInputMask(). + QString inputMask() const { return m_inputMask; } + + //! \return separator for this date format, a single character like "-" or "/" + QString separator() const { return m_separator; } + + //! \return true if \a str contains only spaces + //! and separators according to the date format. + bool isEmpty( const QString& str ) const; + + protected: + //! Input mask generated using the formatter settings. Can be used in QLineEdit::setInputMask(). + QString m_inputMask; + + //! Order of date sections + QDateEdit::Order m_order; + + //! 4 or 2 digits + bool m_longYear; + + bool m_monthWithLeadingZero, m_dayWithLeadingZero; + + //! Date format used in dateToString() + QString m_qtFormat; + + //! Used in stringToDate() to convert string back to QDate + int m_yearpos, m_monthpos, m_daypos; + + QString m_separator; +}; + +/*! @short Time formatter used by KexiTimeTableEdit and KexiDateTimeTableEdit + Following time formats are allowed: HH:MM:SS (24h), HH:MM (24h), HH:MM AM/PM (12h) + Separator MUST be ":" */ +class KEXIGUIUTILS_EXPORT KexiTimeFormatter +{ + public: + //! Creates new formatter with KDE setting for time + KexiTimeFormatter(); + + //! Creates new formatter with given settings +//! @todo KexiDateFormatter(... settings ...); + + ~KexiTimeFormatter(); + + //! converts string \a str to time using predefined settings + //! \return invalid time if the conversion is impossible + QTime stringToTime( const QString& str ) const; + + /*! Converts string \a str to time using predefined settings + and returns QVariant containing the time value. + This method does the same as stringToTime() but if \a string + contains invalid time representation, e.g. contains only spaces + and separators, null QVariant() is returned. */ + QVariant stringToVariant( const QString& str ); + + //! converts \a time to string using predefined settings + //! \return null string if \a time is invalid + QString timeToString( const QTime& time ) const; + + //! \return Input mask generated using the formatter settings. + //! Can be used in QLineEdit::setInputMask(). + QString inputMask() const { return m_inputMask; } + + //! \return true if \a str contains only spaces + //! and separators according to the time format. + bool isEmpty( const QString& str ) const; + + protected: + //! Input mask generated using the formatter settings. Can be used in QLineEdit::setInputMask(). + QString m_inputMask; + +// //! Order of date sections +// QDateEdit::Order m_order; + + //! 12 or 12h + bool m_24h; + + bool m_hoursWithLeadingZero; + + //! Time format used in timeToString(). Notation from KLocale::setTimeFormat() is used. + QString m_outputFormat; + + //! Used in stringToTime() to convert string back to QTime + int m_hourpos, m_minpos, m_secpos, m_ampmpos; + + QRegExp *m_hmsRegExp, *m_hmRegExp; +}; + +//! \return a date/time input mask using date and time formatter. +//! Date is separated from time by one space character. +KEXIGUIUTILS_EXPORT QString dateTimeInputMask( + const KexiDateFormatter& dateFormatter, const KexiTimeFormatter& timeFormatter); + +/*! \return a QDateTime value converted from string using \a dateFormatter and \a timeFormatter. + A single space between date and time is assumed. + Invalid value is returned when \a str contains no valid date or \a str contains invalid time. + Value with time equal 00:00:00 is returned if \a str contains empty time part. */ +KEXIGUIUTILS_EXPORT QDateTime stringToDateTime( + const KexiDateFormatter& dateFormatter, const KexiTimeFormatter& timeFormatter, const QString& str); + +/*! \return true if \a str contains only spaces and separators according to formats provided by + \a dateFormatter and \a timeFormatter. */ +KEXIGUIUTILS_EXPORT bool dateTimeIsEmpty( const KexiDateFormatter& dateFormatter, + const KexiTimeFormatter& timeFormatter, const QString& str ); + +/*! \return true if \a str gives valid date/time value according to formats provided by + \a dateFormatter and \a timeFormatter. */ +KEXIGUIUTILS_EXPORT bool dateTimeIsValid( const KexiDateFormatter& dateFormatter, + const KexiTimeFormatter& timeFormatter, const QString& str ); + +#endif diff --git a/kexi/widget/utils/kexidisplayutils.cpp b/kexi/widget/utils/kexidisplayutils.cpp new file mode 100644 index 00000000..c7d238b1 --- /dev/null +++ b/kexi/widget/utils/kexidisplayutils.cpp @@ -0,0 +1,172 @@ +/* This file is part of the KDE project + Copyright (C) 2005-2006 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "kexidisplayutils.h" + +#include <qpixmap.h> +#include <qpainter.h> +#include <qimage.h> +#include <qwidget.h> + +#include <klocale.h> +#include <kstaticdeleter.h> + +// a color for displaying default values or autonumbers +#define SPECIAL_TEXT_COLOR Qt::blue + +static KStaticDeleter<QPixmap> KexiDisplayUtils_autonum_deleter; +QPixmap* KexiDisplayUtils_autonum = 0; + +static const unsigned int autonumber_png_len = 245; +static const unsigned char autonumber_png_data[] = { + 0x89,0x50,0x4e,0x47,0x0d,0x0a,0x1a,0x0a,0x00,0x00,0x00,0x0d,0x49,0x48, + 0x44,0x52,0x00,0x00,0x00,0x0b,0x00,0x00,0x00,0x0d,0x08,0x06,0x00,0x00, + 0x00,0x7f,0xf5,0x94,0x3b,0x00,0x00,0x00,0x06,0x62,0x4b,0x47,0x44,0x00, + 0xff,0x00,0xff,0x00,0xff,0xa0,0xbd,0xa7,0x93,0x00,0x00,0x00,0x09,0x70, + 0x48,0x59,0x73,0x00,0x00,0x0b,0x11,0x00,0x00,0x0b,0x11,0x01,0x7f,0x64, + 0x5f,0x91,0x00,0x00,0x00,0x07,0x74,0x49,0x4d,0x45,0x07,0xd4,0x08,0x14, + 0x0c,0x09,0x11,0x18,0x18,0x1d,0x4f,0x00,0x00,0x00,0x82,0x49,0x44,0x41, + 0x54,0x78,0x9c,0x8d,0x91,0x41,0x0e,0x03,0x31,0x08,0x03,0x87,0xbe,0x2e, + 0x1c,0xb3,0xff,0xbf,0xf6,0x1d,0xee,0x81,0xa0,0x05,0xaa,0x55,0x6b,0x29, + 0x92,0x03,0x06,0x59,0x06,0x49,0x48,0x02,0xa4,0xe4,0xf1,0x5f,0x1b,0xa4, + 0x78,0x6b,0xc3,0xc2,0x24,0x61,0x86,0x00,0x24,0x8c,0x83,0x53,0x33,0xe9, + 0xe6,0xaf,0x29,0x4a,0x48,0x29,0xf4,0x0d,0xbc,0xc1,0xe1,0xc9,0x46,0xb5, + 0x72,0xfa,0xcf,0xe2,0x2a,0x4c,0x71,0xf3,0x5c,0x2d,0xd5,0x5a,0xc0,0xcd, + 0x62,0xea,0x6f,0xf4,0x88,0x86,0x95,0xf0,0x4a,0xf2,0xee,0x6b,0xf8,0x1e, + 0x03,0x55,0xf8,0x73,0xf3,0x28,0x7e,0x6d,0x6e,0x69,0xc4,0xc6,0xfb,0x52, + 0x23,0x8d,0x3c,0x56,0x5e,0xd0,0x2f,0x40,0xd1,0xf4,0x6b,0xc4,0xd5,0xf8, + 0x07,0x69,0x14,0xc6,0x69,0x9a,0x12,0x79,0x9a,0x00,0x00,0x00,0x00,0x49, + 0x45,0x4e,0x44,0xae,0x42,0x60,0x82 +}; + +/* Generated by qembed */ +#include <qcstring.h> +#include <qdict.h> +static struct Embed { + unsigned int size; + const unsigned char *data; + const char *name; +} embed_vec[] = { + { 245, autonumber_png_data, "autonumber.png" }, + { 0, 0, 0 } +}; + +QPixmap* getPix(int id) +{ +// QByteArray ba; +// ba.setRawData( (char*)embed_vec[id].data, embed_vec[id].size ); + QPixmap *pix = new QPixmap(); + pix->loadFromData( embed_vec[id].data, embed_vec[id].size ); + return pix; +} + +static void initDisplayUtilsImages() +{ + if (!KexiDisplayUtils_autonum) { +/*! @warning not reentrant! */ + KexiDisplayUtils_autonum_deleter.setObject( KexiDisplayUtils_autonum, getPix(0) ); + } +} + +//----------------- + +KexiDisplayUtils::DisplayParameters::DisplayParameters() +{ +} + +KexiDisplayUtils::DisplayParameters::DisplayParameters(QWidget *w) +{ + textColor = w->palette().active().foreground(); + selectedTextColor = w->palette().active().highlightedText(); + font = w->font(); +} + +void KexiDisplayUtils::initDisplayForAutonumberSign(DisplayParameters& par, QWidget *widget) +{ + initDisplayUtilsImages(); + + par.textColor = SPECIAL_TEXT_COLOR; + par.selectedTextColor = SPECIAL_TEXT_COLOR; //hmm, unused anyway + par.font = widget->font(); + par.font.setItalic(true); + QFontMetrics fm(par.font); + par.textWidth = fm.width(i18n("(autonumber)")); + par.textHeight = fm.height(); +} + +void KexiDisplayUtils::initDisplayForDefaultValue(DisplayParameters& par, QWidget *widget) +{ + par.textColor = SPECIAL_TEXT_COLOR; + par.selectedTextColor = widget->palette().active().highlightedText(); + par.font = widget->font(); + par.font.setItalic(true); +} + +void KexiDisplayUtils::paintAutonumberSign(const DisplayParameters& par, QPainter* painter, + int x, int y, int width, int height, int align, bool overrideColor) +{ + painter->save(); + + painter->setFont(par.font); + if (!overrideColor) + painter->setPen(par.textColor); + +// int text_x = x; + if (!(align & Qt::AlignVertical_Mask)) + align |= Qt::AlignVCenter; + if (!(align & Qt::AlignHorizontal_Mask)) + align |= Qt::AlignLeft; + + int y_pixmap_pos = 0; + if (align & Qt::AlignVCenter) { + y_pixmap_pos = QMAX(0, y+1 + (height - KexiDisplayUtils_autonum->height())/2); + } + else if (align & Qt::AlignTop) { + y_pixmap_pos = y + QMAX(0, (par.textHeight - KexiDisplayUtils_autonum->height())/2); + } + else if (align & Qt::AlignBottom) { + y_pixmap_pos = y+1 + height - KexiDisplayUtils_autonum->height() + - QMAX(0, (par.textHeight - KexiDisplayUtils_autonum->height())/2); + } + + if (align & (Qt::AlignLeft | Qt::AlignJustify)) { +// text_x = x + KexiDisplayUtils_autonum->width() + 2; + if (!overrideColor) { + painter->drawPixmap( x, y_pixmap_pos, *KexiDisplayUtils_autonum ); + x += (KexiDisplayUtils_autonum->width() + 4); + } + } + else if (align & Qt::AlignRight) { + if (!overrideColor) { + painter->drawPixmap( x + width - par.textWidth - KexiDisplayUtils_autonum->width() - 4, + y_pixmap_pos, *KexiDisplayUtils_autonum ); + } + } + else if (align & Qt::AlignCenter) { + //! @todo + if (!overrideColor) + painter->drawPixmap( x + (width - par.textWidth)/2 - KexiDisplayUtils_autonum->width() - 4, + y_pixmap_pos, *KexiDisplayUtils_autonum ); + } + + painter->drawText(x, y, width, height, align, i18n("(autonumber)")); + + painter->restore(); +} + diff --git a/kexi/widget/utils/kexidisplayutils.h b/kexi/widget/utils/kexidisplayutils.h new file mode 100644 index 00000000..8790b662 --- /dev/null +++ b/kexi/widget/utils/kexidisplayutils.h @@ -0,0 +1,57 @@ +/* This file is part of the KDE project + Copyright (C) 2005-2006 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIDISPUTILS_H +#define KEXIDISPUTILS_H + +#include <qfont.h> +#include <qcolor.h> +class QWidget; + +//! \brief A set of utilities related to displaying common elements in Kexi, like e.g. (autonumber) sign +class KEXIGUIUTILS_EXPORT KexiDisplayUtils +{ + public: + //! Stores set of display parameters used in utility functions + class KEXIGUIUTILS_EXPORT DisplayParameters + { + public: + //! Creates uninitialized parameters + DisplayParameters(); + + //! Copies properties from \a w. + DisplayParameters(QWidget *w); + + QColor textColor, selectedTextColor; + QFont font; + int textWidth, textHeight; //!< used for "(autonumber)" text only + }; + + //! Initializes display parameters for autonumber sign + static void initDisplayForAutonumberSign(DisplayParameters& par, QWidget *widget); + + //! Paints autonumber sign using \a par parameters + static void paintAutonumberSign(const DisplayParameters& par, QPainter* painter, + int x, int y, int width, int height, int align, bool overrideColor = false); + + //! Initializes display parameters for default value + static void initDisplayForDefaultValue(DisplayParameters& par, QWidget *widget); +}; + +#endif diff --git a/kexi/widget/utils/kexidropdownbutton.cpp b/kexi/widget/utils/kexidropdownbutton.cpp new file mode 100644 index 00000000..a17e5cfb --- /dev/null +++ b/kexi/widget/utils/kexidropdownbutton.cpp @@ -0,0 +1,82 @@ +/* This file is part of the KDE project + Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "kexidropdownbutton.h" + +#include <kpopupmenu.h> +#include <kdebug.h> + +#include <qstyle.h> +#include <qapplication.h> + +KexiDropDownButton::KexiDropDownButton(QWidget *parent) + : QToolButton(parent, "KexiDBImageBox::Button") +{ + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); +//! @todo get this from a KStyle +// setFixedWidth(QMAX(18, qApp->globalStrut().width())); + int fixedWidth; + //hack + if (qstricmp(style().name(),"thinkeramik")==0) + fixedWidth = 18; //typical width as in "windows" style + else + fixedWidth = style().querySubControlMetrics( QStyle::CC_ComboBox, + this, QStyle::SC_ComboBoxArrow ).width(); + setFixedWidth( fixedWidth ); + setPopupDelay(10/*ms*/); +} + +KexiDropDownButton::~KexiDropDownButton() +{ +} + +void KexiDropDownButton::drawButton( QPainter *p ) +{ + QToolButton::drawButton(p); + QStyle::SFlags arrowFlags = QStyle::Style_Default; + if (isDown() || state()==On) + arrowFlags |= QStyle::Style_Down; + if (isEnabled()) + arrowFlags |= QStyle::Style_Enabled; + style().drawPrimitive(QStyle::PE_ArrowDown, p, + QRect((width()-7)/2, height()-9, 7, 7), colorGroup(), + arrowFlags, QStyleOption() ); +} + +QSize KexiDropDownButton::sizeHint () const +{ + return QSize( fontMetrics().maxWidth() + 2*2, fontMetrics().height()*2 + 2*2 ); +} + +void KexiDropDownButton::keyPressEvent( QKeyEvent * e ) +{ + const int k = e->key(); + const bool dropDown = (e->state() == Qt::NoButton && (k==Qt::Key_Space || k==Qt::Key_Enter || k==Qt::Key_Return || k==Qt::Key_F2 || k==Qt::Key_F4)) + || (e->state() == Qt::AltButton && k==Qt::Key_Down); + if (dropDown) { + e->accept(); + animateClick(); + QMouseEvent me( QEvent::MouseButtonPress, QPoint(2,2), Qt::LeftButton, Qt::NoButton ); + QApplication::sendEvent( this, &me ); + return; + } + QToolButton::keyPressEvent(e); +} + +#include "kexidropdownbutton.moc" diff --git a/kexi/widget/utils/kexidropdownbutton.h b/kexi/widget/utils/kexidropdownbutton.h new file mode 100644 index 00000000..fccbd409 --- /dev/null +++ b/kexi/widget/utils/kexidropdownbutton.h @@ -0,0 +1,45 @@ +/* This file is part of the KDE project + Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KexiDropDownButton_H +#define KexiDropDownButton_H + +#include <qtoolbutton.h> +#include <qguardedptr.h> + +//! @short A button for drop-down "Image" menu +/*! Used in KexiDBImageBox and KexiBlobTableEdit. + Additionally, the button reacts on pressing space, return, enter, + F2, F4 and alt+down buttons. */ +class KEXIGUIUTILS_EXPORT KexiDropDownButton : public QToolButton +{ + Q_OBJECT + + public: + KexiDropDownButton(QWidget *parent); + virtual ~KexiDropDownButton(); + + virtual void drawButton( QPainter *p ); + + virtual QSize sizeHint () const; + + virtual void keyPressEvent ( QKeyEvent * e ); +}; + +#endif diff --git a/kexi/widget/utils/kexiflowlayout.cpp b/kexi/widget/utils/kexiflowlayout.cpp new file mode 100644 index 00000000..b8a8601e --- /dev/null +++ b/kexi/widget/utils/kexiflowlayout.cpp @@ -0,0 +1,452 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr> + + 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 Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "kexiflowlayout.h" + +#include <kdebug.h> + +/// Iterator class + +class KexiFlowLayoutIterator : public QGLayoutIterator +{ + public: + KexiFlowLayoutIterator( QPtrList<QLayoutItem> *list ) + : m_idx(0), m_list( list ) + {} + uint count() const; + QLayoutItem *current(); + QLayoutItem *next(); + QLayoutItem *takeCurrent(); + + private: + int m_idx; + QPtrList<QLayoutItem> *m_list; +}; + +uint +KexiFlowLayoutIterator::count() const +{ + return m_list->count(); +} + +QLayoutItem * +KexiFlowLayoutIterator::current() +{ + return (m_idx < (int)count()) ? m_list->at(m_idx) : 0; +} + +QLayoutItem * +KexiFlowLayoutIterator::next() +{ + m_idx++; + return current(); +} + +QLayoutItem * +KexiFlowLayoutIterator::takeCurrent() +{ + return (m_idx < (int)count()) ? m_list->take(m_idx) : 0; +} + +//// The layout itself + +KexiFlowLayout::KexiFlowLayout(QWidget *parent, int border, int space, const char *name) + : QLayout(parent, border, space, name) +{ + m_orientation = Horizontal; + m_justify = false; + m_cached_width = 0; +} + +KexiFlowLayout::KexiFlowLayout(QLayout* parent, int space, const char *name) + : QLayout( parent, space, name ) +{ + m_orientation = Horizontal; + m_justify = false; + m_cached_width = 0; +} + +KexiFlowLayout::KexiFlowLayout(int space, const char *name) + : QLayout(space, name) + { + m_orientation = Horizontal; + m_justify = false; + m_cached_width = 0; + } + +KexiFlowLayout::~KexiFlowLayout() +{ + deleteAllItems(); +} + +void +KexiFlowLayout::addItem(QLayoutItem *item) +{ + m_list.append(item); +} + +void +KexiFlowLayout::addSpacing(int size) +{ + if (m_orientation == Horizontal) + addItem( new QSpacerItem( size, 0, QSizePolicy::Fixed, QSizePolicy::Minimum ) ); + else + addItem( new QSpacerItem( 0, size, QSizePolicy::Minimum, QSizePolicy::Fixed ) ); +} + +QLayoutIterator +KexiFlowLayout::iterator() +{ + return QLayoutIterator( new KexiFlowLayoutIterator(&m_list) ); +} + +QPtrList<QWidget>* +KexiFlowLayout::widgetList() const +{ + QPtrList<QWidget> *list = new QPtrList<QWidget>(); + for (QPtrListIterator<QLayoutItem> it(m_list); it.current(); ++it) { + if(it.current()->widget()) + list->append(it.current()->widget()); + } + return list; +} + +void +KexiFlowLayout::invalidate() +{ + QLayout::invalidate(); + m_cached_sizeHint = QSize(); + m_cached_minSize = QSize(); + m_cached_width = 0; +} + +bool +KexiFlowLayout::isEmpty() +{ + return m_list.isEmpty(); +} + +bool +KexiFlowLayout::hasHeightForWidth() const +{ + return (m_orientation == Horizontal); +} + +int +KexiFlowLayout::heightForWidth(int w) const +{ + if(m_cached_width != w) { + // workaround to allow this method to stay 'const' + KexiFlowLayout *mthis = (KexiFlowLayout*)this; + int h = mthis->simulateLayout( QRect(0,0,w,0) ); + mthis->m_cached_hfw = h; + mthis->m_cached_width = w; + return h; + } + return m_cached_hfw; +} + +QSize +KexiFlowLayout::sizeHint() const +{ + if(m_cached_sizeHint.isEmpty()) { + KexiFlowLayout *mthis = (KexiFlowLayout*)this; + QRect r = QRect(0, 0, 2000, 2000); + mthis->simulateLayout(r); + } + return m_cached_sizeHint; +} + +QSize +KexiFlowLayout::minimumSize() const +{ +//js: do we really need to simulate layout here? +// I commented this out because it was impossible to stretch layout conveniently. +// Now, minimum size is computed automatically based on item's minimumSize... +#if 0 + if(m_cached_minSize.isEmpty()) { + KexiFlowLayout *mthis = (KexiFlowLayout*)this; + QRect r = QRect(0, 0, 2000, 2000); + mthis->simulateLayout(r); + } +#endif + return m_cached_minSize; +} + +QSizePolicy::ExpandData +KexiFlowLayout::expanding() const +{ + if(m_orientation == Vertical) + return QSizePolicy::Vertically; + else + return QSizePolicy::Horizontally; +} + +void +KexiFlowLayout::setGeometry(const QRect &r) +{ + QLayout::setGeometry(r); + if(m_orientation == Horizontal) + doHorizontalLayout(r); + else + doVerticalLayout(r); +} + +int +KexiFlowLayout::simulateLayout(const QRect &r) +{ + if(m_orientation == Horizontal) + return doHorizontalLayout(r, true); + else + return doVerticalLayout(r, true); +} + +int +KexiFlowLayout::doHorizontalLayout(const QRect &r, bool testOnly) +{ + int x = r.x(); + int y = r.y(); + int h = 0; // height of this line + int availableSpace = r.width() + spacing(); + int expandingWidgets=0; // number of widgets in the line with QSizePolicy == Expanding + QPtrListIterator<QLayoutItem> it(m_list); + QPtrList<QLayoutItem> currentLine; + QLayoutItem *o; + QSize minSize, sizeHint(20, 20); + int minSizeHeight = 0 - spacing(); + + while ( (o = it.current()) != 0 ) { + if(o->isEmpty()) { /// do not consider hidden widgets + ++it; + continue; + } + +// kdDebug() << "- doHorizontalLayout(): " << o->widget()->className() << " " << o->widget()->name() << endl; + QSize oSizeHint = o->sizeHint(); // we cache these ones because it can take a while to get it (eg for child layouts) + if ((x + oSizeHint.width()) > r.right() && h > 0) { + // do the layout of current line + QPtrListIterator<QLayoutItem> it2(currentLine); + QLayoutItem *item; + int wx = r.x(); + int sizeHintWidth = 0 -spacing(), minSizeWidth=0 - spacing(), lineMinHeight=0; + while( (item = it2.current()) != 0 ) { + QSize itemSizeHint = item->sizeHint(); // we cache these ones because it can take + QSize itemMinSize = item->minimumSize(); // a while to get them + QSize s; + if(m_justify) { + if(expandingWidgets != 0) { + if(item->expanding() == QSizePolicy::Horizontally || item->expanding() == QSizePolicy::BothDirections) + s = QSize( QMIN(itemSizeHint.width() + availableSpace / expandingWidgets + , r.width()), itemSizeHint.height() ); + else + s = QSize( QMIN(itemSizeHint.width(), r.width()), itemSizeHint.height() ); + } + else + s = QSize( QMIN(itemSizeHint.width() + availableSpace / (int)currentLine.count() + , r.width()), itemSizeHint.height() ); + } + else + s = QSize ( QMIN(itemSizeHint.width(), r.width()), itemSizeHint.height() ); + if(!testOnly) + item->setGeometry( QRect(QPoint(wx, y), s) ); + wx = wx + s.width() + spacing(); + minSizeWidth = minSizeWidth + spacing() + itemMinSize.width(); + sizeHintWidth = sizeHintWidth + spacing() + itemSizeHint.width(); + lineMinHeight = QMAX( lineMinHeight, itemMinSize.height() ); + ++it2; + } + sizeHint = sizeHint.expandedTo( QSize(sizeHintWidth, 0) ); + minSize = minSize.expandedTo( QSize(minSizeWidth, 0) ); + minSizeHeight = minSizeHeight + spacing() + lineMinHeight; + // start a new line + y = y + spacing() + h; + h = 0; + x = r.x(); + currentLine.clear(); + expandingWidgets = 0; + availableSpace = r.width() + spacing(); + } + + x = x + spacing() + oSizeHint.width(); + h = QMAX( h, oSizeHint.height() ); + currentLine.append(o); + if(o->expanding() == QSizePolicy::Horizontally || o->expanding() == QSizePolicy::BothDirections) + ++expandingWidgets; + availableSpace = QMAX(0, availableSpace - spacing() - oSizeHint.width()); + ++it; + } + + // don't forget to layout the last line + QPtrListIterator<QLayoutItem> it2(currentLine); + QLayoutItem *item; + int wx = r.x(); + int sizeHintWidth = 0 -spacing(), minSizeWidth=0 - spacing(), lineMinHeight=0; + while( (item = it2.current()) != 0 ) { + QSize itemSizeHint = item->sizeHint(); // we cache these ones because it can take + QSize itemMinSize = item->minimumSize(); // a while to get them + QSize s; + if(m_justify) { + if(expandingWidgets != 0) { + if(item->expanding() == QSizePolicy::Horizontally || item->expanding() == QSizePolicy::BothDirections) + s = QSize( QMIN(itemSizeHint.width() + availableSpace / expandingWidgets + , r.width()), itemSizeHint.height() ); + else + s = QSize( QMIN(itemSizeHint.width(), r.width()), itemSizeHint.height() ); + } + else + s = QSize( QMIN(itemSizeHint.width() + availableSpace / (int)currentLine.count() + , r.width()), itemSizeHint.height() ); + } + else + s = QSize ( QMIN(itemSizeHint.width(), r.width()), itemSizeHint.height() ); + if(!testOnly) + item->setGeometry( QRect(QPoint(wx, y), s) ); + wx = wx + s.width() + spacing(); + minSizeWidth = minSizeWidth + spacing() + itemMinSize.width(); + sizeHintWidth = sizeHintWidth + spacing() + itemSizeHint.width(); + lineMinHeight = QMAX( lineMinHeight, itemMinSize.height() ); + ++it2; + } + sizeHint = sizeHint.expandedTo( QSize(sizeHintWidth, y + spacing() + h) ); + minSizeHeight = minSizeHeight + spacing() + lineMinHeight; + minSize = minSize.expandedTo( QSize(minSizeWidth, minSizeHeight) ); + + // store sizeHint() and minimumSize() + m_cached_sizeHint = sizeHint + QSize(2* margin(), 2*margin()); + m_cached_minSize = minSize + QSize(2* margin() , 2*margin()); + // return our height + return y + h - r.y(); +} + +int +KexiFlowLayout::doVerticalLayout(const QRect &r, bool testOnly) +{ + int x = r.x(); + int y = r.y(); + int w = 0; // width of this line + int availableSpace = r.height() + spacing(); + int expandingWidgets=0; // number of widgets in the line with QSizePolicy == Expanding + QPtrListIterator<QLayoutItem> it(m_list); + QPtrList<QLayoutItem> currentLine; + QLayoutItem *o; + QSize minSize, sizeHint(20, 20); + int minSizeWidth = 0 - spacing(); + + while ( (o = it.current()) != 0 ) { + if(o->isEmpty()) { /// do not consider hidden widgets + ++it; + continue; + } + + QSize oSizeHint = o->sizeHint(); // we cache these ones because it can take a while to get it (eg for child layouts) + if (y + oSizeHint.height() > r.bottom() && w > 0) { + // do the layout of current line + QPtrListIterator<QLayoutItem> it2(currentLine); + QLayoutItem *item; + int wy = r.y(); + int sizeHintHeight = 0 - spacing(), minSizeHeight = 0 - spacing(), colMinWidth=0; + while( (item = it2.current()) != 0 ) { + QSize itemSizeHint = item->sizeHint(); // we cache these ones because it can take + QSize itemMinSize = item->minimumSize(); // a while to get them + QSize s; + if(m_justify) { + if(expandingWidgets != 0) { + if(item->expanding() == QSizePolicy::Vertically || item->expanding() == QSizePolicy::BothDirections) + s = QSize( itemSizeHint.width(), QMIN(itemSizeHint.height() + availableSpace / expandingWidgets + , r.height()) ); + else + s = QSize( itemSizeHint.width(), QMIN(itemSizeHint.height(), r.height()) ); + } + else + s = QSize( itemSizeHint.width(), QMIN(itemSizeHint.height() + availableSpace / (int)currentLine.count() + , r.height()) ); + } + else + s = QSize ( itemSizeHint.width(), QMIN(itemSizeHint.height(), r.height()) ); + if(!testOnly) + item->setGeometry( QRect(QPoint(x, wy), s) ); + wy = wy + s.height() + spacing(); + minSizeHeight = minSizeHeight + spacing() + itemMinSize.height(); + sizeHintHeight = sizeHintHeight + spacing() + itemSizeHint.height(); + colMinWidth = QMAX( colMinWidth, itemMinSize.width() ); + ++it2; + } + sizeHint = sizeHint.expandedTo( QSize(0, sizeHintHeight) ); + minSize = minSize.expandedTo( QSize(0, minSizeHeight) ); + minSizeWidth = minSizeWidth + spacing() + colMinWidth; + // start a new column + x = x + spacing() + w; + w = 0; + y = r.y(); + currentLine.clear(); + expandingWidgets = 0; + availableSpace = r.height() + spacing(); + } + + y = y + spacing() + oSizeHint.height(); + w = QMAX( w, oSizeHint.width() ); + currentLine.append(o); + if(o->expanding() == QSizePolicy::Vertically || o->expanding() == QSizePolicy::BothDirections) + ++expandingWidgets; + availableSpace = QMAX(0, availableSpace - spacing() - oSizeHint.height()); + ++it; + } + + // don't forget to layout the last line + QPtrListIterator<QLayoutItem> it2(currentLine); + QLayoutItem *item; + int wy = r.y(); + int sizeHintHeight = 0 - spacing(), minSizeHeight = 0 - spacing(), colMinWidth=0; + while( (item = it2.current()) != 0 ) { + QSize itemSizeHint = item->sizeHint(); // we cache these ones because it can take + QSize itemMinSize = item->minimumSize(); // a while to get them + QSize s; + if(m_justify) { + if(expandingWidgets != 0) { + if(item->expanding() == QSizePolicy::Vertically || item->expanding() == QSizePolicy::BothDirections) + s = QSize( itemSizeHint.width(), QMIN(itemSizeHint.height() + availableSpace / expandingWidgets + , r.height()) ); + else + s = QSize( itemSizeHint.width(), QMIN(itemSizeHint.height(), r.height()) ); + } + else + s = QSize( itemSizeHint.width(), QMIN(itemSizeHint.height() + availableSpace / (int)currentLine.count() + , r.height()) ); + } + else + s = QSize ( itemSizeHint.width(), QMIN(itemSizeHint.height(), r.height()) ); + if(!testOnly) + item->setGeometry( QRect(QPoint(x, wy), s) ); + wy = wy + s.height() + spacing(); + minSizeHeight = minSizeHeight + spacing() + itemMinSize.height(); + sizeHintHeight = sizeHintHeight + spacing() + itemSizeHint.height(); + colMinWidth = QMAX( colMinWidth, itemMinSize.width() ); + ++it2; + } + sizeHint = sizeHint.expandedTo( QSize( x + spacing() + w, sizeHintHeight) ); + minSizeWidth = minSizeWidth + spacing() + colMinWidth; + minSize = minSize.expandedTo( QSize(minSizeWidth, minSizeHeight) ); + + // store sizeHint() and minimumSize() + m_cached_sizeHint = sizeHint + QSize(2* margin(), 2*margin()); + m_cached_minSize = minSize + QSize(2* margin(), 2*margin()); + // return our width + return x + w - r.x(); +} + diff --git a/kexi/widget/utils/kexiflowlayout.h b/kexi/widget/utils/kexiflowlayout.h new file mode 100644 index 00000000..173ddad5 --- /dev/null +++ b/kexi/widget/utils/kexiflowlayout.h @@ -0,0 +1,79 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Cedric Pasteur <cedric.pasteur@free.fr> + + 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 Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIFLOWLAYOUT_H +#define KEXIFLOWLAYOUT_H + +#include <qlayout.h> +#include <qptrlist.h> + +//! @short a special "flow" layout +class KEXIGUIUTILS_EXPORT KexiFlowLayout : public QLayout +{ + public: + KexiFlowLayout(QWidget *parent, int border=0, int space=-1, const char *name=0); + KexiFlowLayout(QLayout* parent, int space=-1, const char *name=0); + KexiFlowLayout(int space=-1, const char *name=0); + + ~KexiFlowLayout(); + + /*! \return the widgets in the order of the layout, + ie as it is stored in m_list. You must delete the list after using it. */ + QPtrList<QWidget>* widgetList() const; + + /*! Sets layout's orientation to \a orientation. Default orientation is Vertical. */ + void setOrientation(Orientation orientation) { m_orientation = orientation; } + + /*! \return layout's orientation. */ + Qt::Orientation orientation() const { return m_orientation; } + + void setJustified(bool justify) { m_justify = justify; } + bool isJustified() const { return m_justify; } + + virtual void addItem(QLayoutItem *item); + virtual void addSpacing(int size); + virtual QLayoutIterator iterator(); + virtual void invalidate(); + + virtual bool hasHeightForWidth() const; + virtual int heightForWidth(int width) const; + virtual QSize sizeHint() const; + virtual QSize minimumSize() const; + virtual QSizePolicy::ExpandData expanding() const; + + virtual bool isEmpty(); + + protected: + virtual void setGeometry(const QRect&); + int simulateLayout(const QRect &r); + int doHorizontalLayout(const QRect&, bool testonly = false); + int doVerticalLayout(const QRect&, bool testonly = false); + + private: + QPtrList<QLayoutItem> m_list; + int m_cached_width; + int m_cached_hfw; + bool m_justify; + Orientation m_orientation; + QSize m_cached_sizeHint; + QSize m_cached_minSize; +}; + +#endif + diff --git a/kexi/widget/utils/kexigradientwidget.cpp b/kexi/widget/utils/kexigradientwidget.cpp new file mode 100644 index 00000000..0411318d --- /dev/null +++ b/kexi/widget/utils/kexigradientwidget.cpp @@ -0,0 +1,358 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Christian Nitschkowski <segfault_ii@web.de> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include <qapplication.h> +#include <qbitmap.h> +#include <qimage.h> +#include <qobjectlist.h> +#include <qpainter.h> +#include <qstyle.h> + +#include <kimageeffect.h> +#include <kpixmap.h> + +#include "kexigradientwidget.h" + +KexiGradientWidget::KexiGradientWidget( QWidget *parent, const char *name, WFlags f ) + : QWidget( parent, name, f ), p_displayMode( NoGradient ), + p_gradientType( VerticalGradient ), + p_color1( Qt::white ), p_color2( Qt::blue ), p_currentChild( 0 ), + p_opacity( 0.5 ), p_cacheDirty( true ) +{ + p_customBackgroundWidgets.setAutoDelete( false ); + p_knownWidgets.setAutoDelete( false ); + + p_backgroundColor = QWidget::paletteBackgroundColor(); + + connect ( &p_rebuildDelayTimer, SIGNAL( timeout() ), this, SLOT( setCacheDirty() ) ); + + installEventFilter( this ); +} + +KexiGradientWidget::~KexiGradientWidget() +{ +} + +bool KexiGradientWidget::isValidChildWidget( QObject* child ) { + const QWidget* wgt = dynamic_cast<QWidget*>( child ); + + if ( wgt == 0L ) + return false; + + if ( wgt->inherits( "QScrollView" ) ) + return false; + if ( wgt->inherits( "QComboBox" ) ) + return false; + if ( wgt->inherits( "QLineEdit" ) ) + return false; + if ( wgt->inherits( "KexiDBForm" ) ) + return false; + + return true; +} + +void KexiGradientWidget::buildChildrenList( WidgetList& list, QWidget* p ) { + QObjectList* objects = p->queryList( "QWidget", 0, false, false ); + + for ( QObjectList::Iterator it = objects->begin(); it != objects->end(); ++it ) { + if ( isValidChildWidget( ( *it ) ) == false ) + continue; + list.append( dynamic_cast<QWidget*>( ( *it ) ) ); + buildChildrenList( list, dynamic_cast<QWidget*>( ( *it ) ) ); + } + + delete objects; +} + +void KexiGradientWidget::rebuildCache( void ) { + WidgetList childWidgetList; + buildChildrenList( childWidgetList, this ); + + /** + Disable the effect and behave like a normal QWidget. + */ + if ( p_displayMode == NoGradient ) { +// if ( p_backgroundPixmap.isNull() ) { + //unsetPalette(); + //} else { + QWidget::setPaletteBackgroundPixmap( p_backgroundPixmap ); + //} + QWidget::setPaletteBackgroundColor( p_backgroundColor ); + + for ( WidgetList::Iterator it = childWidgetList.begin(); + it != childWidgetList.end(); ++it ) { + + if ( p_customBackgroundWidgets.contains( ( *it ) ) == false ) { + ( *it )->unsetPalette(); + } + } + /** + The cache is now in a current state. + */ + p_cacheDirty = false; + return; + } + + KPixmap tempPixmap; + QImage gradientImage; + QImage bgImage; + + /** + Draw the gradient + */ + gradientImage = KImageEffect::gradient( size(), p_color1, p_color2, + (KImageEffect::GradientType)p_gradientType ); + + /** + Draw the widget-background in a pixmap and fade it with the gradient. + */ + if ( p_displayMode == FadedGradient ) { + tempPixmap.resize( size() ); + QPainter p( &tempPixmap, this ); + + if ( p_backgroundPixmap.isNull() ) { + /* + Need to unset the palette, otherwise the old gradient + will be used as a background, not the widget's default bg. + */ + unsetPalette(); + p.fillRect( 0, 0, width(), height(), palette().brush( + isEnabled() ? QPalette::Active : QPalette::Disabled, + QColorGroup::Background ) ); + } else { + p.drawTiledPixmap( 0, 0, width(), height(), p_backgroundPixmap ); + } + + p.end(); + + bgImage = tempPixmap; + + KImageEffect::blend( gradientImage, bgImage, (float)p_opacity ); + + tempPixmap.convertFromImage( bgImage ); + } else if ( p_displayMode == SimpleGradient ) { + /** + Use the gradient as the final background-pixmap + if displaymode is set to SimpleGradient. + */ + tempPixmap.convertFromImage( gradientImage ); + } + + /** + All children need to have our background set. + */ + KPixmap partPixmap; + QRect area; + QWidget* childWidget = 0; + const QPoint topLeft( 0, 0 ); + + for ( WidgetList::Iterator it = childWidgetList.begin(); + it != childWidgetList.end(); ++it ) { + + childWidget = ( *it ); + + /** + Exclude widgets with a custom palette. + */ + if ( p_customBackgroundWidgets.contains( childWidget ) ) { + continue; + } + + partPixmap.resize( childWidget->size() ); + /** + Get the part of the tempPixmap that is + under the current child-widget. + */ + if ( childWidget->parent() == this ) { + area = childWidget->geometry(); + } else { + area.setTopLeft( childWidget->mapTo( this, + childWidget->clipRegion().boundingRect().topLeft() ) ); + area.setSize( childWidget->size() ); + } + bitBlt( &partPixmap, topLeft, &tempPixmap, area ); + + p_currentChild = childWidget; + childWidget->setPaletteBackgroundPixmap( partPixmap ); + } + + QWidget::setPaletteBackgroundPixmap( tempPixmap ); + /** + Unset the dirty-flag at the end of the method. + QWidget::setPaletteBackgroundPixmap() causes this + to get set to true again, so set it to false + right after setting the pixmap. + */ + p_cacheDirty = false; +} + +void KexiGradientWidget::paintEvent( QPaintEvent* e ) { + /** + Rebuild the background-pixmap if necessary. + */ + if ( p_cacheDirty == true ) { + rebuildCache(); + } + + /** + Draw the widget as usual + */ + QWidget::paintEvent( e ); +} + +bool KexiGradientWidget::eventFilter( QObject* object, QEvent* event ) { + QWidget* child = dynamic_cast<QWidget*>( object ); + + /** + Manage list of child-widgets. + */ + if ( object == this ) { + if ( event->type() == QEvent::ChildInserted ) { + child = dynamic_cast<QWidget*>( dynamic_cast<QChildEvent*>( event )->child() ); + if ( isValidChildWidget( child ) == false ) { + return false; + } + /** + Add the new child-widget to our list of known widgets. + */ + p_knownWidgets.append( child ); + /** + ... and install 'this' as the child's event-filter. + */ + child->installEventFilter( this ); + } else if ( event->type() == QEvent::ChildRemoved ) { + /** + Remove the child-widget from the list of known widgets. + */ + p_knownWidgets.remove( dynamic_cast<QWidget*>( dynamic_cast<QChildEvent*>( event )->child() ) ); + } + return false; + } + + /** + Manage custombackground-list. + */ + if ( event->type() == QEvent::PaletteChange ) { + /** + p_currentChild will be == 0L, when the user + sets it's palette manually. + In this case, it has to be added to the customBackground-list. + */ + if ( p_currentChild == 0L && child != 0L ) { + if ( p_customBackgroundWidgets.contains( child ) == false ) { + p_customBackgroundWidgets.append( child ); + return false; + } + } + /** + Check if the widget whose PaletteChange-event we handle + isn't the widget we set the background in rebuildCache(). + */ + if ( child != p_currentChild && child != 0L ) { + /** + Add the new child to the list of widgets, we don't set + the background ourselves if it isn't in the list. + */ + if ( p_customBackgroundWidgets.contains( child ) == false ) { + if ( child->paletteBackgroundPixmap() != 0L ) { + p_customBackgroundWidgets.append( child ); + } + } else { + /** + If the palette is now the default-palette again, + remove it from the "don't set background in rebuildCache()"-list + and rebuild the cache, so it again will get the gradient background. + */ + if ( child->paletteBackgroundPixmap() == 0L ) { + p_customBackgroundWidgets.remove( child ); + if ( p_displayMode != NoGradient ) { + p_cacheDirty = true; + } + } + } + } + p_currentChild = 0; + } + + if ( event->type() == QEvent::Move ) { + if ( p_customBackgroundWidgets.contains( child ) == false ) { + updateChildBackground( child ); + } + } + return false; +} + +void KexiGradientWidget::updateChildBackground( QWidget* childWidget ) +{ + KPixmap partPixmap; + KPixmap bgPixmap; + QRect area; + const QPoint topLeft( 0, 0 ); + + bgPixmap = paletteBackgroundPixmap() ? (*paletteBackgroundPixmap()) : QPixmap(); + if ( bgPixmap.isNull() ) + return; + + /** + Exclude widgtes that don't have a parent. + This happens when children are removed + which are in the knownWidgets-list. + */ + if ( childWidget->parent() == 0L ) + return; + + /** + Exclude widgets with a custom palette. + */ + if ( p_customBackgroundWidgets.contains( childWidget ) ) { + return; + } + + partPixmap.resize( childWidget->size() ); + /** + Get the part of the tempPixmap that is + under the current child-widget. + */ + if ( childWidget->parent() == this ) { + area = childWidget->geometry(); + } else { + area.setTopLeft( childWidget->mapTo( this, + childWidget->clipRegion().boundingRect().topLeft() ) ); + area.setSize( childWidget->size() ); + } + bitBlt( &partPixmap, topLeft, &bgPixmap, area ); + + p_currentChild = childWidget; + childWidget->setPaletteBackgroundPixmap( partPixmap ); +} + +void KexiGradientWidget::setPaletteBackgroundColor( const QColor& color ) +{ + p_backgroundColor = color; + if ( p_displayMode == NoGradient ) { + QWidget::setPaletteBackgroundColor( p_backgroundColor ); + } +} + +const QColor& KexiGradientWidget::paletteBackgroundColor() const +{ + return p_backgroundColor; +} + +#include "kexigradientwidget.moc" diff --git a/kexi/widget/utils/kexigradientwidget.h b/kexi/widget/utils/kexigradientwidget.h new file mode 100644 index 00000000..0032e7b1 --- /dev/null +++ b/kexi/widget/utils/kexigradientwidget.h @@ -0,0 +1,247 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Christian Nitschkowski <segfault_ii@web.de> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIGRADIENTWIDGET_H +#define KEXIGRADIENTWIDGET_H + +#include <qtimer.h> +#include <qwidget.h> + +#include <kimageeffect.h> +#include <kpixmap.h> + +#define REBUILD_DELAY 100 + +//! @short A simple widget that can use different types of gradients as the background. +/*! + @author Christian Nitschkowski +*/ +class KEXIGUIUTILS_EXPORT KexiGradientWidget : public QWidget { + typedef QPtrList<QWidget> WidgetList; + + Q_OBJECT + Q_PROPERTY(DisplayMode displayMode READ displayMode WRITE setDisplayMode DESIGNABLE true) + Q_PROPERTY(GradientType gradientType READ gradientType WRITE setGradientType DESIGNABLE true) + Q_PROPERTY(QColor gradientColor1 READ gradientColor1 WRITE setGradientColor1 DESIGNABLE true) + Q_PROPERTY(QColor gradientColor2 READ gradientColor2 WRITE setGradientColor2 DESIGNABLE true) + Q_PROPERTY(double blendOpacity READ blendOpacity WRITE setBlendOpacity DESIGNABLE true) + Q_ENUMS( DisplayMode GradientType ) + + public: + /*! + Modes for displaying the gradient. + */ + enum DisplayMode { + NoGradient, //!< No gradient at all. Will behave just like a QWidget + FadedGradient, //!< Gradient will be faded with the widgets background + SimpleGradient //!< Gradient will replace the usual widget background + }; + + /*! + Gradient type specification. + See GradientType for more details (part of the KDEFX library) + */ + enum GradientType { + VerticalGradient = KImageEffect::VerticalGradient, + HorizontalGradient = KImageEffect::HorizontalGradient, + DiagonalGradient = KImageEffect::DiagonalGradient, + CrossDiagonalGradient = KImageEffect::CrossDiagonalGradient, + PyramidGradient = KImageEffect::PyramidGradient, + RectangleGradient = KImageEffect::RectangleGradient, + PipeCrossGradient = KImageEffect::PipeCrossGradient, + EllipticGradient = KImageEffect::EllipticGradient + }; + + KexiGradientWidget( QWidget *parent = 0, const char *name = 0, WFlags f = 0 ); + + virtual ~KexiGradientWidget(); + + virtual void setPaletteBackgroundPixmap( const QPixmap& pixmap ) { + p_backgroundPixmap = pixmap; + p_rebuildDelayTimer.start( REBUILD_DELAY, true ); + } + + virtual const QColor& paletteBackgroundColor() const; + + /*! + Set the displaymode \a mode. + The widget will be updated automatically. + */ + void setDisplayMode( DisplayMode mode ) { + p_displayMode = mode; + p_cacheDirty = true; + update(); + } + + /*! + Get the current displaymode. + */ + DisplayMode displayMode() const { + return p_displayMode; + } + + /*! + Set the gradient-type. + */ + void setGradientType( GradientType type ) { + p_gradientType = type; + p_cacheDirty = true; + update(); + } + + /*! + Get the current gradient-type. + */ + GradientType gradientType() const { + return p_gradientType; + } + + /*! Set color #1 for the gradient-effect. + \a color is the new color. */ + void setGradientColor1( const QColor& color ) { + p_color1 = color; + p_cacheDirty = true; + } + + /*! Set color #2 for the gradient-effect. + \a color is the new color. */ + void setGradientColor2( const QColor& color ) { + p_color2 = color; + p_cacheDirty = true; + } + + /*! + Set both colors for the gradient. + \a color1 is the first color, + \a color2 the second. + */ + void setGradientColors( const QColor& color1, const QColor& color2 ) { + p_color1 = color1; + p_color2 = color2; + p_cacheDirty = true; + } + + /*! \return the color #1 used for the gradient. */ + QColor gradientColor1() const { return p_color1; } + + /*! \return the color #2 used for the gradient. */ + QColor gradientColor2() const { return p_color2; } + + /*! + Sets the opacity of the gradient when fading with background. + \a opacity has to be between 0.0 and 1.0. + */ + void setBlendOpacity( double opacity ) { + p_opacity = opacity; + p_cacheDirty = true; + } + + double blendOpacity() const { return p_opacity; } + + public slots: + virtual void setPaletteBackgroundColor( const QColor& color ); + + protected: + virtual bool eventFilter( QObject* object, QEvent* event ); + virtual void enabledChange( bool enabled ) { + p_cacheDirty = true; + QWidget::enabledChange( enabled ); + } + + virtual void paletteChange( const QPalette& pal ) { + p_cacheDirty = true; + QWidget::paletteChange( pal ); + } + + virtual void paintEvent( QPaintEvent* e ); + + virtual void resizeEvent( QResizeEvent* e ) { + p_rebuildDelayTimer.start( REBUILD_DELAY, true ); + QWidget::resizeEvent( e ); + } + + virtual void styleChange( QStyle& style ) { + p_cacheDirty = true; + QWidget::styleChange( style ); + } + + private: + /*! + Builds a list of children of \a p. + Only widgets that work correctly with KexiGradientWidget + will be in this list. + The results will be stored in \a list. + The method recursively calls itself until all children of \a p + have been found and stored in the list. + */ + static void buildChildrenList( WidgetList& list, QWidget* p ); + /*! + \a return if the \a child is a widget that should + get a background set. + */ + static bool isValidChildWidget( QObject* child ); + + /*! + Rebuilds the cache completely. + This is done automatically if necessary. + */ + void rebuildCache(); + + /*! + Sets the background of \a childWidget. + This is necessary when the child has been moved. + For performance-reasons this is used only for Move-events. + The same code is used for PaletteChange-events, but in a + different location. + */ + void updateChildBackground( QWidget* childWidget ); + + private: + WidgetList p_knownWidgets; + WidgetList p_customBackgroundWidgets; + DisplayMode p_displayMode; + GradientType p_gradientType; + KPixmap p_backgroundPixmap; + QColor p_color1; + QColor p_color2; + QTimer p_rebuildDelayTimer; + QWidget* p_currentChild; + double p_opacity; + bool p_cacheDirty; + + QColor p_backgroundColor; + + public slots: + /*! + The cache needs to be rebuild once the widget + is set up completely. + */ + virtual void polish() { + QWidget::polish(); + rebuildCache(); + } + + private slots: + void setCacheDirty() { + rebuildCache(); + } + + }; + +#endif diff --git a/kexi/widget/utils/kexirecordmarker.cpp b/kexi/widget/utils/kexirecordmarker.cpp new file mode 100644 index 00000000..d434fcaf --- /dev/null +++ b/kexi/widget/utils/kexirecordmarker.cpp @@ -0,0 +1,307 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Lucijan Busch <lucijan@gmx.at> + Copyright (C) 2002 Till Busch <till@bux.at> + Copyright (C) 2002 Daniel Molkentin <molkentin@kde.org> + Copyright (C) 2003-2006 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "kexirecordmarker.h" + +#include <qcolor.h> +#include <qstyle.h> +#include <qpixmap.h> +#include <qpainter.h> +#include <qimage.h> +#include <qapplication.h> + +#include <kdebug.h> +#include <kstaticdeleter.h> + +#include <kexiutils/utils.h> + +static KStaticDeleter<QImage> KexiRecordMarker_pen_deleter, KexiRecordMarker_plus_deleter; +QImage* KexiRecordMarker_pen = 0, *KexiRecordMarker_plus = 0; + +static const unsigned char img_pen_data[] = { + 0x00,0x00,0x03,0x30,0x78,0x9c,0xfb,0xff,0xff,0x3f,0xc3,0x7f,0x32,0x30, + 0x10,0x80,0x88,0xff,0xe4,0xe8,0x85,0xe9,0xc7,0xc6,0x26,0x55,0x3f,0x3a, + 0x4d,0x8e,0x7e,0x72,0xfc,0x32,0xd2,0xf5,0xa3,0xeb,0xa5,0xb5,0x7e,0x5c, + 0xe9,0x85,0x54,0xfb,0xb1,0xa5,0x1b,0x52,0xdc,0x0e,0x00,0xf2,0xea,0x0a, + 0x13 +}; +static const unsigned char img_plus_data[] = { + 0x00,0x00,0x01,0x90,0x78,0x9c,0xfb,0xff,0xff,0x3f,0xc3,0x7f,0x28,0x86, + 0x82,0xff,0x50,0x0c,0x17,0x47,0xc7,0xd4,0x50,0x87,0x05,0xc0,0xd5,0xe1, + 0x10,0xa7,0x16,0x26,0xca,0x5e,0x7c,0xfe,0x20,0x47,0x1d,0xb2,0x5a,0x5c, + 0xea,0x40,0x72,0x00,0x03,0x6e,0x74,0x8c +}; + +static struct EmbedImage { + int width, height, depth; + const unsigned char *data; + ulong compressed; + int numColors; + const QRgb *colorTable; + bool alpha; + const char *name; +} embed_image[] = { + { 17, 12, 32, (const unsigned char*)img_pen_data, 57, 0, 0, true, "tableview_pen.png" }, + { 10, 10, 32, (const unsigned char*)img_pen_data, 50, 0, 0, true, "tableview_plus.png" } +}; + +QImage* getImg(const unsigned char* data, int id) +{ + QByteArray baunzip; + baunzip = qUncompress( data, embed_image[id].compressed ); + QImage *img = new QImage( QImage((uchar*)baunzip.data(), + embed_image[id].width, embed_image[id].height, + embed_image[id].depth, (QRgb*)embed_image[id].colorTable, + embed_image[id].numColors, QImage::BigEndian + ).copy() ); + if ( embed_image[id].alpha ) + img->setAlphaBuffer(true); + return img; +} + +static void initRecordMarkerImages() +{ + if (!KexiRecordMarker_pen) { +/*! @warning not reentrant! */ + KexiRecordMarker_pen_deleter.setObject( KexiRecordMarker_pen, getImg(img_pen_data, 0) ); + KexiRecordMarker_plus_deleter.setObject( KexiRecordMarker_plus, getImg(img_plus_data, 1) ); + } +} + +//---------------------------------------------------------------- + +//! @internal +class KexiRecordMarker::Private +{ +public: + Private() + : rowHeight(1) + , offset(0) + , currentRow(-1) + , highlightedRow(-1) + , editRow(-1) + , rows(0) + , selectionBackgroundColor(qApp->palette().active().highlight()) + , showInsertRow(true) + { + } + int rowHeight; + int offset; + int currentRow; + int highlightedRow; + int editRow; + int rows; + QColor selectionBackgroundColor; + bool showInsertRow : 1; +}; + +//---------------------------------------------------------------- + +KexiRecordMarker::KexiRecordMarker(QWidget *parent, const char* name) + : QWidget(parent, name) + , d( new Private() ) +{ + initRecordMarkerImages(); +} + +KexiRecordMarker::~KexiRecordMarker() +{ + delete d; +} + +QImage* KexiRecordMarker::penImage() +{ + initRecordMarkerImages(); + return KexiRecordMarker_pen; +} + +QImage* KexiRecordMarker::plusImage() +{ + initRecordMarkerImages(); + return KexiRecordMarker_plus; +} + +void KexiRecordMarker::addLabel(bool upd) +{ + d->rows++; + if (upd) + update(); +} + +void KexiRecordMarker::removeLabel(bool upd) +{ + if (d->rows > 0) { + d->rows--; + if (upd) + update(); + } +} + +void KexiRecordMarker::addLabels(int num, bool upd) +{ + d->rows += num; + if (upd) + update(); +} + +void KexiRecordMarker::clear(bool upd) +{ + d->rows=0; + if (upd) + update(); +} + +int KexiRecordMarker::rows() const +{ + if (d->showInsertRow) + return d->rows +1; + else + return d->rows; +} + +void KexiRecordMarker::paintEvent(QPaintEvent *e) +{ + QPainter p(this); + QRect r(e->rect()); + + int first = (r.top() + d->offset) / d->rowHeight; + int last = (r.bottom() + d->offset) / d->rowHeight; + if(last > (d->rows-1+(d->showInsertRow?1:0))) + last = d->rows-1+(d->showInsertRow?1:0); + + QColorGroup selectedColorGroup(colorGroup()); + selectedColorGroup.setColor( QColorGroup::Button, + KexiUtils::blendedColors( selectedColorGroup.color(QColorGroup::Background), + d->selectionBackgroundColor, 2, 1) ); + selectedColorGroup.setColor( QColorGroup::Background, + selectedColorGroup.color(QColorGroup::Button) ); //set background color as well (e.g. for thinkeramik) + QColorGroup highlightedColorGroup(colorGroup()); + highlightedColorGroup.setColor( QColorGroup::Button, + KexiUtils::blendedColors( highlightedColorGroup.color(QColorGroup::Background), + d->selectionBackgroundColor, 4, 1) ); + highlightedColorGroup.setColor( QColorGroup::Background, + highlightedColorGroup.color(QColorGroup::Button) ); //set background color as well (e.g. for thinkeramik) + for(int i=first; i <= last; i++) + { + int y = ((d->rowHeight * i)-d->offset); + QRect r(0, y, width(), d->rowHeight); + p.drawRect(r); + style().drawPrimitive( QStyle::PE_HeaderSection, &p, r, + (d->currentRow == i) ? selectedColorGroup : (d->highlightedRow == i ? highlightedColorGroup : colorGroup()), + QStyle::Style_Raised | (isEnabled() ? QStyle::Style_Enabled : 0)); + } + if (d->editRow!=-1 && d->editRow >= first && d->editRow <= (last/*+1 for insert row*/)) { + //show pen when editing + int ofs = d->rowHeight / 4; + int pos = ((d->rowHeight*(d->currentRow>=0?d->currentRow:0))-d->offset)-ofs/2+1; + p.drawImage((d->rowHeight-KexiRecordMarker_pen->width())/2, + (d->rowHeight-KexiRecordMarker_pen->height())/2+pos,*KexiRecordMarker_pen); + } + else if (d->currentRow >= first && d->currentRow <= last + && (!d->showInsertRow || (d->showInsertRow && d->currentRow < last)))/*don't display marker for 'insert' row*/ + { + //show marker + p.setBrush(colorGroup().foreground()); + p.setPen(QPen(Qt::NoPen)); + QPointArray points(3); + int ofs = d->rowHeight / 4; + int ofs2 = (width() - ofs) / 2 -1; + int pos = ((d->rowHeight*d->currentRow)-d->offset)-ofs/2+2; + points.putPoints(0, 3, ofs2, pos+ofs, ofs2 + ofs, pos+ofs*2, + ofs2,pos+ofs*3); + p.drawPolygon(points); +// kdDebug() <<"KexiRecordMarker::paintEvent(): POLYGON" << endl; +/* int half = d->rowHeight / 2; + points.setPoints(3, 2, pos + 2, width() - 5, pos + half, 2, pos + (2 * half) - 2);*/ + } + if (d->showInsertRow && d->editRow < last + && last == (d->rows-1+(d->showInsertRow?1:0)) ) { + //show plus sign + int pos = ((d->rowHeight*last)-d->offset)+(d->rowHeight-KexiRecordMarker_plus->height())/2; +// p.drawImage((width()-d->plusImg.width())/2-1, pos, d->plusImg); + p.drawImage((width()-KexiRecordMarker_plus->width())/2, pos, *KexiRecordMarker_plus); + } +} + +void KexiRecordMarker::setCurrentRow(int row) +{ + if (row == d->currentRow) + return; + int oldRow = d->currentRow; + d->currentRow=row; + + if (oldRow != -1) + update(0,(d->rowHeight*(oldRow))-d->offset-1, width()+2, d->rowHeight+2); + if (d->currentRow != -1) + update(0,(d->rowHeight*d->currentRow)-d->offset-1, width()+2, d->rowHeight+2); +} + +void KexiRecordMarker::setHighlightedRow(int row) +{ + if (row == d->highlightedRow) + return; + int oldRow = d->highlightedRow; + d->highlightedRow = row; + + if (oldRow != -1) + update(0,(d->rowHeight*(oldRow))-d->offset-1, width()+2, d->rowHeight+2); + if (d->currentRow != -1) + update(0,(d->rowHeight*d->highlightedRow)-d->offset-1, width()+2, d->rowHeight+2); +} + +void KexiRecordMarker::setOffset(int offset) +{ + int oldOff = d->offset; + d->offset = offset; + scroll(0,oldOff-offset); +} + +void KexiRecordMarker::setCellHeight(int cellHeight) +{ + d->rowHeight = cellHeight; +} + +void KexiRecordMarker::setEditRow(int row) +{ + d->editRow = row; +//TODO: update only needed area! + update(); +} + +void KexiRecordMarker::showInsertRow(bool show) +{ + d->showInsertRow = show; +//TODO: update only needed area! + update(); +} + +void KexiRecordMarker::setSelectionBackgroundColor(const QColor &color) +{ + d->selectionBackgroundColor = color; +} + +QColor KexiRecordMarker::selectionBackgroundColor() const +{ + return d->selectionBackgroundColor; +} + +#include "kexirecordmarker.moc" diff --git a/kexi/widget/utils/kexirecordmarker.h b/kexi/widget/utils/kexirecordmarker.h new file mode 100644 index 00000000..1408f83b --- /dev/null +++ b/kexi/widget/utils/kexirecordmarker.h @@ -0,0 +1,72 @@ +/* This file is part of the KDE project + Copyright (C) 2002 Lucijan Busch <lucijan@gmx.at> + Copyright (C) 2002 Till Busch <till@bux.at> + Copyright (C) 2002 Daniel Molkentin <molkentin@kde.org> + Copyright (C) 2003-2006 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIRECORDMARKER_H +#define KEXIRECORDMARKER_H + +#include <qwidget.h> + +class QImage; + +//! \brief Record marker, usually displayed at the left side of a table view or a continuous form. +class KEXIGUIUTILS_EXPORT KexiRecordMarker : public QWidget +{ + Q_OBJECT + + public: + KexiRecordMarker(QWidget *parent, const char* name = 0); + ~KexiRecordMarker(); + + int rows() const; + + static QImage* penImage(); + static QImage* plusImage(); + + public slots: + void setOffset(int offset); + void setCellHeight(int cellHeight); + void setCurrentRow(int row); + void setHighlightedRow(int row); + + /*! Sets 'edit row' flag for \a row. Use row==-1 if you want to switch the flag off. */ + void setEditRow(int row); + void showInsertRow(bool show); + + QColor selectionBackgroundColor() const; + void setSelectionBackgroundColor(const QColor &color); + + void addLabel(bool upd=true); + void removeLabel(bool upd=true); + + /*! Adds \a num labels */ + void addLabels(int num, bool upd=true); + + void clear(bool upd=true); + + protected: + virtual void paintEvent(QPaintEvent *e); + + class Private; + Private * const d; +}; + +#endif diff --git a/kexi/widget/utils/kexirecordnavigator.cpp b/kexi/widget/utils/kexirecordnavigator.cpp new file mode 100644 index 00000000..f0dff087 --- /dev/null +++ b/kexi/widget/utils/kexirecordnavigator.cpp @@ -0,0 +1,511 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Lucijan Busch <lucijan@kde.org> + Copyright (C) 2003-2004 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include <qtoolbutton.h> +#include <qlayout.h> +#include <qlabel.h> +#include <qvalidator.h> +#include <qtooltip.h> +#include <qscrollview.h> + +#include <klocale.h> +#include <kiconloader.h> +#include <klineedit.h> +#include <kguiitem.h> +#include <kstaticdeleter.h> + +#include "kexirecordnavigator.h" +#include "kexirecordmarker.h" + +//! @internal +class KexiRecordNavigatorPrivate +{ + public: + KexiRecordNavigatorPrivate() + : handler(0) + , editingIndicatorLabel(0) + , editingIndicatorEnabled(false) + , editingIndicatorVisible(false) + { + } + KexiRecordNavigatorHandler *handler; + QHBoxLayout *lyr; + + QLabel *editingIndicatorLabel; + bool editingIndicatorEnabled : 1; + bool editingIndicatorVisible : 1; +}; + +//-------------------------------------------------- + +KexiRecordNavigatorHandler::KexiRecordNavigatorHandler() +{ +} + +KexiRecordNavigatorHandler::~KexiRecordNavigatorHandler() +{ +} + +//-------------------------------------------------- + +KexiRecordNavigator::KexiRecordNavigator(QWidget *parent, int leftMargin, const char *name) + : QFrame(parent, name) + , m_view(0) + , m_isInsertingEnabled(true) + , d( new KexiRecordNavigatorPrivate() ) +{ + if (parent->inherits("QScrollView")) + setParentView( dynamic_cast<QScrollView*>(parent) ); + setFrameStyle(QFrame::NoFrame); + d->lyr = new QHBoxLayout(this,0,0,"nav_lyr"); + + m_textLabel = new QLabel(this); + d->lyr->addWidget( m_textLabel ); + setLabelText(i18n("Row:")); + + int bw = 6+SmallIcon("navigator_first").width(); //QMIN( horizontalScrollBar()->height(), 20); + QFont f = font(); + f.setPixelSize((bw > 12) ? 12 : bw); + QFontMetrics fm(f); + m_nav1DigitWidth = fm.width("8"); + + d->lyr->addWidget( m_navBtnFirst = new QToolButton(this) ); + m_navBtnFirst->setFixedWidth(bw); + m_navBtnFirst->setFocusPolicy(NoFocus); + m_navBtnFirst->setIconSet( SmallIconSet("navigator_first") ); + QToolTip::add(m_navBtnFirst, i18n("First row")); + + d->lyr->addWidget( m_navBtnPrev = new QToolButton(this) ); + m_navBtnPrev->setFixedWidth(bw); + m_navBtnPrev->setFocusPolicy(NoFocus); + m_navBtnPrev->setIconSet( SmallIconSet("navigator_prev") ); + m_navBtnPrev->setAutoRepeat(true); + QToolTip::add(m_navBtnPrev, i18n("Previous row")); + + d->lyr->addSpacing( 6 ); + + d->lyr->addWidget( m_navRecordNumber = new KLineEdit(this) ); + m_navRecordNumber->setAlignment(AlignRight | AlignVCenter); + m_navRecordNumber->setFocusPolicy(ClickFocus); + m_navRecordNumber->installEventFilter(this); +// m_navRowNumber->setFixedWidth(fw); + m_navRecordNumberValidator = new QIntValidator(1, INT_MAX, this); + m_navRecordNumber->setValidator(m_navRecordNumberValidator); + m_navRecordNumber->installEventFilter(this); + QToolTip::add(m_navRecordNumber, i18n("Current row number")); + + KLineEdit *lbl_of = new KLineEdit(i18n("of"), this); + lbl_of->setSizePolicy(QSizePolicy::Fixed,QSizePolicy::Preferred); + lbl_of->setMaximumWidth(fm.width(lbl_of->text())+8); + lbl_of->setReadOnly(true); + lbl_of->setLineWidth(0); + lbl_of->setFocusPolicy(NoFocus); + lbl_of->setAlignment(AlignCenter); + d->lyr->addWidget( lbl_of ); + + d->lyr->addWidget( m_navRecordCount = new KLineEdit(this) ); + m_navRecordCount->setSizePolicy(QSizePolicy::Fixed,QSizePolicy::Preferred); + m_navRecordCount->setReadOnly(true); + m_navRecordCount->setLineWidth(0); + m_navRecordCount->setFocusPolicy(NoFocus); + m_navRecordCount->setAlignment(AlignLeft | AlignVCenter); + QToolTip::add(m_navRecordCount, i18n("Number of rows")); + + lbl_of->setFont(f); + m_navRecordNumber->setFont(f); + m_navRecordCount->setFont(f); + setFont(f); + + d->lyr->addWidget( m_navBtnNext = new QToolButton(this) ); + m_navBtnNext->setFixedWidth(bw); + m_navBtnNext->setFocusPolicy(NoFocus); + m_navBtnNext->setIconSet( SmallIconSet("navigator_next") ); + m_navBtnNext->setAutoRepeat(true); + QToolTip::add(m_navBtnNext, i18n("Next row")); + + d->lyr->addWidget( m_navBtnLast = new QToolButton(this) ); + m_navBtnLast->setFixedWidth(bw); + m_navBtnLast->setFocusPolicy(NoFocus); + m_navBtnLast->setIconSet( SmallIconSet("navigator_last") ); + QToolTip::add(m_navBtnLast, i18n("Last row")); + + d->lyr->addSpacing( 6 ); + d->lyr->addWidget( m_navBtnNew = new QToolButton(this) ); + m_navBtnNew->setFixedWidth(bw); + m_navBtnNew->setFocusPolicy(NoFocus); + m_navBtnNew->setIconSet( SmallIconSet("navigator_new") ); + QToolTip::add(m_navBtnNew, i18n("New row")); + m_navBtnNext->setEnabled(isInsertingEnabled()); + + d->lyr->addSpacing( 6 ); + d->lyr->addStretch(10); + + connect(m_navBtnPrev,SIGNAL(clicked()),this,SLOT(slotPrevButtonClicked())); + connect(m_navBtnNext,SIGNAL(clicked()),this,SLOT(slotNextButtonClicked())); + connect(m_navBtnLast,SIGNAL(clicked()),this,SLOT(slotLastButtonClicked())); + connect(m_navBtnFirst,SIGNAL(clicked()),this,SLOT(slotFirstButtonClicked())); + connect(m_navBtnNew,SIGNAL(clicked()),this,SLOT(slotNewButtonClicked())); + + setRecordCount(0); + setCurrentRecordNumber(0); + + updateGeometry(leftMargin); +} + +KexiRecordNavigator::~KexiRecordNavigator() +{ + delete d; +} + +void KexiRecordNavigator::setInsertingEnabled(bool set) +{ + if (m_isInsertingEnabled==set) + return; + m_isInsertingEnabled = set; + if (isEnabled()) + m_navBtnNew->setEnabled( m_isInsertingEnabled ); +} + +void KexiRecordNavigator::setEnabled( bool set ) +{ + QFrame::setEnabled(set); + if (set && !m_isInsertingEnabled) + m_navBtnNew->setEnabled( false ); +} + +bool KexiRecordNavigator::eventFilter( QObject *o, QEvent *e ) +{ + if (o==m_navRecordNumber) { + bool recordEntered = false; + bool ret; + if (e->type()==QEvent::KeyPress) { + QKeyEvent *ke = static_cast<QKeyEvent*>(e); + switch (ke->key()) { + case Qt::Key_Escape: { + ke->accept(); + m_navRecordNumber->undo(); + if (m_view) + m_view->setFocus(); + return true; + } + case Qt::Key_Enter: + case Qt::Key_Return: + case Qt::Key_Tab: + case Qt::Key_BackTab: + { + recordEntered=true; + ke->accept(); //to avoid pressing Enter later + ret = true; + } + default:; + } + } + else if (e->type()==QEvent::FocusOut) { + if (static_cast<QFocusEvent*>(e)->reason()!=QFocusEvent::Tab + && static_cast<QFocusEvent*>(e)->reason()!=QFocusEvent::Backtab + && static_cast<QFocusEvent*>(e)->reason()!=QFocusEvent::Other) + recordEntered=true; + ret = false; + } + + if (recordEntered) { + bool ok=true; + uint r = m_navRecordNumber->text().toUInt(&ok); + if (!ok || r<1) + r = (recordCount()>0)?1:0; + if (m_view && (hasFocus() || e->type()==QEvent::KeyPress)) + m_view->setFocus(); + setCurrentRecordNumber(r); + emit recordNumberEntered(r); + if (d->handler) + d->handler->moveToRecordRequested(r-1); + return ret; + } + } +/* + bool ok=true; + int r = text.toInt(&ok); + if (!ok || r<1) + r = 1; + emit recordNumberEntered(r);*/ + return false; +} + +void KexiRecordNavigator::setCurrentRecordNumber(uint r) +{ + uint recCnt = recordCount(); + if (r>(recCnt+(m_isInsertingEnabled?1:0))) + r = recCnt+(m_isInsertingEnabled?1:0); + QString n; + if (r>0) + n = QString::number(r); + else + n = " "; +// if (d->navRecordNumber->text().length() != n.length()) {//resize +// d->navRecordNumber->setFixedWidth( +// d->nav1DigitWidth*QMAX( QMAX(n.length(),2)+1,d->navRecordCount->text().length()+1)+6 +// ); +// } + + m_navRecordNumber->setText(n); + m_navRecordCount->deselect(); + updateButtons(recCnt); +} + +void KexiRecordNavigator::updateButtons(uint recCnt) +{ + const uint r = currentRecordNumber(); + if (isEnabled()) { + m_navBtnPrev->setEnabled(r > 1); + m_navBtnFirst->setEnabled(r > 1); + m_navBtnNext->setEnabled(r > 0 + && r < (recCnt +(m_isInsertingEnabled?(1+d->editingIndicatorVisible/*if we're editing, next btn is avail.*/):0) ) ); + m_navBtnLast->setEnabled(r!=(recCnt+(m_isInsertingEnabled?1:0)) && (m_isInsertingEnabled || recCnt>0)); + } +} + +void KexiRecordNavigator::setRecordCount(uint count) +{ + const QString & n = QString::number(count); + if (m_isInsertingEnabled && currentRecordNumber()==0) { + setCurrentRecordNumber(1); + } + if (m_navRecordCount->text().length() != n.length()) {//resize + m_navRecordCount->setFixedWidth(m_nav1DigitWidth*n.length()+6); + + if (m_view && m_view->horizontalScrollBar()->isVisible()) { + //+width of the delta + resize(width()+(n.length()-m_navRecordCount->text().length())*m_nav1DigitWidth, height()); +// horizontalScrollBar()->move(d->navPanel->x()+d->navPanel->width()+20,horizontalScrollBar()->y()); + } + } + //update row number widget's width + const int w = m_nav1DigitWidth*QMAX( QMAX(n.length(),2)+1,m_navRecordNumber->text().length()+1)+6; + if (m_navRecordNumber->width()!=w) //resize + m_navRecordNumber->setFixedWidth(w); + + m_navRecordCount->setText(n); + m_navRecordCount->deselect(); + if (m_view) + m_view->updateScrollBars(); + updateButtons(recordCount()); +} + +uint KexiRecordNavigator::currentRecordNumber() const +{ + bool ok=true; + int r = m_navRecordNumber->text().toInt(&ok); + if (!ok || r<1) + r = 0; + return r; +} + +uint KexiRecordNavigator::recordCount() const +{ + bool ok=true; + int r = m_navRecordCount->text().toInt(&ok); + if (!ok || r<1) + r = 0; + return r; +} + +void KexiRecordNavigator::setParentView(QScrollView *view) +{ + m_view = view; +} + +void KexiRecordNavigator::updateGeometry(int leftMargin) +{ + QFrame::updateGeometry(); + if (m_view) { + int navWidth; + if (m_view->horizontalScrollBar()->isVisible()) { + navWidth = sizeHint().width(); + } + else { + navWidth = leftMargin + m_view->clipper()->width(); + } + + setGeometry( + m_view->frameWidth(), + m_view->height() - m_view->horizontalScrollBar()->sizeHint().height()-m_view->frameWidth(), + navWidth, + m_view->horizontalScrollBar()->sizeHint().height() + ); + + m_view->updateScrollBars(); + } +} + +void KexiRecordNavigator::setHBarGeometry( QScrollBar & hbar, int x, int y, int w, int h ) +{ + hbar.setGeometry( x + width(), y, w - width(), h ); +} + +void KexiRecordNavigator::setLabelText(const QString& text) +{ + m_textLabel->setText(text.isEmpty() ? QString::null : (QString::fromLatin1(" ")+text+" ")); +} + +void KexiRecordNavigator::setInsertingButtonVisible(bool set) +{ + if (set) + m_navBtnNew->show(); + else + m_navBtnNew->hide(); +} + +void KexiRecordNavigator::slotPrevButtonClicked() +{ + emit prevButtonClicked(); + if (d->handler) + d->handler->moveToPreviousRecordRequested(); +} + +void KexiRecordNavigator::slotNextButtonClicked() +{ + emit nextButtonClicked(); + if (d->handler) + d->handler->moveToNextRecordRequested(); +} + +void KexiRecordNavigator::slotLastButtonClicked() +{ + emit lastButtonClicked(); + if (d->handler) + d->handler->moveToLastRecordRequested(); +} + +void KexiRecordNavigator::slotFirstButtonClicked() +{ + emit firstButtonClicked(); + if (d->handler) + d->handler->moveToFirstRecordRequested(); +} + +void KexiRecordNavigator::slotNewButtonClicked() +{ + emit newButtonClicked(); + if (d->handler) + d->handler->addNewRecordRequested(); +} + + +void KexiRecordNavigator::setRecordHandler(KexiRecordNavigatorHandler *handler) +{ + d->handler = handler; +} + +bool KexiRecordNavigator::editingIndicatorVisible() const +{ + return d->editingIndicatorVisible; +} + +bool KexiRecordNavigator::editingIndicatorEnabled() const +{ + return d->editingIndicatorEnabled; +} + +void KexiRecordNavigator::setEditingIndicatorEnabled(bool set) +{ + d->editingIndicatorEnabled = set; + if (d->editingIndicatorEnabled) { + if (!d->editingIndicatorLabel) { + d->editingIndicatorLabel = new QLabel(this); + d->editingIndicatorLabel->setAlignment(Qt::AlignCenter); + QPixmap pix; + pix.convertFromImage( *KexiRecordMarker::penImage() ); + d->editingIndicatorLabel->setFixedWidth( pix.width() + 2*2 ); + d->lyr->insertWidget( 0, d->editingIndicatorLabel ); + } + d->editingIndicatorLabel->show(); + } + else { + if (d->editingIndicatorLabel) { + d->editingIndicatorLabel->hide(); + } + } +} + +void KexiRecordNavigator::showEditingIndicator(bool show) +{ + d->editingIndicatorVisible = show; + updateButtons(recordCount()); //this will refresh 'next btn' + if (!d->editingIndicatorEnabled) + return; + if (d->editingIndicatorVisible) { + QPixmap pix; + pix.convertFromImage( *KexiRecordMarker::penImage() ); + d->editingIndicatorLabel->setPixmap( pix ); + QToolTip::add( d->editingIndicatorLabel, i18n("Editing indicator") ); + } + else { + d->editingIndicatorLabel->setPixmap( QPixmap() ); + QToolTip::remove( d->editingIndicatorLabel ); + } +} + +//------------------------------------------------ + +//! @internal +class KexiRecordNavigatorActionsInternal { + public: + KexiRecordNavigatorActionsInternal() + : moveToFirstRecord(i18n("First row"), "navigator_first", i18n("Go to first row")) + , moveToPreviousRecord(i18n("Previous row"), "navigator_prev", i18n("Go to previous row")) + , moveToNextRecord(i18n("Next row"), "navigator_next", i18n("Go to next row")) + , moveToLastRecord(i18n("Last row"), "navigator_last", i18n("Go to last row")) + , moveToNewRecord(i18n("New row"), "navigator_new", i18n("Go to new row")) + { + } + static void init(); + KGuiItem moveToFirstRecord; + KGuiItem moveToPreviousRecord; + KGuiItem moveToNextRecord; + KGuiItem moveToLastRecord; + KGuiItem moveToNewRecord; +}; + +static KStaticDeleter<KexiRecordNavigatorActionsInternal> KexiRecordNavigatorActions_deleter; +KexiRecordNavigatorActionsInternal* KexiRecordNavigatorActions_internal = 0; + +void KexiRecordNavigatorActionsInternal::init() +{ + if (!KexiRecordNavigatorActions_internal) + KexiRecordNavigatorActions_deleter.setObject(KexiRecordNavigatorActions_internal, + new KexiRecordNavigatorActionsInternal()); +} + +const KGuiItem& KexiRecordNavigator::Actions::moveToFirstRecord() +{ KexiRecordNavigatorActionsInternal::init(); return KexiRecordNavigatorActions_internal->moveToFirstRecord; } + +const KGuiItem& KexiRecordNavigator::Actions::moveToPreviousRecord() +{ KexiRecordNavigatorActionsInternal::init(); return KexiRecordNavigatorActions_internal->moveToPreviousRecord; } + +const KGuiItem& KexiRecordNavigator::Actions::moveToNextRecord() +{ KexiRecordNavigatorActionsInternal::init(); return KexiRecordNavigatorActions_internal->moveToNextRecord; } + +const KGuiItem& KexiRecordNavigator::Actions::moveToLastRecord() +{ KexiRecordNavigatorActionsInternal::init(); return KexiRecordNavigatorActions_internal->moveToLastRecord; } + +const KGuiItem& KexiRecordNavigator::Actions::moveToNewRecord() +{ KexiRecordNavigatorActionsInternal::init(); return KexiRecordNavigatorActions_internal->moveToNewRecord; } + +#include "kexirecordnavigator.moc" diff --git a/kexi/widget/utils/kexirecordnavigator.h b/kexi/widget/utils/kexirecordnavigator.h new file mode 100644 index 00000000..674746e2 --- /dev/null +++ b/kexi/widget/utils/kexirecordnavigator.h @@ -0,0 +1,190 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Lucijan Busch <lucijan@kde.org> + Copyright (C) 2003-2004 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXIRECORDNAVIGATOR_H +#define KEXIRECORDNAVIGATOR_H + +#include <qframe.h> +#include <kexi_export.h> + +class QToolButton; +class QIntValidator; +class QScrollView; +class QScrollBar; +class QLabel; +class KLineEdit; +class KGuiItem; +class KexiRecordNavigatorPrivate; + +//! \brief KexiRecordNavigatorHandler interface handles requests generated by KexiRecordNavigator +class KEXIGUIUTILS_EXPORT KexiRecordNavigatorHandler +{ + public: + KexiRecordNavigatorHandler(); + virtual ~KexiRecordNavigatorHandler(); + + //! Moving to record \a r is requested. Records are counted from 0. + virtual void moveToRecordRequested(uint r) = 0; + virtual void moveToLastRecordRequested() = 0; + virtual void moveToPreviousRecordRequested() = 0; + virtual void moveToNextRecordRequested() = 0; + virtual void moveToFirstRecordRequested() = 0; + virtual void addNewRecordRequested() = 0; +}; + + +//! \brief KexiRecordNavigator class provides a record navigator. +/*! Record navigator is usually used for data tables (e.g. KexiTableView) + or data-aware forms. + + You can plug KexiRecordNavigator object to your data-aware object in two ways: + 1) By connectiong to slots prevButtonClicked(), etc. + 2) A bit cleaner way: by inheriting from KexiRecordNavigatorHandler interface + in your data-aware class and implementing all it's prototype methods like + moveToRecordRequested(), and then caling setRecordHandler() on navigator's object. + Note that using this way, you can allow to exist more than one navigator widget + connected with your data-aware object (don't matter if this is sane). + */ +class KEXIGUIUTILS_EXPORT KexiRecordNavigator : public QFrame +{ + Q_OBJECT + + public: + KexiRecordNavigator(QWidget *parent, int leftMargin = 0, const char *name=0); + virtual ~KexiRecordNavigator(); + + void setParentView(QScrollView *view); + + /*! Sets record navigator handler. This allows to react + on actions performed within navigator and vice versa. */ + void setRecordHandler(KexiRecordNavigatorHandler *handler); + + /*! \return true if data inserting is enabled (the default). */ + inline bool isInsertingEnabled() const { return m_isInsertingEnabled; } + + /*! \return current record number displayed for this navigator. + can return 0, if the 'text box's content is cleared. */ + uint currentRecordNumber() const; + + /*! \return record count displayed for this navigator. */ + uint recordCount() const; + + /*! Sets horizontal bar's \a hbar (at the bottom) geometry so this record navigator + is properly positioned together with horizontal scroll bar. This method is used + in QScrollView::setHBarGeometry() implementations: + see KexiTableView::setHBarGeometry() and KexiFormScrollView::setHBarGeometry() + for usage examples. */ + void setHBarGeometry( QScrollBar & hbar, int x, int y, int w, int h ); + + /*! @internal used for keyboard handling. */ + virtual bool eventFilter( QObject *o, QEvent *e ); + + /*! \return true if "editing" indicator is visible for this navigator. + @see showEditingIndicator() */ + bool editingIndicatorVisible() const; + + /*! \return true if "editing" indicator is enabled for this navigator. + Only meaningful if setEditingIndicatorEnabled(true) is called. */ + bool editingIndicatorEnabled() const; + + //! @short A set of GUI items usable for displaying related actions. + /*! For instance, the items are used by Kexi main window to create shared actions. */ + class KEXIGUIUTILS_EXPORT Actions { + public: + static const KGuiItem& moveToFirstRecord(); + static const KGuiItem& moveToPreviousRecord(); + static const KGuiItem& moveToNextRecord(); + static const KGuiItem& moveToLastRecord(); + static const KGuiItem& moveToNewRecord(); + }; + + public slots: + /*! Sets insertingEnabled flag. If true, "+" button will be enabled. */ + void setInsertingEnabled(bool set); + + /*! Sets visibility of "inserting" button. */ + void setInsertingButtonVisible(bool set); + + /*! Sets visibility of the place where "editing" indicator will be displayed. + "editing" indicator will display KexiRecordMarker::penImage() image when + setEditingIndicatorVisible() is called. + This method is currently used e.g. within standard kexi forms + (see KexiFormScrollView class). */ + void setEditingIndicatorEnabled(bool set); + + /*! Shows or hides "editing" indicator. */ + void showEditingIndicator(bool show); + + virtual void setEnabled(bool set); + + /*! Sets current record number for this navigator, + i.e. a value that will be displayed in the 'record number' text box. + This can also affect button's enabling and disabling. + If @p r is 0, 'record number' text box's content is cleared. */ + void setCurrentRecordNumber(uint r); + + /*! Sets record count for this navigator. + This can also affect button's enabling and disabling. + By default count is 0. */ + void setRecordCount(uint count); + + void updateGeometry(int leftMargin); + + /*! Sets label text at the left of the for record navigator's button. + By default this label contains translated "Row:" text. */ + void setLabelText(const QString& text); + + signals: + void prevButtonClicked(); + void nextButtonClicked(); + void lastButtonClicked(); + void firstButtonClicked(); + void newButtonClicked(); + void recordNumberEntered( uint r ); + + protected slots: + void slotPrevButtonClicked(); + void slotNextButtonClicked(); + void slotLastButtonClicked(); + void slotFirstButtonClicked(); + void slotNewButtonClicked(); + //void slotRecordNumberReturnPressed(const QString& text); + + protected: + void updateButtons(uint recCnt); + + QLabel *m_textLabel; + QToolButton *m_navBtnFirst; + QToolButton *m_navBtnPrev; + QToolButton *m_navBtnNext; + QToolButton *m_navBtnLast; + QToolButton *m_navBtnNew; + KLineEdit *m_navRecordNumber; + QIntValidator *m_navRecordNumberValidator; + KLineEdit *m_navRecordCount; //!< readonly counter + uint m_nav1DigitWidth; +// uint m_recordCount; + QScrollView *m_view; + bool m_isInsertingEnabled : 1; + + KexiRecordNavigatorPrivate *d; +}; + +#endif diff --git a/kexi/widget/utils/kexisharedactionclient.cpp b/kexi/widget/utils/kexisharedactionclient.cpp new file mode 100644 index 00000000..4dbd9299 --- /dev/null +++ b/kexi/widget/utils/kexisharedactionclient.cpp @@ -0,0 +1,39 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "kexisharedactionclient.h" + +#include <kaction.h> + +KexiSharedActionClient::KexiSharedActionClient() + : m_sharedActions(101, false) +{ +} + +KexiSharedActionClient::~KexiSharedActionClient() +{ +} + +void KexiSharedActionClient::plugSharedAction(KAction* a) +{ + if (!a) + return; + m_sharedActions.insert(a->name(), a); +} + diff --git a/kexi/widget/utils/kexisharedactionclient.h b/kexi/widget/utils/kexisharedactionclient.h new file mode 100644 index 00000000..80181bc7 --- /dev/null +++ b/kexi/widget/utils/kexisharedactionclient.h @@ -0,0 +1,49 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXISHAREDACTIONCLIENT_H +#define KEXISHAREDACTIONCLIENT_H + +#include <qasciidict.h> + +class KAction; +#include <kexi_export.h> + +//! The KexiSharedActionClient is an interface using application-wide (shared) actions. +/** See KexiTableView and KexiFormScrollView for example usage. +*/ +class KEXIGUIUTILS_EXPORT KexiSharedActionClient +{ + public: + KexiSharedActionClient(); + virtual ~KexiSharedActionClient(); + + /*! Plugs action \a a for a widget. The action will be later looked up (by name) + on key press event, to get proper shortcut. If found, we know that the action is already + performed at main window's level, so we should give up. Otherwise - default shortcut + will be used (example: Shift+Enter key for "data_save_row" action). \sa KexiTableView::shortCutPressed() + */ + void plugSharedAction(KAction* a); + + protected: + //! Actions pluged for this widget using plugSharedAction(), available by name. + QAsciiDict<KAction> m_sharedActions; +}; + +#endif diff --git a/kexi/widget/utils/kexitooltip.cpp b/kexi/widget/utils/kexitooltip.cpp new file mode 100644 index 00000000..69a8b583 --- /dev/null +++ b/kexi/widget/utils/kexitooltip.cpp @@ -0,0 +1,76 @@ +/* This file is part of the KDE project + Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#include "kexitooltip.h" + +#include <qpixmap.h> +#include <qbitmap.h> +#include <qpainter.h> +#include <qimage.h> +#include <qtooltip.h> +#include <qfont.h> +#include <qfontmetrics.h> +#include <qtimer.h> + + +KexiToolTip::KexiToolTip(const QVariant& value, QWidget* parent) + : QWidget(parent, "KexiToolTip", Qt::WStyle_Customize | Qt::WType_Popup | Qt::WStyle_NoBorder + | Qt::WX11BypassWM | Qt::WDestructiveClose) + , m_value(value) +{ + setPalette( QToolTip::palette() ); + setFocusPolicy(QWidget::NoFocus); +} + +KexiToolTip::~KexiToolTip() +{ +} + +QSize KexiToolTip::sizeHint() const +{ + QSize sz(fontMetrics().boundingRect(m_value.toString()).size()); + return sz; +} + +void KexiToolTip::show() +{ + updateGeometry(); + QWidget::show(); +} + +void KexiToolTip::paintEvent( QPaintEvent *pev ) +{ + QWidget::paintEvent(pev); + QPainter p(this); + drawFrame(p); + drawContents(p); +} + +void KexiToolTip::drawFrame(QPainter& p) +{ + p.setPen( QPen(palette().active().foreground(), 1) ); + p.drawRect(rect()); +} + +void KexiToolTip::drawContents(QPainter& p) +{ + p.drawText(rect(), Qt::AlignCenter, m_value.toString()); +} + +#include "kexitooltip.moc" diff --git a/kexi/widget/utils/kexitooltip.h b/kexi/widget/utils/kexitooltip.h new file mode 100644 index 00000000..cbb0931f --- /dev/null +++ b/kexi/widget/utils/kexitooltip.h @@ -0,0 +1,47 @@ +/* This file is part of the KDE project + Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl> + + This program 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 program 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 program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KEXITOOLTIP_H +#define KEXITOOLTIP_H + +#include <qwidget.h> +#include <qvariant.h> + +//! \brief A tooltip that can display rich content +class KEXIGUIUTILS_EXPORT KexiToolTip : public QWidget +{ + Q_OBJECT + public: + KexiToolTip(const QVariant& value, QWidget* parent); + virtual ~KexiToolTip(); + + virtual QSize sizeHint() const; + + public slots: + virtual void show(); + + protected: + virtual void paintEvent( QPaintEvent *pev ); + virtual void drawFrame(QPainter& p); + virtual void drawContents(QPainter& p); + + QVariant m_value; +}; + +#endif diff --git a/kexi/widget/utils/klistviewitemtemplate.h b/kexi/widget/utils/klistviewitemtemplate.h new file mode 100644 index 00000000..1c89f96c --- /dev/null +++ b/kexi/widget/utils/klistviewitemtemplate.h @@ -0,0 +1,50 @@ +/* This file is part of the KDE project + Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl> + + 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 Street, Fifth Floor, + * Boston, MA 02110-1301, USA. +*/ + +#ifndef KLISTVIEWITEMTEMPLATE_H +#define KLISTVIEWITEMTEMPLATE_H + +#include <klistview.h> + +//! QListViewItem class with ability for storing additional data member +template<class type> +class KListViewItemTemplate : public KListViewItem +{ + public: + KListViewItemTemplate(type _data, QListView *parent) + : KListViewItem(parent), data(_data) {} + KListViewItemTemplate(type _data, QListViewItem *parent) + : KListViewItem(parent), data(_data) {} + KListViewItemTemplate(type _data, QListView *parent, QListViewItem *after) + : KListViewItem(parent, after), data(_data) {} + KListViewItemTemplate(type _data, QListViewItem *parent, QListViewItem *after) + : KListViewItem(parent, after), data(_data) {} + KListViewItemTemplate(type _data, QListView *parent, QString label1, QString label2=QString::null, QString label3=QString::null, QString label4=QString::null, QString label5=QString::null, QString label6=QString::null, QString label7=QString::null, QString label8=QString::null) + : KListViewItem(parent, label1, label2, label3, label4, label5, label6, label7, label8), data(_data) {} + KListViewItemTemplate(type _data, QListViewItem *parent, QString label1, QString label2=QString::null, QString label3=QString::null, QString label4=QString::null, QString label5=QString::null, QString label6=QString::null, QString label7=QString::null, QString label8=QString::null) + : KListViewItem(parent, label1, label2, label3, label4, label5, label6, label7, label8), data(_data) {} + KListViewItemTemplate(type _data, QListView *parent, QListViewItem *after, QString label1, QString label2=QString::null, QString label3=QString::null, QString label4=QString::null, QString label5=QString::null, QString label6=QString::null, QString label7=QString::null, QString label8=QString::null) + : KListViewItem(parent, after, label1, label2, label3, label4, label5, label6, label7, label8), data(_data) {} + KListViewItemTemplate(type _data, QListViewItem *parent, QListViewItem *after, QString label1, QString label2=QString::null, QString label3=QString::null, QString label4=QString::null, QString label5=QString::null, QString label6=QString::null, QString label7=QString::null, QString label8=QString::null) + : KListViewItem(parent, after, label1, label2, label3, label4, label5, label6, label7, label8), data(_data) {} + + type data; +}; + +#endif |