diff --git a/src/main/KexiMainWindow.cpp b/src/main/KexiMainWindow.cpp index 34ec9b3a0..e3d812818 100644 --- a/src/main/KexiMainWindow.cpp +++ b/src/main/KexiMainWindow.cpp @@ -1,4385 +1,4385 @@ /* This file is part of the KDE project Copyright (C) 2003 Lucijan Busch Copyright (C) 2003-2017 Jarosław Staniek 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 "KexiMainWindow.h" #include "KexiMainWindow_p.h" #include "kexiactionproxy.h" #include "kexipartmanager.h" #include "kexipart.h" #include "kexipartinfo.h" #include "kexipartguiclient.h" #include "kexiproject.h" #include "kexiprojectdata.h" #include "kexi.h" #include "kexiinternalpart.h" #include "kexiactioncategories.h" #include "kexifinddialog.h" #include "kexisearchandreplaceiface.h" #include "KexiBugReportDialog.h" #define KEXI_SKIP_REGISTERICONSRESOURCE #define KEXI_SKIP_SETUPPRIVATEICONSRESOURCE #include "KexiRegisterResource_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "startup/KexiStartup.h" #include "startup/KexiNewProjectAssistant.h" #include "startup/KexiOpenProjectAssistant.h" #include "startup/KexiWelcomeAssistant.h" #include "startup/KexiImportExportAssistant.h" #include "startup/KexiStartupDialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if !defined(KexiVDebug) # define KexiVDebug if (0) qDebug() #endif #ifdef HAVE_KCRASH #include //! @todo else, add Breakpad? https://phabricator.kde.org/T1642 #endif KexiDockWidgetStyle::KexiDockWidgetStyle(const QString &baseStyleName) : QProxyStyle(baseStyleName) { } KexiDockWidgetStyle::~KexiDockWidgetStyle() { } void KexiDockWidgetStyle::polish(QWidget* widget) { baseStyle()->polish(widget); widget->setContentsMargins(0, 0, 0, 0); } class Q_DECL_HIDDEN KexiDockWidget::Private { public: Private() {} QSize hint; }; KexiDockWidget::KexiDockWidget(const QString &_tabText, QWidget *parent) : QDockWidget(parent), tabText(_tabText), d(new Private) { // No floatable dockers, Dolphin had problems, we don't want the same... // https://bugs.kde.org/show_bug.cgi?id=288629 // https://bugs.kde.org/show_bug.cgi?id=322299 setFeatures(QDockWidget::NoDockWidgetFeatures);//DockWidgetClosable); setAllowedAreas(Qt::LeftDockWidgetArea|Qt::RightDockWidgetArea); setFocusPolicy(Qt::NoFocus); if (style()->objectName().compare("windowsvista", Qt::CaseInsensitive) == 0) { // windowsvista style has broken accelerator visualization support KAcceleratorManager::setNoAccel(this); } KexiDockWidgetStyle *customStyle = new KexiDockWidgetStyle(style()->objectName()); customStyle->setParent(this); setStyle(customStyle); setTitleBarWidget(new QWidget(this)); // hide the title layout()->setContentsMargins(0, 0, 0, 0); layout()->setSpacing(0); } KexiDockWidget::~KexiDockWidget() { delete d; } void KexiDockWidget::paintEvent(QPaintEvent *pe) { Q_UNUSED(pe); QStylePainter p(this); if (isFloating()) { QStyleOptionFrame framOpt; framOpt.initFrom(this); p.drawPrimitive(QStyle::PE_FrameDockWidget, framOpt); } // Title must be painted after the frame, since the areas overlap, and // the title may wish to extend out to all sides (eg. XP style) - QStyleOptionDockWidgetV2 titleOpt; + QStyleOptionDockWidget titleOpt; initStyleOption(&titleOpt); p.drawControl(QStyle::CE_DockWidgetTitle, titleOpt); } void KexiDockWidget::setSizeHint(const QSize& hint) { d->hint = hint; } QSize KexiDockWidget::sizeHint() const { return d->hint.isValid() ? d->hint : QDockWidget::sizeHint(); } //------------------------------------------------- KexiMainWindowTabWidget::KexiMainWindowTabWidget(QWidget *parent, KexiMainWidget* mainWidget) : QTabWidget(parent) , m_mainWidget(mainWidget) , m_tabIndex(-1) { m_closeAction = new QAction(koIcon("tab-close"), xi18n("&Close Tab"), this); m_closeAction->setToolTip(xi18n("Close the current tab")); m_closeAction->setWhatsThis(xi18n("Closes the current tab.")); m_closeAllTabsAction = new QAction(xi18n("Cl&ose All Tabs"), this); m_closeAllTabsAction->setToolTip(xi18n("Close all tabs")); m_closeAllTabsAction->setWhatsThis(xi18n("Closes all tabs.")); connect(m_closeAction, SIGNAL(triggered()), this, SLOT(closeTab())); connect(m_closeAllTabsAction, SIGNAL(triggered()), this, SLOT(closeAllTabs())); //! @todo insert window list in the corner widget as in firefox #if 0 // close-tab button: QToolButton* rightWidget = new QToolButton(this); rightWidget->setDefaultAction(m_closeAction); rightWidget->setText(QString()); rightWidget->setAutoRaise(true); rightWidget->adjustSize(); setCornerWidget(rightWidget, Qt::TopRightCorner); #endif setMovable(true); setDocumentMode(true); tabBar()->setExpanding(true); } KexiMainWindowTabWidget::~KexiMainWindowTabWidget() { } void KexiMainWindowTabWidget::paintEvent(QPaintEvent * event) { if (count() > 0) QTabWidget::paintEvent(event); else QWidget::paintEvent(event); } void KexiMainWindowTabWidget::mousePressEvent(QMouseEvent *event) { //! @todo KEXI3 test KexiMainWindowTabWidget's contextMenu event port from KTabWidget if (event->button() == Qt::RightButton) { int tab = tabBar()->tabAt(event->pos()); const QPoint realPos(tabBar()->mapToGlobal(event->pos())); if (QRect(tabBar()->mapToGlobal(QPoint(0,0)), tabBar()->mapToGlobal(QPoint(tabBar()->width()-1, tabBar()->height()-1))).contains(realPos)) { showContextMenuForTab(tab, tabBar()->mapToGlobal(event->pos())); return; } } QTabWidget::mousePressEvent(event); } void KexiMainWindowTabWidget::closeTab() { KexiMainWindow *main = dynamic_cast(KexiMainWindowIface::global()); if (main) { main->closeWindowForTab(m_tabIndex); } } tristate KexiMainWindowTabWidget::closeAllTabs() { tristate alternateResult = true; QList windowList; KexiMainWindow *main = dynamic_cast(KexiMainWindowIface::global()); if (!main) { return alternateResult; } for (int i = 0; i < count(); i++) { KexiWindow *window = main->windowForTab(i); if (window) { windowList.append(window); } } foreach (KexiWindow *window, windowList) { tristate result = main->closeWindow(window); if (result != true && result != false) { return result; } if (result == false) { alternateResult = false; } } return alternateResult; } void KexiMainWindowTabWidget::showContextMenuForTab(int index, const QPoint& point) { QMenu menu; if (index >= 0) { menu.addAction(m_closeAction); } if (count() > 0) { menu.addAction(m_closeAllTabsAction); } //! @todo add "&Detach Tab" if (menu.actions().isEmpty()) { return; } setTabIndexFromContextMenu(index); menu.exec(point); } void KexiMainWindowTabWidget::setTabIndexFromContextMenu(int clickedIndex) { if (currentIndex() == -1) { m_tabIndex = -1; return; } m_tabIndex = clickedIndex; } //------------------------------------------------- static bool setupIconTheme(KLocalizedString *errorMessage, KLocalizedString *detailsErrorMessage) { // Register kexi resource first to have priority over the standard breeze theme. // For example "table" icon exists in both resources. if (!registerResource("icons/kexi_breeze.rcc", QStandardPaths::AppDataLocation, QString(), QString(), errorMessage, detailsErrorMessage) || !registerGlobalBreezeIconsResource(errorMessage, detailsErrorMessage)) { return false; } setupBreezeIconTheme(); // tell KIconLoader an co. about the theme KConfigGroup cg(KSharedConfig::openConfig(), "Icons"); cg.writeEntry("Theme", "breeze"); cg.sync(); return true; } //! @todo 3.1 replace with KexiStyle bool setupApplication() { #if defined Q_OS_WIN || defined Q_OS_MACOS // Only this style matches current Kexi theme and can be supported/tested const char *name = "breeze"; QScopedPointer style(QStyleFactory::create(name)); if (!style || style->objectName() != name) { qWarning() << qPrintable(QString("Could not find application style %1. " "Kexi will not start. Please check if Kexi is properly installed. ") .arg(name)); return false; } qApp->setStyle(style.take()); #endif return true; } //static int KexiMainWindow::create(const QStringList &arguments, const QString &componentName, const QList &extraOptions) { qApp->setQuitOnLastWindowClosed(false); KLocalizedString::setApplicationDomain("kexi"); //! @todo KEXI3 app->setAttribute(Qt::AA_UseHighDpiPixmaps, true); KexiAboutData aboutData; if (!componentName.isEmpty()) { aboutData.setComponentName(componentName); } KAboutData::setApplicationData(aboutData); if (!setupApplication()) { return 1; } #ifdef HAVE_KCRASH KCrash::initialize(); #endif KLocalizedString errorMessage; KLocalizedString detailsErrorMessage; if (!setupIconTheme(&errorMessage, &detailsErrorMessage)) { if (detailsErrorMessage.isEmpty()) { KMessageBox::error(nullptr, errorMessage.toString()); } else { KMessageBox::detailedError(nullptr, errorMessage.toString(), detailsErrorMessage.toString()); } qWarning() << qPrintable(errorMessage.toString(Kuit::PlainText)); return 1; } QApplication::setWindowIcon(koIcon("kexi")); const tristate res = KexiStartupHandler::global()->init(arguments, extraOptions); if (!res || ~res) { return (~res) ? 0 : 1; } //qDebug() << "startupActions OK"; /* Exit requested, e.g. after database removing. */ if (KexiStartupHandler::global()->action() == KexiStartupData::Exit) { return 0; } KexiMainWindow *win = new KexiMainWindow(); #ifdef KEXI_DEBUG_GUI QWidget* debugWindow = 0; KConfigGroup generalGroup = KSharedConfig::openConfig()->group("General"); if (generalGroup.readEntry("ShowInternalDebugger", false)) { debugWindow = KexiUtils::createDebugWindow(win); debugWindow->show(); } #endif if (true != win->startup()) { delete win; return 1; } win->restoreSettings(); win->show(); #ifdef KEXI_DEBUG_GUI win->raise(); static_cast(win)->activateWindow(); #endif /*foreach (QWidget *widget, QApplication::topLevelWidgets()) { qDebug() << widget; }*/ return 0; } //------------------------------------------------- KexiMainMenuActionShortcut::KexiMainMenuActionShortcut(const QKeySequence& key, QAction *action, QWidget *parent) : QShortcut(key, parent) , m_action(action) { connect(this, SIGNAL(activated()), this, SLOT(slotActivated())); } KexiMainMenuActionShortcut::~KexiMainMenuActionShortcut() { } void KexiMainMenuActionShortcut::slotActivated() { if (!m_action->isEnabled()) { return; } m_action->trigger(); } //------------------------------------------------- KexiMainWindow::KexiMainWindow(QWidget *parent) : KexiMainWindowSuper(parent) , KexiMainWindowIface() , KexiGUIMessageHandler(this) , d(new KexiMainWindow::Private(this)) { setObjectName("KexiMainWindow"); setAttribute(Qt::WA_DeleteOnClose); kexiTester() << KexiTestObject(this); if (d->userMode) qDebug() << "starting up in the User Mode"; setAsDefaultHost(); //this is default host now. //get informed connect(&Kexi::partManager(), SIGNAL(partLoaded(KexiPart::Part*)), this, SLOT(slotPartLoaded(KexiPart::Part*))); connect(&Kexi::partManager(), SIGNAL(newObjectRequested(KexiPart::Info*)), this, SLOT(newObject(KexiPart::Info*))); setAcceptDrops(true); setupActions(); setupMainWidget(); updateAppCaption(); if (!d->userMode) { setupContextHelp(); setupPropertyEditor(); } invalidateActions(); d->timer.singleShot(0, this, SLOT(slotLastActions())); if (KexiStartupHandler::global()->forcedFullScreen()) { toggleFullScreen(true); } // --- global config //! @todo move to specialized KexiConfig class KConfigGroup tablesGroup(d->config->group("Tables")); const int defaultMaxLengthForTextFields = tablesGroup.readEntry("DefaultMaxLengthForTextFields", int(-1)); if (defaultMaxLengthForTextFields >= 0) { KDbField::setDefaultMaxLength(defaultMaxLengthForTextFields); } // --- /global config } KexiMainWindow::~KexiMainWindow() { d->forceWindowClosing = true; closeProject(); delete d; Kexi::deleteGlobalObjects(); } KexiProject *KexiMainWindow::project() { return d->prj; } QList KexiMainWindow::allActions() const { return actionCollection()->actions(); } KActionCollection *KexiMainWindow::actionCollection() const { return d->actionCollection; } KexiWindow* KexiMainWindow::currentWindow() const { return windowForTab(d->mainWidget->tabWidget()->currentIndex()); } KexiWindow* KexiMainWindow::windowForTab(int tabIndex) const { if (!d->mainWidget->tabWidget()) return 0; KexiWindowContainer *windowContainer = dynamic_cast(d->mainWidget->tabWidget()->widget(tabIndex)); if (!windowContainer) return 0; return windowContainer->window; } void KexiMainWindow::setupMainMenuActionShortcut(QAction * action) { if (!action->shortcut().isEmpty()) { foreach(const QKeySequence &shortcut, action->shortcuts()) { (void)new KexiMainMenuActionShortcut(shortcut, action, this); } } } static void addThreeDotsToActionText(QAction* action) { action->setText(xi18nc("Action name with three dots...", "%1...", action->text())); } QAction * KexiMainWindow::addAction(const char *name, const QIcon &icon, const QString& text, const char *shortcut) { QAction *action = icon.isNull() ? new QAction(text, this) : new QAction(icon, text, this); actionCollection()->addAction(name, action); if (shortcut) { action->setShortcut(QKeySequence(shortcut)); QShortcut *s = new QShortcut(action->shortcut(), this); connect(s, SIGNAL(activated()), action, SLOT(trigger())); } return action; } QAction * KexiMainWindow::addAction(const char *name, const QString& text, const char *shortcut) { return addAction(name, QIcon(), text, shortcut); } void KexiMainWindow::setupActions() { KActionCollection *ac = actionCollection(); // PROJECT MENU QAction *action; ac->addAction("project_new", action = new KexiMenuWidgetAction(KStandardAction::New, this)); addThreeDotsToActionText(action); action->setShortcuts(KStandardShortcut::openNew()); action->setToolTip(xi18n("Create a new project")); action->setWhatsThis( xi18n("Creates a new project. Currently opened project is not affected.")); connect(action, SIGNAL(triggered()), this, SLOT(slotProjectNew())); setupMainMenuActionShortcut(action); ac->addAction("project_open", action = new KexiMenuWidgetAction(KStandardAction::Open, this)); action->setToolTip(xi18n("Open an existing project")); action->setWhatsThis( xi18n("Opens an existing project. Currently opened project is not affected.")); connect(action, SIGNAL(triggered()), this, SLOT(slotProjectOpen())); setupMainMenuActionShortcut(action); { ac->addAction("project_welcome", action = d->action_project_welcome = new KexiMenuWidgetAction( QIcon(), xi18n("Welcome"), this)); addThreeDotsToActionText(action); connect(action, SIGNAL(triggered()), this, SLOT(slotProjectWelcome())); setupMainMenuActionShortcut(action); action->setToolTip(xi18n("Show Welcome page")); action->setWhatsThis( xi18n("Shows Welcome page with list of recently opened projects and other information. ")); } ac->addAction("project_save", d->action_save = KStandardAction::save(this, SLOT(slotProjectSave()), this)); d->action_save->setToolTip(xi18n("Save object changes")); d->action_save->setWhatsThis(xi18n("Saves object changes from currently selected window.")); setupMainMenuActionShortcut(d->action_save); d->action_save_as = addAction("project_saveas", koIcon("document-save-as"), xi18n("Save &As...")); d->action_save_as->setToolTip(xi18n("Save object as")); d->action_save_as->setWhatsThis( xi18n("Saves object from currently selected window under a new name (within the same project).")); connect(d->action_save_as, SIGNAL(triggered()), this, SLOT(slotProjectSaveAs())); #ifdef KEXI_SHOW_UNIMPLEMENTED ac->addAction("project_properties", action = d->action_project_properties = new KexiMenuWidgetAction( koIcon("document-properties"), futureI18n("Project Properties"), this)); connect(action, SIGNAL(triggered()), this, SLOT(slotProjectProperties())); setupMainMenuActionShortcut(action); #else d->action_project_properties = d->dummy_action; #endif //! @todo replace document-import icon with something other ac->addAction("project_import_export_send", action = d->action_project_import_export_send = new KexiMenuWidgetAction( koIcon("document-import"), xi18n("&Import, Export or Send..."), this)); action->setToolTip(xi18n("Import, export or send project")); action->setWhatsThis( xi18n("Imports, exports or sends project.")); connect(action, SIGNAL(triggered()), this, SLOT(slotProjectImportExportOrSend())); setupMainMenuActionShortcut(action); ac->addAction("project_close", action = d->action_close = new KexiMenuWidgetAction( koIcon("window-close"), xi18nc("Close Project", "&Close"), this)); action->setToolTip(xi18n("Close the current project")); action->setWhatsThis(xi18n("Closes the current project.")); connect(action, SIGNAL(triggered()), this, SLOT(slotProjectClose())); setupMainMenuActionShortcut(action); ac->addAction("quit", action = new KexiMenuWidgetAction(KStandardAction::Quit, this)); connect(action, SIGNAL(triggered()), this, SLOT(slotProjectQuit())); action->setWhatsThis(xi18n("Quits Kexi application.")); setupMainMenuActionShortcut(action); #ifdef KEXI_SHOW_UNIMPLEMENTED d->action_project_relations = addAction("project_relations", KexiIcon("database-relations"), futureI18n("&Relationships..."), "Ctrl+R"); d->action_project_relations->setToolTip(futureI18n("Project relationships")); d->action_project_relations->setWhatsThis(futureI18n("Shows project relationships.")); connect(d->action_project_relations, SIGNAL(triggered()), this, SLOT(slotProjectRelations())); #else d->action_project_relations = d->dummy_action; #endif d->action_tools_import_project = addAction("tools_import_project", KexiIcon("database-import"), xi18n("&Import Database...")); d->action_tools_import_project->setToolTip(xi18n("Import entire database as a Kexi project")); d->action_tools_import_project->setWhatsThis( xi18n("Imports entire database as a Kexi project.")); connect(d->action_tools_import_project, SIGNAL(triggered()), this, SLOT(slotToolsImportProject())); d->action_tools_data_import = addAction("tools_import_tables", koIcon("document-import"), xi18n("Import Tables...")); d->action_tools_data_import->setToolTip(xi18n("Import data from an external source into this project")); d->action_tools_data_import->setWhatsThis(xi18n("Imports data from an external source into this project.")); connect(d->action_tools_data_import, SIGNAL(triggered()), this, SLOT(slotToolsImportTables())); d->action_tools_compact_database = addAction("tools_compact_database", //! @todo icon koIcon("application-x-compress"), xi18n("&Compact Database...")); d->action_tools_compact_database->setToolTip(xi18n("Compact the current database project")); d->action_tools_compact_database->setWhatsThis( xi18n("Compacts the current database project, so it will take less space and work faster.")); connect(d->action_tools_compact_database, SIGNAL(triggered()), this, SLOT(slotToolsCompactDatabase())); if (d->userMode) d->action_project_import_data_table = 0; else { d->action_project_import_data_table = addAction("project_import_data_table", KexiIcon("document-empty"), /*! @todo: change to "file_import" with a table or so */ xi18nc("Import->Table Data From File...", "Import Data From &File...")); d->action_project_import_data_table->setToolTip(xi18n("Import table data from a file")); d->action_project_import_data_table->setWhatsThis(xi18n("Imports table data from a file.")); connect(d->action_project_import_data_table, SIGNAL(triggered()), this, SLOT(slotProjectImportDataTable())); } d->action_project_export_data_table = addAction("project_export_data_table", KexiIcon("table"), /*! @todo: change to "file_export" with a table or so */ xi18nc("Export->Table or Query Data to File...", "Export Data to &File...")); d->action_project_export_data_table->setToolTip( xi18n("Export data from the active table or query to a file")); d->action_project_export_data_table->setWhatsThis( xi18n("Exports data from the active table or query to a file.")); connect(d->action_project_export_data_table, SIGNAL(triggered()), this, SLOT(slotProjectExportDataTable())); //! @todo new QAction(xi18n("From File..."), "document-open", 0, //! this, SLOT(slotImportFile()), actionCollection(), "project_import_file"); //! @todo new QAction(xi18n("From Server..."), "network-server-database", 0, //! this, SLOT(slotImportServer()), actionCollection(), "project_import_server"); #ifdef KEXI_QUICK_PRINTING_SUPPORT ac->addAction("project_print", d->action_project_print = KStandardAction::print(this, SLOT(slotProjectPrint()), this)); d->action_project_print->setToolTip(futureI18n("Print data from the active table or query")); d->action_project_print->setWhatsThis(futureI18n("Prints data from the active table or query.")); ac->addAction("project_print_preview", d->action_project_print_preview = KStandardAction::printPreview( this, SLOT(slotProjectPrintPreview()), this)); d->action_project_print_preview->setToolTip( futureI18n("Show print preview for the active table or query")); d->action_project_print_preview->setWhatsThis( futureI18n("Shows print preview for the active table or query.")); d->action_project_print_setup = addAction("project_print_setup", koIcon("configure"), futureI18n("Print Set&up...")); //!< @todo document-page-setup could be a better icon d->action_project_print_setup->setToolTip( futureI18n("Show print setup for the active table or query")); d->action_project_print_setup->setWhatsThis( futureI18n("Shows print setup for the active table or query.")); connect(d->action_project_print_setup, SIGNAL(triggered()), this, SLOT(slotProjectPageSetup())); #endif //EDIT MENU d->action_edit_cut = createSharedAction(KStandardAction::Cut); d->action_edit_copy = createSharedAction(KStandardAction::Copy); d->action_edit_paste = createSharedAction(KStandardAction::Paste); if (d->userMode) d->action_edit_paste_special_data_table = 0; else { d->action_edit_paste_special_data_table = addAction( "edit_paste_special_data_table", d->action_edit_paste->icon(), xi18nc("Paste Special->As Data &Table...", "Paste Special...")); d->action_edit_paste_special_data_table->setToolTip( xi18n("Paste clipboard data as a table")); d->action_edit_paste_special_data_table->setWhatsThis( xi18n("Pastes clipboard data as a table.")); connect(d->action_edit_paste_special_data_table, SIGNAL(triggered()), this, SLOT(slotEditPasteSpecialDataTable())); } d->action_edit_copy_special_data_table = addAction( "edit_copy_special_data_table", KexiIcon("table"), xi18nc("Copy Special->Table or Query Data...", "Copy Special...")); d->action_edit_copy_special_data_table->setToolTip( xi18n("Copy selected table or query data to clipboard")); d->action_edit_copy_special_data_table->setWhatsThis( xi18n("Copies selected table or query data to clipboard.")); connect(d->action_edit_copy_special_data_table, SIGNAL(triggered()), this, SLOT(slotEditCopySpecialDataTable())); d->action_edit_undo = createSharedAction(KStandardAction::Undo); d->action_edit_undo->setWhatsThis(xi18n("Reverts the most recent editing action.")); d->action_edit_redo = createSharedAction(KStandardAction::Redo); d->action_edit_redo->setWhatsThis(xi18n("Reverts the most recent undo action.")); ac->addAction("edit_find", d->action_edit_find = KStandardAction::find( this, SLOT(slotEditFind()), this)); d->action_edit_find->setToolTip(xi18n("Find text")); d->action_edit_find->setWhatsThis(xi18n("Looks up the first occurrence of a piece of text.")); ac->addAction("edit_findnext", d->action_edit_findnext = KStandardAction::findNext( this, SLOT(slotEditFindNext()), this)); ac->addAction("edit_findprevious", d->action_edit_findprev = KStandardAction::findPrev( this, SLOT(slotEditFindPrevious()), this)); d->action_edit_replace = 0; //! @todo d->action_edit_replace = KStandardAction::replace( //! this, SLOT(slotEditReplace()), actionCollection(), "project_print_preview" ); d->action_edit_replace_all = 0; //! @todo d->action_edit_replace_all = new QAction( xi18n("Replace All"), "", 0, //! this, SLOT(slotEditReplaceAll()), actionCollection(), "edit_replaceall"); d->action_edit_select_all = createSharedAction(KStandardAction::SelectAll); d->action_edit_delete = createSharedAction(xi18n("&Delete"), koIconName("edit-delete"), QKeySequence(), "edit_delete"); d->action_edit_delete->setToolTip(xi18n("Delete selected object")); d->action_edit_delete->setWhatsThis(xi18n("Deletes currently selected object.")); d->action_edit_delete_row = createSharedAction(xi18n("Delete Record"), KexiIconName("edit-table-delete-row"), QKeySequence(Qt::CTRL + Qt::Key_Delete), "edit_delete_row"); d->action_edit_delete_row->setToolTip(xi18n("Delete the current record")); d->action_edit_delete_row->setWhatsThis(xi18n("Deletes the current record.")); d->action_edit_clear_table = createSharedAction(xi18n("Clear Table Contents..."), KexiIconName("edit-table-clear"), QKeySequence(), "edit_clear_table"); d->action_edit_clear_table->setToolTip(xi18n("Clear table contents")); d->action_edit_clear_table->setWhatsThis(xi18n("Clears table contents.")); setActionVolatile(d->action_edit_clear_table, true); d->action_edit_edititem = createSharedAction(xi18n("Edit Item"), QString(), QKeySequence(), /* CONFLICT in TV: Qt::Key_F2, */ "edit_edititem"); d->action_edit_edititem->setToolTip(xi18n("Edit currently selected item")); d->action_edit_edititem->setWhatsThis(xi18n("Edits currently selected item.")); d->action_edit_insert_empty_row = createSharedAction(xi18n("&Insert Empty Row"), KexiIconName("edit-table-insert-row"), QKeySequence(Qt::SHIFT | Qt::CTRL | Qt::Key_Insert), "edit_insert_empty_row"); setActionVolatile(d->action_edit_insert_empty_row, true); d->action_edit_insert_empty_row->setToolTip(xi18n("Insert one empty row above")); d->action_edit_insert_empty_row->setWhatsThis( xi18n("Inserts one empty row above currently selected table row.")); //VIEW MENU /* UNUSED, see KexiToggleViewModeAction if (!d->userMode) { d->action_view_mode = new QActionGroup(this); ac->addAction( "view_data_mode", d->action_view_data_mode = new KToggleAction( KexiIcon("data-view"), xi18n("&Data View"), d->action_view_mode) ); // d->action_view_data_mode->setObjectName("view_data_mode"); d->action_view_data_mode->setShortcut(QKeySequence("F6")); //d->action_view_data_mode->setExclusiveGroup("view_mode"); d->action_view_data_mode->setToolTip(xi18n("Switch to data view")); d->action_view_data_mode->setWhatsThis(xi18n("Switches to data view.")); d->actions_for_view_modes.insert( Kexi::DataViewMode, d->action_view_data_mode ); connect(d->action_view_data_mode, SIGNAL(triggered()), this, SLOT(slotViewDataMode())); } else { d->action_view_mode = 0; d->action_view_data_mode = 0; } if (!d->userMode) { ac->addAction( "view_design_mode", d->action_view_design_mode = new KToggleAction( KexiIcon("design-view"), xi18n("D&esign View"), d->action_view_mode) ); // d->action_view_design_mode->setObjectName("view_design_mode"); d->action_view_design_mode->setShortcut(QKeySequence("F7")); //d->action_view_design_mode->setExclusiveGroup("view_mode"); d->action_view_design_mode->setToolTip(xi18n("Switch to design view")); d->action_view_design_mode->setWhatsThis(xi18n("Switches to design view.")); d->actions_for_view_modes.insert( Kexi::DesignViewMode, d->action_view_design_mode ); connect(d->action_view_design_mode, SIGNAL(triggered()), this, SLOT(slotViewDesignMode())); } else d->action_view_design_mode = 0; if (!d->userMode) { ac->addAction( "view_text_mode", d->action_view_text_mode = new KToggleAction( KexiIcon("sql-view"), xi18n("&Text View"), d->action_view_mode) ); d->action_view_text_mode->setObjectName("view_text_mode"); d->action_view_text_mode->setShortcut(QKeySequence("F8")); //d->action_view_text_mode->setExclusiveGroup("view_mode"); d->action_view_text_mode->setToolTip(xi18n("Switch to text view")); d->action_view_text_mode->setWhatsThis(xi18n("Switches to text view.")); d->actions_for_view_modes.insert( Kexi::TextViewMode, d->action_view_text_mode ); connect(d->action_view_text_mode, SIGNAL(triggered()), this, SLOT(slotViewTextMode())); } else d->action_view_text_mode = 0; */ if (d->isProjectNavigatorVisible) { d->action_show_nav = addAction("view_navigator", xi18n("Show Project Navigator"), "Alt+0"); d->action_show_nav->setToolTip(xi18n("Show the Project Navigator pane")); d->action_show_nav->setWhatsThis(xi18n("Shows the Project Navigator pane.")); connect(d->action_show_nav, SIGNAL(triggered()), this, SLOT(slotShowNavigator())); } else { d->action_show_nav = 0; } if (d->isProjectNavigatorVisible) { // Shortcut taken from "Activate Projects pane" http://doc.qt.io/qtcreator/creator-keyboard-shortcuts.html d->action_activate_nav = addAction("activate_navigator", xi18n("Activate Project Navigator"), "Alt+X"); d->action_activate_nav->setToolTip(xi18n("Activate the Project Navigator pane")); d->action_activate_nav->setWhatsThis(xi18n("Activates the Project Navigator pane. If it is hidden, shows it first.")); connect(d->action_activate_nav, SIGNAL(triggered()), this, SLOT(slotActivateNavigator())); } else { d->action_activate_nav = 0; } d->action_activate_mainarea = addAction("activate_mainarea", xi18n("Activate main area") // , "Alt+2"? //! @todo activate_mainarea: pressing Esc in project nav or propeditor should move back to the main area ); d->action_activate_mainarea->setToolTip(xi18n("Activate the main area")); d->action_activate_mainarea->setWhatsThis(xi18n("Activates the main area.")); connect(d->action_activate_mainarea, SIGNAL(triggered()), this, SLOT(slotActivateMainArea())); //! @todo windows with "_3" prefix have conflicting auto shortcut set to Alt+3 -> remove that! if (!d->userMode) { d->action_show_propeditor = addAction("view_propeditor", xi18n("Show Property Editor"), "Alt+3"); d->action_show_propeditor->setToolTip(xi18n("Show the Property Editor pane")); d->action_show_propeditor->setWhatsThis(xi18n("Shows the Property Editor pane.")); connect(d->action_show_propeditor, SIGNAL(triggered()), this, SLOT(slotShowPropertyEditor())); } else { d->action_show_propeditor = 0; } if (!d->userMode) { d->action_activate_propeditor = addAction("activate_propeditor", xi18n("Activate Property Editor"), "Alt+-"); d->action_activate_propeditor->setToolTip(xi18n("Activate the Property Editor pane")); d->action_activate_propeditor->setWhatsThis(xi18n("Activates the Property Editor pane. If it is hidden, shows it first.")); connect(d->action_activate_propeditor, SIGNAL(triggered()), this, SLOT(slotActivatePropertyEditor())); } else { d->action_activate_propeditor = 0; } d->action_view_global_search = addAction("view_global_search", xi18n("Switch to Global Search"), "Ctrl+K"); d->action_view_global_search->setToolTip(xi18n("Switch to Global Search box")); d->action_view_global_search->setWhatsThis(xi18n("Switches to Global Search box.")); // (connection is added elsewhere) //DATA MENU d->action_data_save_row = createSharedAction(xi18n("&Save Record"), koIconName("dialog-ok"), QKeySequence(Qt::SHIFT + Qt::Key_Return), "data_save_row"); d->action_data_save_row->setToolTip(xi18n("Save changes made to the current record")); d->action_data_save_row->setWhatsThis(xi18n("Saves changes made to the current record.")); //temp. disable because of problems with volatile actions setActionVolatile( d->action_data_save_row, true ); d->action_data_cancel_row_changes = createSharedAction(xi18n("&Cancel Record Changes"), koIconName("dialog-cancel"), QKeySequence(Qt::Key_Escape), "data_cancel_row_changes"); d->action_data_cancel_row_changes->setToolTip( xi18n("Cancel changes made to the current record")); d->action_data_cancel_row_changes->setWhatsThis( xi18n("Cancels changes made to the current record.")); //temp. disable because of problems with volatile actions setActionVolatile( d->action_data_cancel_row_changes, true ); d->action_data_execute = createSharedAction( xi18n("&Execute"), koIconName("media-playback-start"), QKeySequence(), "data_execute"); //! @todo d->action_data_execute->setToolTip(xi18n("")); //! @todo d->action_data_execute->setWhatsThis(xi18n("")); #ifdef KEXI_SHOW_UNIMPLEMENTED action = createSharedAction(futureI18n("&Filter"), koIconName("view-filter"), QKeySequence(), "data_filter"); setActionVolatile(action, true); #endif //! @todo action->setToolTip(xi18n("")); //! @todo action->setWhatsThis(xi18n("")); // - record-navigation related actions createSharedAction(KexiRecordNavigator::Actions::moveToFirstRecord(), QKeySequence(), "data_go_to_first_record"); createSharedAction(KexiRecordNavigator::Actions::moveToPreviousRecord(), QKeySequence(), "data_go_to_previous_record"); createSharedAction(KexiRecordNavigator::Actions::moveToNextRecord(), QKeySequence(), "data_go_to_next_record"); createSharedAction(KexiRecordNavigator::Actions::moveToLastRecord(), QKeySequence(), "data_go_to_last_record"); createSharedAction(KexiRecordNavigator::Actions::moveToNewRecord(), QKeySequence(), "data_go_to_new_record"); //FORMAT MENU d->action_format_font = createSharedAction(xi18n("&Font..."), koIconName("fonts-package"), QKeySequence(), "format_font"); d->action_format_font->setToolTip(xi18n("Change font for selected object")); d->action_format_font->setWhatsThis(xi18n("Changes font for selected object.")); //TOOLS MENU //WINDOW MENU //additional 'Window' menu items d->action_window_next = addAction("window_next", xi18n("&Next Window"), #ifdef Q_OS_WIN "Ctrl+Tab" #else "Alt+Right" #endif ); d->action_window_next->setToolTip(xi18n("Next window")); d->action_window_next->setWhatsThis(xi18n("Switches to the next window.")); connect(d->action_window_next, SIGNAL(triggered()), this, SLOT(activateNextWindow())); d->action_window_previous = addAction("window_previous", xi18n("&Previous Window"), #ifdef Q_OS_WIN "Ctrl+Shift+Tab" #else "Alt+Left" #endif ); d->action_window_previous->setToolTip(xi18n("Previous window")); d->action_window_previous->setWhatsThis(xi18n("Switches to the previous window.")); connect(d->action_window_previous, SIGNAL(triggered()), this, SLOT(activatePreviousWindow())); d->action_window_fullscreen = KStandardAction::fullScreen(this, SLOT(toggleFullScreen(bool)), this, ac); ac->addAction("full_screen", d->action_window_fullscreen); QList shortcuts; shortcuts << d->action_window_fullscreen->shortcut() << QKeySequence("F11"); d->action_window_fullscreen->setShortcuts(shortcuts); QShortcut *s = new QShortcut(d->action_window_fullscreen->shortcut(), this); connect(s, SIGNAL(activated()), d->action_window_fullscreen, SLOT(trigger())); if (d->action_window_fullscreen->shortcuts().count() > 1) { QShortcut *sa = new QShortcut(d->action_window_fullscreen->shortcuts().value(1), this); connect(sa, SIGNAL(activated()), d->action_window_fullscreen, SLOT(trigger())); } //SETTINGS MENU //! @todo put 'configure keys' into settings view #ifdef KEXI_SHOW_UNIMPLEMENTED //! @todo toolbars configuration will be handled in a special way #endif #ifdef KEXI_MACROS_SUPPORT Kexi::tempShowMacros() = true; #else Kexi::tempShowMacros() = false; #endif #ifdef KEXI_SCRIPTS_SUPPORT Kexi::tempShowScripts() = true; #else Kexi::tempShowScripts() = false; #endif #ifdef KEXI_SHOW_UNIMPLEMENTED //! @todo implement settings window in a specific way ac->addAction("settings", action = d->action_settings = new KexiMenuWidgetAction( KStandardAction::Preferences, this)); action->setObjectName("settings"); action->setText(futureI18n("Settings...")); action->setToolTip(futureI18n("Show Kexi settings")); action->setWhatsThis(futureI18n("Shows Kexi settings.")); connect(action, SIGNAL(triggered()), this, SLOT(slotSettings())); setupMainMenuActionShortcut(action); #else d->action_settings = d->dummy_action; #endif //! @todo reenable 'tip of the day' later #if 0 KStandardAction::tipOfDay(this, SLOT(slotTipOfTheDayAction()), actionCollection()) ->setWhatsThis(xi18n("This shows useful tips on the use of this application.")); #endif // GLOBAL d->action_show_help_menu = addAction("help_show_menu", xi18nc("Help Menu", "Help"), "Alt+H"); d->action_show_help_menu->setToolTip(xi18n("Show Help menu")); d->action_show_help_menu->setWhatsThis(xi18n("Shows Help menu.")); // (connection is added elsewhere) // ----- declare action categories, so form's "assign action to button" // (and macros in the future) will be able to recognize category // of actions and filter them ----------------------------------- //! @todo shouldn't we move this to core? Kexi::ActionCategories *acat = Kexi::actionCategories(); acat->addAction("data_execute", Kexi::PartItemActionCategory); //! @todo unused for now acat->addWindowAction("data_filter", KexiPart::TableObjectType, KexiPart::QueryObjectType, KexiPart::FormObjectType); acat->addWindowAction("data_save_row", KexiPart::TableObjectType, KexiPart::QueryObjectType, KexiPart::FormObjectType); acat->addWindowAction("data_cancel_row_changes", KexiPart::TableObjectType, KexiPart::QueryObjectType, KexiPart::FormObjectType); acat->addWindowAction("delete_table_row", KexiPart::TableObjectType, KexiPart::QueryObjectType, KexiPart::FormObjectType); //! @todo support this in KexiPart::FormObjectType as well acat->addWindowAction("data_sort_az", KexiPart::TableObjectType, KexiPart::QueryObjectType); //! @todo support this in KexiPart::FormObjectType as well acat->addWindowAction("data_sort_za", KexiPart::TableObjectType, KexiPart::QueryObjectType); //! @todo support this in KexiPart::FormObjectType as well acat->addWindowAction("edit_clear_table", KexiPart::TableObjectType, KexiPart::QueryObjectType); //! @todo support this in KexiPart::FormObjectType as well acat->addWindowAction("edit_copy_special_data_table", KexiPart::TableObjectType, KexiPart::QueryObjectType); //! @todo support this in FormObjectType as well acat->addWindowAction("project_export_data_table", KexiPart::TableObjectType, KexiPart::QueryObjectType); // GlobalActions, etc. acat->addAction("edit_copy", Kexi::GlobalActionCategory | Kexi::PartItemActionCategory); acat->addAction("edit_cut", Kexi::GlobalActionCategory | Kexi::PartItemActionCategory); acat->addAction("edit_paste", Kexi::GlobalActionCategory | Kexi::PartItemActionCategory); acat->addAction("edit_delete", Kexi::GlobalActionCategory | Kexi::PartItemActionCategory | Kexi::WindowActionCategory, KexiPart::TableObjectType, KexiPart::QueryObjectType, KexiPart::FormObjectType); acat->addAction("edit_delete_row", Kexi::GlobalActionCategory | Kexi::WindowActionCategory, KexiPart::TableObjectType, KexiPart::QueryObjectType, KexiPart::FormObjectType); acat->addAction("edit_edititem", Kexi::PartItemActionCategory | Kexi::WindowActionCategory, KexiPart::TableObjectType, KexiPart::QueryObjectType); acat->addAction("edit_find", Kexi::GlobalActionCategory | Kexi::WindowActionCategory, KexiPart::TableObjectType, KexiPart::QueryObjectType, KexiPart::FormObjectType); acat->addAction("edit_findnext", Kexi::GlobalActionCategory | Kexi::WindowActionCategory, KexiPart::TableObjectType, KexiPart::QueryObjectType, KexiPart::FormObjectType); acat->addAction("edit_findprevious", Kexi::GlobalActionCategory | Kexi::WindowActionCategory, KexiPart::TableObjectType, KexiPart::QueryObjectType, KexiPart::FormObjectType); acat->addAction("edit_replace", Kexi::GlobalActionCategory | Kexi::WindowActionCategory, KexiPart::TableObjectType, KexiPart::QueryObjectType, KexiPart::FormObjectType); acat->addAction("edit_paste_special_data_table", Kexi::GlobalActionCategory); acat->addAction("help_about_app", Kexi::GlobalActionCategory); acat->addAction("help_about_kde", Kexi::GlobalActionCategory); acat->addAction("help_contents", Kexi::GlobalActionCategory); acat->addAction("help_report_bug", Kexi::GlobalActionCategory); acat->addAction("help_whats_this", Kexi::GlobalActionCategory); acat->addAction("help_donate", Kexi::GlobalActionCategory); // disabled for now acat->addAction("switch_application_language", Kexi::GlobalActionCategory); acat->addAction("options_configure_keybinding", Kexi::GlobalActionCategory); acat->addAction("project_close", Kexi::GlobalActionCategory); acat->addAction("project_import_data_table", Kexi::GlobalActionCategory); acat->addAction("project_new", Kexi::GlobalActionCategory); acat->addAction("project_open", Kexi::GlobalActionCategory); #ifdef KEXI_QUICK_PRINTING_SUPPORT //! @todo support this in FormObjectType, ReportObjectType as well as others acat->addAction("project_print", Kexi::WindowActionCategory, KexiPart::TableObjectType, KexiPart::QueryObjectType); //! @todo support this in FormObjectType, ReportObjectType as well as others acat->addAction("project_print_preview", Kexi::WindowActionCategory, KexiPart::TableObjectType, KexiPart::QueryObjectType); //! @todo support this in FormObjectType, ReportObjectType as well as others acat->addAction("project_print_setup", Kexi::WindowActionCategory, KexiPart::TableObjectType, KexiPart::QueryObjectType); #endif acat->addAction("quit", Kexi::GlobalActionCategory); acat->addAction("tools_compact_database", Kexi::GlobalActionCategory); acat->addAction("tools_import_project", Kexi::GlobalActionCategory); acat->addAction("tools_import_tables", Kexi::GlobalActionCategory); acat->addAction("view_data_mode", Kexi::GlobalActionCategory); acat->addAction("view_design_mode", Kexi::GlobalActionCategory); acat->addAction("view_text_mode", Kexi::GlobalActionCategory); acat->addAction("view_mainarea", Kexi::GlobalActionCategory); acat->addAction("view_navigator", Kexi::GlobalActionCategory); acat->addAction("activate_navigator", Kexi::GlobalActionCategory); acat->addAction("view_propeditor", Kexi::GlobalActionCategory); acat->addAction("activate_mainarea", Kexi::GlobalActionCategory); acat->addAction("activate_propeditor", Kexi::GlobalActionCategory); acat->addAction("window_close", Kexi::GlobalActionCategory | Kexi::WindowActionCategory); acat->setAllObjectTypesSupported("window_close", true); acat->addAction("window_next", Kexi::GlobalActionCategory); acat->addAction("window_previous", Kexi::GlobalActionCategory); acat->addAction("full_screen", Kexi::GlobalActionCategory); //skipped - design view only acat->addAction("format_font", Kexi::NoActionCategory); acat->addAction("project_save", Kexi::NoActionCategory); acat->addAction("edit_insert_empty_row", Kexi::NoActionCategory); //! @todo support this in KexiPart::TableObjectType, KexiPart::QueryObjectType later acat->addAction("edit_select_all", Kexi::NoActionCategory); //! @todo support this in KexiPart::TableObjectType, KexiPart::QueryObjectType, KexiPart::FormObjectType later acat->addAction("edit_redo", Kexi::NoActionCategory); //! @todo support this in KexiPart::TableObjectType, KexiPart::QueryObjectType, KexiPart::FormObjectType later acat->addAction("edit_undo", Kexi::NoActionCategory); //record-navigation related actions acat->addAction("data_go_to_first_record", Kexi::WindowActionCategory, KexiPart::TableObjectType, KexiPart::QueryObjectType, KexiPart::FormObjectType); acat->addAction("data_go_to_previous_record", Kexi::WindowActionCategory, KexiPart::TableObjectType, KexiPart::QueryObjectType, KexiPart::FormObjectType); acat->addAction("data_go_to_next_record", Kexi::WindowActionCategory, KexiPart::TableObjectType, KexiPart::QueryObjectType, KexiPart::FormObjectType); acat->addAction("data_go_to_last_record", Kexi::WindowActionCategory, KexiPart::TableObjectType, KexiPart::QueryObjectType, KexiPart::FormObjectType); acat->addAction("data_go_to_new_record", Kexi::WindowActionCategory, KexiPart::TableObjectType, KexiPart::QueryObjectType, KexiPart::FormObjectType); //skipped - internal: acat->addAction("tablepart_create", Kexi::NoActionCategory); acat->addAction("querypart_create", Kexi::NoActionCategory); acat->addAction("formpart_create", Kexi::NoActionCategory); acat->addAction("reportpart_create", Kexi::NoActionCategory); acat->addAction("macropart_create", Kexi::NoActionCategory); acat->addAction("scriptpart_create", Kexi::NoActionCategory); } void KexiMainWindow::invalidateActions() { invalidateProjectWideActions(); invalidateSharedActions(); } void KexiMainWindow::invalidateSharedActions(QObject *o) { //! @todo enabling is more complex... /* d->action_edit_cut->setEnabled(true); d->action_edit_copy->setEnabled(true); d->action_edit_paste->setEnabled(true);*/ if (!o) o = focusWindow(); KexiSharedActionHost::invalidateSharedActions(o); } void KexiMainWindow::invalidateSharedActions() { invalidateSharedActions(0); } // unused, I think void KexiMainWindow::invalidateSharedActionsLater() { QTimer::singleShot(1, this, SLOT(invalidateSharedActions())); } void KexiMainWindow::invalidateProjectWideActions() { const bool has_window = currentWindow(); const bool window_dirty = currentWindow() && currentWindow()->isDirty(); const bool readOnly = d->prj && d->prj->dbConnection() && d->prj->dbConnection()->options()->isReadOnly(); //PROJECT MENU d->action_save->setEnabled(has_window && window_dirty && !readOnly); d->action_save_as->setEnabled(has_window && !readOnly); d->action_project_properties->setEnabled(d->prj); d->action_close->setEnabled(d->prj); d->action_project_relations->setEnabled(d->prj); //DATA MENU if (d->action_project_import_data_table) d->action_project_import_data_table->setEnabled(d->prj && !readOnly); if (d->action_tools_data_import) d->action_tools_data_import->setEnabled(d->prj && !readOnly); d->action_project_export_data_table->setEnabled( currentWindow() && currentWindow()->part()->info()->isDataExportSupported()); if (d->action_edit_paste_special_data_table) d->action_edit_paste_special_data_table->setEnabled(d->prj && !readOnly); #ifdef KEXI_QUICK_PRINTING_SUPPORT const bool printingActionsEnabled = currentWindow() && currentWindow()->part()->info()->isPrintingSupported() && !currentWindow()->neverSaved(); d->action_project_print->setEnabled(printingActionsEnabled); d->action_project_print_preview->setEnabled(printingActionsEnabled); d->action_project_print_setup->setEnabled(printingActionsEnabled); #endif //EDIT MENU //! @todo "copy special" is currently enabled only for data view mode; //! what about allowing it to enable in design view for "kexi/table" ? if (currentWindow() && currentWindow()->currentViewMode() == Kexi::DataViewMode) { KexiPart::Info *activePartInfo = currentWindow()->part()->info(); d->action_edit_copy_special_data_table->setEnabled( activePartInfo ? activePartInfo->isDataExportSupported() : false); } else { d->action_edit_copy_special_data_table->setEnabled(false); } d->action_edit_find->setEnabled(d->prj); //VIEW MENU if (d->action_show_nav) d->action_show_nav->setEnabled(d->prj); d->action_activate_mainarea->setEnabled(d->prj); if (d->action_show_propeditor) d->action_show_propeditor->setEnabled(d->prj); #ifdef KEXI_SHOW_CONTEXT_HELP d->action_show_helper->setEnabled(d->prj); #endif //CREATE MENU if (d->tabbedToolBar && d->tabbedToolBar->createWidgetToolBar()) d->tabbedToolBar->createWidgetToolBar()->setEnabled(d->prj); // DATA MENU //TOOLS MENU // "compact db" supported if there's no db or the current db supports compacting and is opened r/w: d->action_tools_compact_database->setEnabled( !d->prj || (!readOnly && d->prj && d->prj->dbConnection() && (d->prj->dbConnection()->driver()->features() & KDbDriver::CompactingDatabaseSupported)) ); //DOCKS if (d->navigator) { d->navigator->setEnabled(d->prj); } if (d->propEditor) d->propEditorTabWidget->setEnabled(d->prj); } tristate KexiMainWindow::startup() { tristate result = true; switch (KexiStartupHandler::global()->action()) { case KexiStartupHandler::CreateBlankProject: d->updatePropEditorVisibility(Kexi::NoViewMode); break; #ifdef KEXI_PROJECT_TEMPLATES case KexiStartupHandler::CreateFromTemplate: result = createProjectFromTemplate(*KexiStartupHandler::global()->projectData()); break; #endif case KexiStartupHandler::OpenProject: result = openProject(*KexiStartupHandler::global()->projectData()); break; case KexiStartupHandler::ImportProject: result = showProjectMigrationWizard( KexiStartupHandler::global()->importActionData().mimeType, KexiStartupHandler::global()->importActionData().fileName ); break; case KexiStartupHandler::ShowWelcomeScreen: //! @todo show welcome screen as soon as is available QTimer::singleShot(100, this, SLOT(slotProjectWelcome())); break; default: d->updatePropEditorVisibility(Kexi::NoViewMode); } return result; } static QString internalReason(const KDbResult &result) { const QString msg = result.message(); if (msg.isEmpty()) { return QString(); } return xi18n("
(reason: %1)", msg); } tristate KexiMainWindow::openProject(const KexiProjectData& projectData) { //qDebug() << projectData; QScopedPointer prj(createKexiProjectObject(projectData)); if (~KexiDBPasswordDialog::getPasswordIfNeeded(prj->data()->connectionData(), this)) { return cancelled; } bool incompatibleWithKexi; tristate res = prj->open(&incompatibleWithKexi); if (prj->data()->connectionData()->isPasswordNeeded()) { // password was supplied in this session, and shouldn't be stored or reused afterwards, // so let's remove it prj->data()->connectionData()->setPassword(QString()); } if (~res) { return cancelled; } else if (!res) { if (incompatibleWithKexi) { if (KMessageBox::Yes == KMessageBox::questionYesNo(this, xi18nc("@info (don't add tags around %1, it's done already)", "Database project %1 does not appear to have been created using Kexi." "Do you want to import it as a new Kexi project?", projectData.infoString()), QString(), KGuiItem(xi18nc("@action:button Import Database", "&Import..."), KexiIconName("database-import")), KStandardGuiItem::cancel())) { const bool anotherProjectAlreadyOpened = prj; tristate res = showProjectMigrationWizard("application/x-kexi-connectiondata", projectData.databaseName(), *projectData.connectionData()); if (!anotherProjectAlreadyOpened) //the project could have been opened within this instance return res; //always return cancelled because even if migration succeeded, new Kexi instance //will be started if user wanted to open the imported db return cancelled; } return cancelled; } return false; } // success d->prj = prj.take(); setupProjectNavigator(); d->prj->data()->setLastOpened(QDateTime::currentDateTime()); Kexi::recentProjects()->addProjectData(*d->prj->data()); updateReadOnlyState(); invalidateActions(); setMessagesEnabled(false); QTimer::singleShot(1, this, SLOT(slotAutoOpenObjectsLater())); if (d->tabbedToolBar) { d->tabbedToolBar->showTab("create");// not needed since create toolbar already shows toolbar! move when kexi starts d->tabbedToolBar->showTab("data"); d->tabbedToolBar->showTab("external"); d->tabbedToolBar->showTab("tools"); d->tabbedToolBar->hideTab("form");//temporalily until createToolbar is split d->tabbedToolBar->hideTab("report");//temporalily until createToolbar is split // make sure any tab is activated d->tabbedToolBar->setCurrentTab(0); } return true; } tristate KexiMainWindow::openProject(const KexiProjectData& data, const QString& shortcutPath, bool *opened) { if (!shortcutPath.isEmpty() && d->prj) { const tristate result = openProjectInExternalKexiInstance( shortcutPath, QString(), QString()); if (result == true) { *opened = true; } return result; } return openProject(data); } tristate KexiMainWindow::createProjectFromTemplate(const KexiProjectData& projectData) { Q_UNUSED(projectData); #ifdef KEXI_PROJECT_TEMPLATES QStringList mimetypes; mimetypes.append(KDb::defaultFileBasedDriverMimeType()); QString fname; //! @todo KEXI3 add equivalent of kfiledialog:/// const QString startDir("kfiledialog:///OpenExistingOrCreateNewProject"/*as in KexiNewProjectWizard*/); const QString caption(xi18nc("@window:title", "Select New Project's Location")); while (true) { if (fname.isEmpty() && !projectData.connectionData()->databaseName().isEmpty()) { //propose filename from db template name fname = projectData.connectionData()->databaseName(); } const bool specialDir = fname.isEmpty(); qDebug() << fname << "............."; QFileDialog dlg(specialDir ? QUrl(startDir) : QUrl(), QString(), this); dlg.setModal(true); dlg.setMimeFilter(mimetypes); if (!specialDir) dlg.selectUrl(QUrl::fromLocalFile(fname); // may also be a filename dlg.setFileMode(QFileDialog::ExistingFile); dlg.setFileMode(QFileDialog::AcceptOpen); dlg.setWindowTitle(caption); if (QDialog::Accepted != dlg.exec()) { return cancelled; } if (dlg.selectedFiles().isEmpty() { return cancelled; } fname = dlg.selectedFiles().first(); if (fname.isEmpty()) { return cancelled; } if (KexiUtils::askForFileOverwriting(fname, this)) { break; } } QFile sourceFile(projectData.connectionData()->fileName()); if (!sourceFile.copy(fname)) { //! @todo show error from with QFile::FileError return false; } return openProject(fname, 0, QString(), projectData.autoopenObjects/*copy*/); #else return false; #endif } void KexiMainWindow::updateReadOnlyState() { const bool readOnly = d->prj && d->prj->dbConnection() && d->prj->dbConnection()->options()->isReadOnly(); //! @todo KEXI3 show read-only flag in the GUI because we have no statusbar if (d->navigator) { d->navigator->setReadOnly(readOnly); } // update "insert ....." actions for every part KexiPart::PartInfoList *plist = Kexi::partManager().infoList(); if (plist) { foreach(KexiPart::Info *info, *plist) { QAction *a = info->newObjectAction(); if (a) a->setEnabled(!readOnly); } } } void KexiMainWindow::slotAutoOpenObjectsLater() { QString not_found_msg; bool openingCancelled; //ok, now open "autoopen: objects if (d->prj) { foreach(KexiProjectData::ObjectInfo* info, d->prj->data()->autoopenObjects) { KexiPart::Info *i = Kexi::partManager().infoForPluginId(info->value("type")); if (!i) { not_found_msg += "
  • "; if (!info->value("name").isEmpty()) not_found_msg += (QString("\"") + info->value("name") + "\" - "); if (info->value("action") == "new") not_found_msg += xi18n("cannot create object - unknown object type \"%1\"", info->value("type")); else not_found_msg += xi18n("unknown object type \"%1\"", info->value("type")); not_found_msg += internalReason(Kexi::partManager().result()) + "
  • "; continue; } // * NEW if (info->value("action") == "new") { if (!newObject(i, &openingCancelled) && !openingCancelled) { not_found_msg += "
  • "; not_found_msg += (xi18n("cannot create object of type \"%1\"", info->value("type")) + internalReason(d->prj->result()) + "
  • "); } else d->wasAutoOpen = true; continue; } KexiPart::Item *item = d->prj->item(i, info->value("name")); if (!item) { QString taskName; if (info->value("action") == "execute") taskName = xi18nc("\"executing object\" action", "executing"); #ifdef KEXI_QUICK_PRINTING_SUPPORT else if (info->value("action") == "print-preview") taskName = futureI18n("making print preview for"); else if (info->value("action") == "print") taskName = futureI18n("printing"); #endif else taskName = xi18n("opening"); not_found_msg += (QString("
  • ") + taskName + " \"" + info->value("name") + "\" - "); if ("table" == info->value("type").toLower()) not_found_msg += xi18n("table not found"); else if ("query" == info->value("type").toLower()) not_found_msg += xi18n("query not found"); else if ("macro" == info->value("type").toLower()) not_found_msg += xi18n("macro not found"); else if ("script" == info->value("type").toLower()) not_found_msg += xi18n("script not found"); else not_found_msg += xi18n("object not found"); not_found_msg += (internalReason(d->prj->result()) + "
  • "); continue; } // * EXECUTE, PRINT, PRINT PREVIEW if (info->value("action") == "execute") { tristate res = executeItem(item); if (false == res) { not_found_msg += (QString("
  • \"") + info->value("name") + "\" - " + xi18n("cannot execute object") + internalReason(d->prj->result()) + "
  • "); } continue; } #ifdef KEXI_QUICK_PRINTING_SUPPORT else if (info->value("action") == "print") { tristate res = printItem(item); if (false == res) { not_found_msg += (QString("
  • \"") + info->value("name") + "\" - " + futureI18n("cannot print object") + internalReason(d->prj->result()) + "
  • "); } continue; } else if (info->value("action") == "print-preview") { tristate res = printPreviewForItem(item); if (false == res) { not_found_msg += (QString("
  • \"") + info->value("name") + "\" - " + futureI18n("cannot make print preview of object") + internalReason(d->prj->result()) + "
  • "); } continue; } #endif Kexi::ViewMode viewMode; if (info->value("action") == "open") viewMode = Kexi::DataViewMode; else if (info->value("action") == "design") viewMode = Kexi::DesignViewMode; else if (info->value("action") == "edittext") viewMode = Kexi::TextViewMode; else continue; //sanity QString openObjectMessage; if (!openObject(item, viewMode, &openingCancelled, 0, &openObjectMessage) && (!openingCancelled || !openObjectMessage.isEmpty())) { not_found_msg += (QString("
  • \"") + info->value("name") + "\" - "); if (openObjectMessage.isEmpty()) not_found_msg += xi18n("cannot open object"); else not_found_msg += openObjectMessage; not_found_msg += internalReason(d->prj->result()) + "
  • "; continue; } else { d->wasAutoOpen = true; } } } setMessagesEnabled(true); if (!not_found_msg.isEmpty()) showErrorMessage(xi18n("You have requested selected objects to be automatically opened " "or processed on startup. Several objects cannot be opened or processed."), QString("
      %1
    ").arg(not_found_msg)); d->updatePropEditorVisibility(currentWindow() ? currentWindow()->currentViewMode() : Kexi::NoViewMode); #if defined(KDOCKWIDGET_P) if (d->propEditor) { KDockWidget *dw = (KDockWidget *)d->propEditorTabWidget->parentWidget(); KDockSplitter *ds = (KDockSplitter *)dw->parentWidget(); if (ds) ds->setSeparatorPosInPercent(d->config->readEntry("RightDockPosition", 80/* % */)); } #endif updateAppCaption(); if (d->tabbedToolBar) { d->tabbedToolBar->hideMainMenu(); } qApp->processEvents(); emit projectOpened(); } tristate KexiMainWindow::closeProject() { if (d->tabbedToolBar) d->tabbedToolBar->hideMainMenu(); #ifndef KEXI_NO_PENDING_DIALOGS if (d->pendingWindowsExist()) { qDebug() << "pendingWindowsExist..."; d->actionToExecuteWhenPendingJobsAreFinished = Private::CloseProjectAction; return cancelled; } #endif //only save nav. visibility setting if there is project opened d->saveSettingsForShowProjectNavigator = d->prj && d->isProjectNavigatorVisible; if (!d->prj) return true; { // make sure the project can be closed bool cancel = false; emit acceptProjectClosingRequested(&cancel); if (cancel) return cancelled; } d->windowExistedBeforeCloseProject = currentWindow(); #if defined(KDOCKWIDGET_P) //remember docks position - will be used on storeSettings() if (d->propEditor) { KDockWidget *dw = (KDockWidget *)d->propEditorTabWidget->parentWidget(); KDockSplitter *ds = (KDockSplitter *)dw->parentWidget(); if (ds) d->propEditorDockSeparatorPos = ds->separatorPosInPercent(); } if (d->nav) { if (d->propEditor) { //! @todo KEXI3 if (d->openedWindowsCount() == 0) //! @todo KEXI3 makeWidgetDockVisible(d->propEditorTabWidget); KDockWidget *dw = (KDockWidget *)d->propEditorTabWidget->parentWidget(); KDockSplitter *ds = (KDockSplitter *)dw->parentWidget(); if (ds) ds->setSeparatorPosInPercent(80); } KDockWidget *dw = (KDockWidget *)d->nav->parentWidget(); KDockSplitter *ds = (KDockSplitter *)dw->parentWidget(); int dwWidth = dw->width(); if (ds) { if (d->openedWindowsCount() != 0 && d->propEditorTabWidget && d->propEditorTabWidget->isVisible()) { d->navDockSeparatorPos = ds->separatorPosInPercent(); } else { d->navDockSeparatorPos = (100 * dwWidth) / width(); } } } #endif //close each window, optionally asking if user wants to close (if data changed) while (currentWindow()) { tristate res = closeWindow(currentWindow()); if (!res || ~res) return res; } // now we will close for sure emit beforeProjectClosing(); if (!d->prj->closeConnection()) return false; if (d->navigator) { d->navWasVisibleBeforeProjectClosing = d->navDockWidget->isVisible(); d->navDockWidget->hide(); d->navigator->setProject(0); slotProjectNavigatorVisibilityChanged(true); // hide side tab } if (d->propEditorDockWidget) d->propEditorDockWidget->hide(); d->clearWindows(); //sanity! delete d->prj; d->prj = 0; updateReadOnlyState(); invalidateActions(); updateAppCaption(); emit projectClosed(); return true; } void KexiMainWindow::setupContextHelp() { #ifdef KEXI_SHOW_CONTEXT_HELP d->ctxHelp = new KexiContextHelp(d->mainWidget, this); //! @todo /* d->ctxHelp->setContextHelp(xi18n("Welcome"),xi18n("The KEXI team wishes you a lot of productive work, " "with this product.


    If you have found a bug or have a feature request, please don't " "hesitate to report it at our issue " "tracking system .


    If you would like to join our effort, the development documentation " "at www.kexi-project.org is a good starting point."),0); */ addToolWindow(d->ctxHelp, KDockWidget::DockBottom | KDockWidget::DockLeft, getMainDockWidget(), 20); #endif } void KexiMainWindow::setupMainWidget() { QVBoxLayout *vlyr = new QVBoxLayout(this); vlyr->setContentsMargins(0, 0, 0, 0); vlyr->setSpacing(0); if (d->isMainMenuVisible) { QWidget *tabbedToolBarContainer = new QWidget(this); vlyr->addWidget(tabbedToolBarContainer); QVBoxLayout *tabbedToolBarContainerLyr = new QVBoxLayout(tabbedToolBarContainer); tabbedToolBarContainerLyr->setSpacing(0); tabbedToolBarContainerLyr->setContentsMargins( KexiUtils::marginHint() / 2, KexiUtils::marginHint() / 2, KexiUtils::marginHint() / 2, KexiUtils::marginHint() / 2); d->tabbedToolBar = new KexiTabbedToolBar(tabbedToolBarContainer); Q_ASSERT(d->action_view_global_search); connect(d->action_view_global_search, SIGNAL(triggered()), d->tabbedToolBar, SLOT(activateSearchLineEdit())); tabbedToolBarContainerLyr->addWidget(d->tabbedToolBar); d->tabbedToolBar->hideTab("form"); //temporarily until createToolbar is split d->tabbedToolBar->hideTab("report"); //temporarily until createToolbar is split } else { d->tabbedToolBar = 0; } QWidget *mainWidgetContainer = new QWidget(); vlyr->addWidget(mainWidgetContainer, 1); QHBoxLayout *mainWidgetContainerLyr = new QHBoxLayout(mainWidgetContainer); mainWidgetContainerLyr->setContentsMargins(0, 0, 0, 0); mainWidgetContainerLyr->setSpacing(0); KMultiTabBar *mtbar = new KMultiTabBar(KMultiTabBar::Left); mtbar->setStyle(KMultiTabBar::VSNET); mainWidgetContainerLyr->addWidget(mtbar); d->multiTabBars.insert(mtbar->position(), mtbar); d->mainWidget = new KexiMainWidget(); d->mainWidget->setParent(this); d->mainWidget->tabWidget()->setTabsClosable(true); connect(d->mainWidget->tabWidget(), SIGNAL(tabCloseRequested(int)), this, SLOT(closeWindowForTab(int))); mainWidgetContainerLyr->addWidget(d->mainWidget, 1); mtbar = new KMultiTabBar(KMultiTabBar::Right); mtbar->setStyle(KMultiTabBar::VSNET); mainWidgetContainerLyr->addWidget(mtbar); d->multiTabBars.insert(mtbar->position(), mtbar); } void KexiMainWindow::slotSetProjectNavigatorVisible(bool set) { if (d->navDockWidget) d->navDockWidget->setVisible(set); } void KexiMainWindow::slotSetPropertyEditorVisible(bool set) { if (d->propEditorDockWidget) d->propEditorDockWidget->setVisible(set); } void KexiMainWindow::slotProjectNavigatorVisibilityChanged(bool visible) { d->setTabBarVisible(KMultiTabBar::Left, PROJECT_NAVIGATOR_TABBAR_ID, d->navDockWidget, !visible); } void KexiMainWindow::slotPropertyEditorVisibilityChanged(bool visible) { if (!d->enable_slotPropertyEditorVisibilityChanged) return; d->setPropertyEditorTabBarVisible(!visible); if (!visible) d->propertyEditorCollapsed = true; } void KexiMainWindow::slotMultiTabBarTabClicked(int id) { if (id == PROJECT_NAVIGATOR_TABBAR_ID) { slotProjectNavigatorVisibilityChanged(true); d->navDockWidget->show(); } else if (id == PROPERTY_EDITOR_TABBAR_ID) { slotPropertyEditorVisibilityChanged(true); d->propEditorDockWidget->show(); d->propertyEditorCollapsed = false; } } static Qt::DockWidgetArea applyRightToLeftToDockArea(Qt::DockWidgetArea area) { if (QApplication::layoutDirection() == Qt::RightToLeft) { if (area == Qt::LeftDockWidgetArea) { return Qt::RightDockWidgetArea; } else if (area == Qt::RightDockWidgetArea) { return Qt::LeftDockWidgetArea; } } return area; } void KexiMainWindow::setupProjectNavigator() { if (!d->isProjectNavigatorVisible) return; if (d->navigator) { d->navDockWidget->show(); } else { KexiDockableWidget* navDockableWidget = new KexiDockableWidget; d->navigator = new KexiProjectNavigator(navDockableWidget); kexiTester() << KexiTestObject(d->navigator, "KexiProjectNavigator"); navDockableWidget->setWidget(d->navigator); d->navDockWidget = new KexiDockWidget(d->navigator->windowTitle(), d->mainWidget); d->navDockWidget->setObjectName("ProjectNavigatorDockWidget"); d->mainWidget->addDockWidget( applyRightToLeftToDockArea(Qt::LeftDockWidgetArea), d->navDockWidget, Qt::Vertical); navDockableWidget->setParent(d->navDockWidget); d->navDockWidget->setWidget(navDockableWidget); KConfigGroup mainWindowGroup(d->config->group("MainWindow")); const QSize projectNavigatorSize = mainWindowGroup.readEntry("ProjectNavigatorSize", QSize()); if (!projectNavigatorSize.isNull()) { navDockableWidget->setSizeHint(projectNavigatorSize); } connect(d->navDockWidget, SIGNAL(visibilityChanged(bool)), this, SLOT(slotProjectNavigatorVisibilityChanged(bool))); //Nav2 Signals connect(d->navigator, SIGNAL(openItem(KexiPart::Item*,Kexi::ViewMode)), this, SLOT(openObject(KexiPart::Item*,Kexi::ViewMode))); connect(d->navigator, SIGNAL(openOrActivateItem(KexiPart::Item*,Kexi::ViewMode)), this, SLOT(openObjectFromNavigator(KexiPart::Item*,Kexi::ViewMode))); connect(d->navigator, SIGNAL(newItem(KexiPart::Info*)), this, SLOT(newObject(KexiPart::Info*))); connect(d->navigator, SIGNAL(removeItem(KexiPart::Item*)), this, SLOT(removeObject(KexiPart::Item*))); connect(d->navigator->model(), SIGNAL(renameItem(KexiPart::Item*,QString,bool*)), this, SLOT(renameObject(KexiPart::Item*,QString,bool*))); connect(d->navigator->model(), SIGNAL(changeItemCaption(KexiPart::Item*,QString,bool*)), this, SLOT(setObjectCaption(KexiPart::Item*,QString,bool*))); connect(d->navigator, SIGNAL(executeItem(KexiPart::Item*)), this, SLOT(executeItem(KexiPart::Item*))); connect(d->navigator, SIGNAL(exportItemToClipboardAsDataTable(KexiPart::Item*)), this, SLOT(copyItemToClipboardAsDataTable(KexiPart::Item*))); connect(d->navigator, SIGNAL(exportItemToFileAsDataTable(KexiPart::Item*)), this, SLOT(exportItemAsDataTable(KexiPart::Item*))); #ifdef KEXI_QUICK_PRINTING_SUPPORT connect(d->navigator, SIGNAL(printItem(KexiPart::Item*)), this, SLOT(printItem(KexiPart::Item*))); connect(d->navigator, SIGNAL(pageSetupForItem(KexiPart::Item*)), this, SLOT(showPageSetupForItem(KexiPart::Item*))); #endif connect(d->navigator, SIGNAL(selectionChanged(KexiPart::Item*)), this, SLOT(slotPartItemSelectedInNavigator(KexiPart::Item*))); } if (d->prj->isConnected()) { QString partManagerErrorMessages; if (!partManagerErrorMessages.isEmpty()) { showWarningContinueMessage(partManagerErrorMessages, QString(), "ShowWarningsRelatedToPluginsLoading"); } d->navigator->setProject(d->prj, QString()/*all classes*/, &partManagerErrorMessages); } connect(d->prj, SIGNAL(newItemStored(KexiPart::Item*)), d->navigator->model(), SLOT(slotAddItem(KexiPart::Item*))); connect(d->prj, SIGNAL(itemRemoved(KexiPart::Item)), d->navigator->model(), SLOT(slotRemoveItem(KexiPart::Item))); d->navigator->setFocus(); if (d->forceShowProjectNavigatorOnCreation) { slotShowNavigator(); d->forceShowProjectNavigatorOnCreation = false; } else if (d->forceHideProjectNavigatorOnCreation) { d->forceHideProjectNavigatorOnCreation = false; } invalidateActions(); } void KexiMainWindow::slotLastActions() { } void KexiMainWindow::setupPropertyEditor() { if (!d->propEditor) { KConfigGroup mainWindowGroup(d->config->group("MainWindow")); //! @todo FIX LAYOUT PROBLEMS d->propEditorDockWidget = new KexiDockWidget(xi18n("Property Editor"), d->mainWidget); d->propEditorDockWidget->setObjectName("PropertyEditorDockWidget"); d->mainWidget->addDockWidget( applyRightToLeftToDockArea(Qt::RightDockWidgetArea), d->propEditorDockWidget, Qt::Vertical ); connect(d->propEditorDockWidget, SIGNAL(visibilityChanged(bool)), this, SLOT(slotPropertyEditorVisibilityChanged(bool))); d->propEditorDockableWidget = new KexiDockableWidget(d->propEditorDockWidget); d->propEditorDockWidget->setWidget(d->propEditorDockableWidget); const QSize propertyEditorSize = mainWindowGroup.readEntry("PropertyEditorSize", QSize()); if (!propertyEditorSize.isNull()) { d->propEditorDockableWidget->setSizeHint(propertyEditorSize); } QWidget *propEditorDockWidgetContents = new QWidget(d->propEditorDockableWidget); d->propEditorDockableWidget->setWidget(propEditorDockWidgetContents); QVBoxLayout *propEditorDockWidgetContentsLyr = new QVBoxLayout(propEditorDockWidgetContents); propEditorDockWidgetContentsLyr->setContentsMargins(0, 0, 0, 0); d->propEditorTabWidget = new QTabWidget(propEditorDockWidgetContents); d->propEditorTabWidget->setDocumentMode(true); propEditorDockWidgetContentsLyr->addWidget(d->propEditorTabWidget); d->propEditor = new KexiPropertyEditorView(d->propEditorTabWidget); d->propEditorTabWidget->setWindowTitle(d->propEditor->windowTitle()); d->propEditorTabWidget->addTab(d->propEditor, xi18n("Properties")); //! @todo REMOVE? d->propEditor->installEventFilter(this); KConfigGroup propertyEditorGroup(d->config->group("PropertyEditor")); QFont f(KexiUtils::smallestReadableFont()); const qreal pointSizeF = propertyEditorGroup.readEntry("FontPointSize", -1.0f); // points are more accurate if (pointSizeF > 0.0) { f.setPointSizeF(pointSizeF); } else { const int pixelSize = propertyEditorGroup.readEntry("FontSize", -1); // compatibility with Kexi 2.x if (pixelSize > 0) { f.setPixelSize(pixelSize); } } d->propEditorTabWidget->setFont(f); d->enable_slotPropertyEditorVisibilityChanged = false; d->propEditorDockWidget->setVisible(false); d->enable_slotPropertyEditorVisibilityChanged = true; } } void KexiMainWindow::slotPartLoaded(KexiPart::Part* p) { if (!p) return; p->createGUIClients(); } void KexiMainWindow::updateAppCaption() { //! @todo allow to set custom "static" app caption d->appCaptionPrefix.clear(); if (d->prj && d->prj->data()) {//add project name d->appCaptionPrefix = d->prj->data()->caption(); if (d->appCaptionPrefix.isEmpty()) { d->appCaptionPrefix = d->prj->data()->databaseName(); } if (d->prj->dbConnection()->options()->isReadOnly()) { d->appCaptionPrefix = xi18nc(" (read only)", "%1 (read only)", d->appCaptionPrefix); } } setWindowTitle(d->appCaptionPrefix); } bool KexiMainWindow::queryClose() { #ifndef KEXI_NO_PENDING_DIALOGS if (d->pendingWindowsExist()) { qDebug() << "pendingWindowsExist..."; d->actionToExecuteWhenPendingJobsAreFinished = Private::QuitAction; return false; } #endif const tristate res = closeProject(); if (~res) return false; if (res == true) storeSettings(); if (! ~res) { Kexi::deleteGlobalObjects(); qApp->quit(); } return ! ~res; } void KexiMainWindow::closeEvent(QCloseEvent *ev) { d->mainWidget->closeEvent(ev); } static const QSize KEXI_MIN_WINDOW_SIZE(1024, 768); void KexiMainWindow::restoreSettings() { KConfigGroup mainWindowGroup(d->config->group("MainWindow")); const bool maximize = mainWindowGroup.readEntry("Maximized", false); const QRect geometry(mainWindowGroup.readEntry("Geometry", QRect())); if (geometry.isValid()) setGeometry(geometry); else if (maximize) setWindowState(windowState() | Qt::WindowMaximized); else { QRect desk = QApplication::desktop()->screenGeometry( QApplication::desktop()->screenNumber(this)); if (desk.width() <= KEXI_MIN_WINDOW_SIZE.width() || desk.height() <= KEXI_MIN_WINDOW_SIZE.height()) { setWindowState(windowState() | Qt::WindowMaximized); } else { resize(KEXI_MIN_WINDOW_SIZE); } } // Saved settings } void KexiMainWindow::storeSettings() { //qDebug(); KConfigGroup mainWindowGroup(d->config->group("MainWindow")); if (isMaximized()) { mainWindowGroup.writeEntry("Maximized", true); mainWindowGroup.deleteEntry("Geometry"); } else { mainWindowGroup.deleteEntry("Maximized"); mainWindowGroup.writeEntry("Geometry", geometry()); } if (d->navigator) mainWindowGroup.writeEntry("ProjectNavigatorSize", d->navigator->parentWidget()->size()); if (d->propEditorDockableWidget) mainWindowGroup.writeEntry("PropertyEditorSize", d->propEditorDockableWidget->size()); d->config->sync(); } void KexiMainWindow::registerChild(KexiWindow *window) { //qDebug(); connect(window, SIGNAL(dirtyChanged(KexiWindow*)), this, SLOT(slotDirtyFlagChanged(KexiWindow*))); if (window->id() != -1) { d->insertWindow(window); } //qDebug() << "ID=" << window->id(); } void KexiMainWindow::updateCustomPropertyPanelTabs(KexiWindow *prevWindow, Kexi::ViewMode prevViewMode) { updateCustomPropertyPanelTabs( prevWindow ? prevWindow->part() : 0, prevWindow ? prevWindow->currentViewMode() : prevViewMode, currentWindow() ? currentWindow()->part() : 0, currentWindow() ? currentWindow()->currentViewMode() : Kexi::NoViewMode ); } void KexiMainWindow::updateCustomPropertyPanelTabs( KexiPart::Part *prevWindowPart, Kexi::ViewMode prevViewMode, KexiPart::Part *curWindowPart, Kexi::ViewMode curViewMode) { if (!d->propEditorTabWidget) return; if ( !curWindowPart || (/*prevWindowPart &&*/ curWindowPart && (prevWindowPart != curWindowPart || prevViewMode != curViewMode) ) ) { if (d->partForPreviouslySetupPropertyPanelTabs) { //remember current page number for this part if (( prevViewMode == Kexi::DesignViewMode && static_cast(d->partForPreviouslySetupPropertyPanelTabs) != curWindowPart) //part changed || curViewMode != Kexi::DesignViewMode) { //..or switching to other view mode d->recentlySelectedPropertyPanelPages.insert( d->partForPreviouslySetupPropertyPanelTabs, d->propEditorTabWidget->currentIndex()); } } //delete old custom tabs (other than 'property' tab) const int count = d->propEditorTabWidget->count(); for (int i = 1; i < count; i++) d->propEditorTabWidget->removeTab(1); } //don't change anything if part is not switched nor view mode changed if ((!prevWindowPart && !curWindowPart) || (prevWindowPart == curWindowPart && prevViewMode == curViewMode) || (curWindowPart && curViewMode != Kexi::DesignViewMode)) { //new part for 'previously setup tabs' d->partForPreviouslySetupPropertyPanelTabs = curWindowPart; return; } if (curWindowPart) { //recreate custom tabs curWindowPart->setupCustomPropertyPanelTabs(d->propEditorTabWidget); //restore current page number for this part if (d->recentlySelectedPropertyPanelPages.contains(curWindowPart)) { d->propEditorTabWidget->setCurrentIndex( d->recentlySelectedPropertyPanelPages[ curWindowPart ] ); } } //new part for 'previously setup tabs' d->partForPreviouslySetupPropertyPanelTabs = curWindowPart; } void KexiMainWindow::activeWindowChanged(KexiWindow *window, KexiWindow *prevWindow) { //qDebug() << "to=" << (window ? window->windowTitle() : ""); bool windowChanged = prevWindow != window; if (windowChanged) { if (prevWindow) { //inform previously activated dialog about deactivation prevWindow->deactivate(); } } updateCustomPropertyPanelTabs(prevWindow, prevWindow ? prevWindow->currentViewMode() : Kexi::NoViewMode); // inform the current view of the new dialog about property switching // (this will also call KexiMainWindow::propertySetSwitched() to update the current property editor's set if (windowChanged && currentWindow()) currentWindow()->selectedView()->propertySetSwitched(); if (windowChanged) { if (currentWindow() && currentWindow()->currentViewMode() != 0 && window) { //on opening new dialog it can be 0; we don't want this d->updatePropEditorVisibility(currentWindow()->currentViewMode()); restoreDesignTabIfNeeded(window->partItem()->pluginId(), window->currentViewMode(), prevWindow ? prevWindow->partItem()->identifier() : 0); activateDesignTabIfNeeded(window->partItem()->pluginId(), window->currentViewMode()); } } invalidateActions(); d->updateFindDialogContents(); if (window) window->setFocus(); } bool KexiMainWindow::activateWindow(int id) { qDebug(); #ifndef KEXI_NO_PENDING_DIALOGS Private::PendingJobType pendingType; return activateWindow(*d->openedWindowFor(id, pendingType)); #else return activateWindow(*d->openedWindowFor(id)); #endif } bool KexiMainWindow::activateWindow(KexiWindow& window) { //qDebug(); d->focus_before_popup = &window; d->mainWidget->tabWidget()->setCurrentWidget(window.parentWidget()/*container*/); window.activate(); return true; } void KexiMainWindow::activateNextWindow() { //! @todo activateNextWindow() } void KexiMainWindow::activatePreviousWindow() { //! @todo activatePreviousWindow() } void KexiMainWindow::slotSettings() { if (d->tabbedToolBar) { d->tabbedToolBar->showMainMenu("settings"); // dummy QLabel *dummy = KEXI_UNFINISHED_LABEL(actionCollection()->action("settings")->text()); d->tabbedToolBar->setMainMenuContent(dummy); } } void KexiMainWindow::slotConfigureKeys() { KShortcutsDialog::configure(actionCollection(), KShortcutsEditor::LetterShortcutsDisallowed, this); } void KexiMainWindow::slotConfigureToolbars() { KEditToolBar edit(actionCollection()); (void) edit.exec(); } void KexiMainWindow::slotProjectNew() { createNewProject(); } KexiProject* KexiMainWindow::createKexiProjectObject(const KexiProjectData &data) { KexiProject *prj = new KexiProject(data, this); connect(prj, SIGNAL(itemRenamed(KexiPart::Item,QString)), this, SLOT(slotObjectRenamed(KexiPart::Item,QString))); if (d->navigator){ connect(prj, SIGNAL(itemRemoved(KexiPart::Item)), d->navigator->model(), SLOT(slotRemoveItem(KexiPart::Item))); } return prj; } void KexiMainWindow::createNewProject() { if (!d->tabbedToolBar) return; d->tabbedToolBar->showMainMenu("project_new"); KexiNewProjectAssistant* assistant = new KexiNewProjectAssistant; connect(assistant, SIGNAL(createProject(KexiProjectData)), this, SLOT(createNewProject(KexiProjectData))); d->tabbedToolBar->setMainMenuContent(assistant); } tristate KexiMainWindow::createNewProject(const KexiProjectData &projectData) { QScopedPointer prj(createKexiProjectObject(projectData)); tristate res = prj->create(true /*overwrite*/); if (res != true) { return res; } //qDebug() << "new project created ---"; if (d->prj) { res = openProjectInExternalKexiInstance( prj->data()->connectionData()->databaseName(), prj->data()->connectionData(), prj->data()->databaseName()); Kexi::recentProjects()->addProjectData(*prj->data()); if (d->tabbedToolBar) { d->tabbedToolBar->hideMainMenu(); } return res; } if (d->tabbedToolBar) { d->tabbedToolBar->hideMainMenu(); } d->prj = prj.take(); setupProjectNavigator(); d->prj->data()->setLastOpened(QDateTime::currentDateTime()); Kexi::recentProjects()->addProjectData(*d->prj->data()); invalidateActions(); updateAppCaption(); return true; } void KexiMainWindow::slotProjectOpen() { if (!d->tabbedToolBar) return; d->tabbedToolBar->showMainMenu("project_open"); KexiOpenProjectAssistant* assistant = new KexiOpenProjectAssistant; connect(assistant, SIGNAL(openProject(KexiProjectData)), this, SLOT(openProject(KexiProjectData))); connect(assistant, SIGNAL(openProject(QString)), this, SLOT(openProject(QString))); d->tabbedToolBar->setMainMenuContent(assistant); } tristate KexiMainWindow::openProject(const QString& aFileName) { return openProject(aFileName, QString(), QString()); } tristate KexiMainWindow::openProject(const QString& aFileName, const QString& fileNameForConnectionData, const QString& dbName) { if (d->prj) return openProjectInExternalKexiInstance(aFileName, fileNameForConnectionData, dbName); KDbConnectionData *cdata = 0; if (!fileNameForConnectionData.isEmpty()) { cdata = Kexi::connset().connectionDataForFileName(fileNameForConnectionData); if (!cdata) { qWarning() << "cdata?"; return false; } } return openProject(aFileName, cdata, dbName); } tristate KexiMainWindow::openProject(const QString& aFileName, KDbConnectionData *cdata, const QString& dbName, const KexiProjectData::AutoOpenObjects& autoopenObjects) { if (d->prj) { return openProjectInExternalKexiInstance(aFileName, cdata, dbName); } KexiProjectData* projectData = 0; const KexiStartupHandler *h = KexiStartupHandler::global(); bool readOnly = h->isSet(h->options().readOnly); bool deleteAfterOpen = false; if (cdata) { //server-based project if (dbName.isEmpty()) {//no database name given, ask user bool cancel; projectData = KexiStartupHandler::global()->selectProject(cdata, &cancel, this); if (cancel) return cancelled; } else { //! @todo caption arg? projectData = new KexiProjectData(*cdata, dbName); deleteAfterOpen = true; } } else { if (aFileName.isEmpty()) { qWarning() << "aFileName.isEmpty()"; return false; } //file-based project qDebug() << "Project File: " << aFileName; KDbConnectionData fileConnData; fileConnData.setDatabaseName(aFileName); QString detectedDriverId; int detectOptions = 0; if (readOnly) { detectOptions |= KexiStartupHandler::OpenReadOnly; } KexiStartupData::Import importActionData; bool forceReadOnly; const tristate res = KexiStartupHandler::detectActionForFile( &importActionData, &detectedDriverId, fileConnData.driverId(), aFileName, this, detectOptions, &forceReadOnly); if (forceReadOnly) { readOnly = true; } if (true != res) return res; if (importActionData) { //importing requested return showProjectMigrationWizard(importActionData.mimeType, importActionData.fileName); } fileConnData.setDriverId(detectedDriverId); if (fileConnData.driverId().isEmpty()) return false; //opening requested projectData = new KexiProjectData(fileConnData); deleteAfterOpen = true; } if (!projectData) return false; projectData->setReadOnly(readOnly); projectData->autoopenObjects = autoopenObjects; const tristate res = openProject(*projectData); if (deleteAfterOpen) //projectData object has been copied delete projectData; return res; } tristate KexiMainWindow::openProjectInExternalKexiInstance(const QString& aFileName, KDbConnectionData *cdata, const QString& dbName) { QString fileNameForConnectionData; if (aFileName.isEmpty()) { //try .kexic file if (cdata) fileNameForConnectionData = Kexi::connset().fileNameForConnectionData(*cdata); } return openProjectInExternalKexiInstance(aFileName, fileNameForConnectionData, dbName); } tristate KexiMainWindow::openProjectInExternalKexiInstance(const QString& aFileName, const QString& fileNameForConnectionData, const QString& dbName) { QString fileName(aFileName); QStringList args; // open a file-based project or a server connection provided as a .kexic file // (we have no other simple way to provide the startup data to a new process) if (fileName.isEmpty()) { //try .kexic file if (!fileNameForConnectionData.isEmpty()) args << "--skip-conn-dialog"; //user does not expect conn. dialog to be shown here if (dbName.isEmpty()) { //use 'kexi --skip-conn-dialog file.kexic' fileName = fileNameForConnectionData; } else { //use 'kexi --skip-conn-dialog --connection file.kexic dbName' if (fileNameForConnectionData.isEmpty()) { qWarning() << "fileNameForConnectionData?"; return false; } args << "--connection" << fileNameForConnectionData; fileName = dbName; } } if (fileName.isEmpty()) { qWarning() << "fileName?"; return false; } //! @todo use KRun //! @todo untested //Can arguments be supplied to KRun like is used here? AP args << fileName; const bool ok = QProcess::startDetached( qApp->applicationFilePath(), args, QFileInfo(fileName).absoluteDir().absolutePath()); if (!ok) { d->showStartProcessMsg(args); } if (d->tabbedToolBar) { d->tabbedToolBar->hideMainMenu(); } return ok; } void KexiMainWindow::slotProjectWelcome() { if (!d->tabbedToolBar) return; d->tabbedToolBar->showMainMenu("project_welcome"); KexiWelcomeAssistant* assistant = new KexiWelcomeAssistant( Kexi::recentProjects(), this); connect(assistant, SIGNAL(openProject(KexiProjectData,QString,bool*)), this, SLOT(openProject(KexiProjectData,QString,bool*))); d->tabbedToolBar->setMainMenuContent(assistant); } void KexiMainWindow::slotProjectSave() { if (!currentWindow() || currentWindow()->currentViewMode() == Kexi::DataViewMode) { return; } saveObject(currentWindow()); updateAppCaption(); invalidateActions(); } void KexiMainWindow::slotProjectSaveAs() { if (!currentWindow() || currentWindow()->currentViewMode() == Kexi::DataViewMode) { return; } saveObject(currentWindow(), QString(), SaveObjectAs); updateAppCaption(); invalidateActions(); } void KexiMainWindow::slotProjectPrint() { #ifdef KEXI_QUICK_PRINTING_SUPPORT if (currentWindow() && currentWindow()->partItem()) printItem(currentWindow()->partItem()); #endif } void KexiMainWindow::slotProjectPrintPreview() { #ifdef KEXI_QUICK_PRINTING_SUPPORT if (currentWindow() && currentWindow()->partItem()) printPreviewForItem(currentWindow()->partItem()); #endif } void KexiMainWindow::slotProjectPageSetup() { #ifdef KEXI_QUICK_PRINTING_SUPPORT if (currentWindow() && currentWindow()->partItem()) showPageSetupForItem(currentWindow()->partItem()); #endif } void KexiMainWindow::slotProjectExportDataTable() { if (currentWindow() && currentWindow()->partItem()) exportItemAsDataTable(currentWindow()->partItem()); } void KexiMainWindow::slotProjectProperties() { if (!d->tabbedToolBar) return; d->tabbedToolBar->showMainMenu("project_properties"); // dummy QLabel *dummy = KEXI_UNFINISHED_LABEL(actionCollection()->action("project_properties")->text()); d->tabbedToolBar->setMainMenuContent(dummy); //! @todo load the implementation not the ui :) // ProjectSettingsUI u(this); // u.exec(); } void KexiMainWindow::slotProjectImportExportOrSend() { if (!d->tabbedToolBar) return; d->tabbedToolBar->showMainMenu("project_import_export_send"); KexiImportExportAssistant* assistant = new KexiImportExportAssistant( d->action_project_import_export_send, d->action_tools_import_project); connect(assistant, SIGNAL(importProject()), this, SLOT(slotToolsImportProject())); d->tabbedToolBar->setMainMenuContent(assistant); } void KexiMainWindow::slotProjectClose() { closeProject(); } void KexiMainWindow::slotProjectRelations() { if (!d->prj) return; KexiWindow *w = KexiInternalPart::createKexiWindowInstance("org.kexi-project.relations", this); activateWindow(*w); } void KexiMainWindow::slotImportFile() { KEXI_UNFINISHED("Import: " + xi18n("From File...")); } void KexiMainWindow::slotImportServer() { KEXI_UNFINISHED("Import: " + xi18n("From Server...")); } void KexiMainWindow::slotProjectQuit() { if (~ closeProject()) return; close(); } void KexiMainWindow::slotActivateNavigator() { if (!d->navigator) { return; } d->navigator->setFocus(); } void KexiMainWindow::slotActivateMainArea() { if (currentWindow()) currentWindow()->setFocus(); } void KexiMainWindow::slotActivatePropertyEditor() { if (!d->propEditor) { return; } if (d->propEditorTabWidget->currentWidget()) d->propEditorTabWidget->currentWidget()->setFocus(); } void KexiMainWindow::slotShowNavigator() { if (d->navDockWidget) d->navDockWidget->setVisible(!d->navDockWidget->isVisible()); } void KexiMainWindow::slotShowPropertyEditor() { if (d->propEditorDockWidget) d->propEditorDockWidget->setVisible(!d->propEditorDockWidget->isVisible()); } tristate KexiMainWindow::switchToViewMode(KexiWindow& window, Kexi::ViewMode viewMode) { const Kexi::ViewMode prevViewMode = currentWindow()->currentViewMode(); if (prevViewMode == viewMode) return true; if (!activateWindow(window)) return false; if (!currentWindow()) { return false; } if (&window != currentWindow()) return false; if (!currentWindow()->supportsViewMode(viewMode)) { showErrorMessage(xi18nc("@info", "Selected view is not supported for %1 object.", currentWindow()->partItem()->name()), xi18nc("@info", "Selected view (%1) is not supported by this object type (%2).", Kexi::nameForViewMode(viewMode), currentWindow()->part()->info()->name())); return false; } updateCustomPropertyPanelTabs(currentWindow()->part(), prevViewMode, currentWindow()->part(), viewMode); tristate res = currentWindow()->switchToViewMode(viewMode); if (!res) { updateCustomPropertyPanelTabs(0, Kexi::NoViewMode); //revert showErrorMessage(xi18n("Switching to other view failed (%1).", Kexi::nameForViewMode(viewMode)), currentWindow()); return false; } if (~res) { updateCustomPropertyPanelTabs(0, Kexi::NoViewMode); //revert return cancelled; } activateWindow(window); invalidateSharedActions(); invalidateProjectWideActions(); d->updateFindDialogContents(); d->updatePropEditorVisibility(viewMode); QString origTabToActivate; if (viewMode == Kexi::DesignViewMode) { // Save the orig tab: we want to back to design tab // when user moved to data view and then immediately to design view. origTabToActivate = d->tabsToActivateOnShow.value(currentWindow()->partItem()->identifier()); } restoreDesignTabIfNeeded(currentWindow()->partItem()->pluginId(), viewMode, currentWindow()->partItem()->identifier()); if (viewMode == Kexi::DesignViewMode) { activateDesignTab(currentWindow()->partItem()->pluginId()); // Restore the saved tab to the orig one. restoreDesignTabIfNeeded() saved tools tab probably. d->tabsToActivateOnShow.insert(currentWindow()->partItem()->identifier(), origTabToActivate); } return true; } void KexiMainWindow::slotViewDataMode() { if (currentWindow()) switchToViewMode(*currentWindow(), Kexi::DataViewMode); } void KexiMainWindow::slotViewDesignMode() { if (currentWindow()) switchToViewMode(*currentWindow(), Kexi::DesignViewMode); } void KexiMainWindow::slotViewTextMode() { if (currentWindow()) switchToViewMode(*currentWindow(), Kexi::TextViewMode); } //! Used to control if we're not Saving-As object under the original name class SaveAsObjectNameValidator : public KexiNameDialogValidator { public: SaveAsObjectNameValidator(const QString &originalObjectName) : m_originalObjectName(originalObjectName) { } virtual bool validate(KexiNameDialog *dialog) const { if (dialog->widget()->nameText() == m_originalObjectName) { KMessageBox::information(dialog, xi18nc("Could not save object under the original name.", "Could not save under the original name.")); return false; } return true; } private: QString m_originalObjectName; }; tristate KexiMainWindow::getNewObjectInfo( KexiPart::Item *partItem, const QString &originalName, KexiPart::Part *part, bool allowOverwriting, bool *overwriteNeeded, const QString& messageWhenAskingForName) { //data was never saved in the past -we need to create a new object at the backend KexiPart::Info *info = part->info(); if (!d->nameDialog) { d->nameDialog = new KexiNameDialog( messageWhenAskingForName, this); //check if that name is allowed d->nameDialog->widget()->addNameSubvalidator( new KDbObjectNameValidator(project()->dbConnection()->driver())); d->nameDialog->buttonBox()->button(QDialogButtonBox::Ok)->setText(xi18nc("@action:button Save object", "Save")); } else { d->nameDialog->widget()->setMessageText(messageWhenAskingForName); } d->nameDialog->widget()->setCaptionText(partItem->caption()); d->nameDialog->widget()->setNameText(partItem->name()); d->nameDialog->setWindowTitle(xi18nc("@title:window", "Save Object As")); d->nameDialog->setDialogIcon(info->iconName()); d->nameDialog->setAllowOverwriting(allowOverwriting); if (!originalName.isEmpty()) { d->nameDialog->setValidator(new SaveAsObjectNameValidator(originalName)); } if (d->nameDialog->execAndCheckIfObjectExists(*project(), *part, overwriteNeeded) != QDialog::Accepted) { return cancelled; } // close window of object that will be overwritten if (*overwriteNeeded) { KexiPart::Item* overwrittenItem = project()->item(info, d->nameDialog->widget()->nameText()); if (overwrittenItem) { KexiWindow * openedWindow = d->openedWindowFor(overwrittenItem->identifier()); if (openedWindow) { const tristate res = closeWindow(openedWindow); if (res != true) { return res; } } } } //update name and caption partItem->setName(d->nameDialog->widget()->nameText()); partItem->setCaption(d->nameDialog->widget()->captionText()); return true; } //! Used to delete part item on exit from block class PartItemDeleter : public QScopedPointer { public: explicit PartItemDeleter(KexiProject *prj) : m_prj(prj) {} ~PartItemDeleter() { if (!isNull()) { m_prj->deleteUnstoredItem(take()); } } private: KexiProject *m_prj; }; static void showSavingObjectFailedErrorMessage(KexiMainWindow *wnd, KexiPart::Item *item) { wnd->showErrorMessage( xi18nc("@info Saving object failed", "Saving %1 object failed.", item->name()), wnd->currentWindow()); } tristate KexiMainWindow::saveObject(KexiWindow *window, const QString& messageWhenAskingForName, SaveObjectOptions options) { tristate res; bool saveAs = options & SaveObjectAs; if (!saveAs && !window->neverSaved()) { //data was saved in the past -just save again res = window->storeData(options & DoNotAsk); if (!res) { showSavingObjectFailedErrorMessage(this, window->partItem()); } return res; } if (saveAs && window->neverSaved()) { //if never saved, saveAs == save saveAs = false; } const int oldItemID = window->partItem()->identifier(); KexiPart::Item *partItem; KexiView::StoreNewDataOptions storeNewDataOptions; PartItemDeleter itemDeleter(d->prj); if (saveAs) { partItem = d->prj->createPartItem(window->part()); if (!partItem) { //! @todo error return false; } itemDeleter.reset(partItem); } else { partItem = window->partItem(); } bool overwriteNeeded; res = getNewObjectInfo(partItem, saveAs ? window->partItem()->name() : QString(), window->part(), true /*allowOverwriting*/, &overwriteNeeded, messageWhenAskingForName); if (res != true) return res; if (overwriteNeeded) { storeNewDataOptions |= KexiView::OverwriteExistingData; } if (saveAs) { res = window->storeDataAs(partItem, storeNewDataOptions); } else { res = window->storeNewData(storeNewDataOptions); } if (~res) return cancelled; if (!res) { showSavingObjectFailedErrorMessage(this, partItem); return false; } d->updateWindowId(window, oldItemID); invalidateProjectWideActions(); itemDeleter.take(); return true; } tristate KexiMainWindow::closeWindow(KexiWindow *window) { return closeWindow(window ? window : currentWindow(), true); } tristate KexiMainWindow::closeCurrentWindow() { return closeWindow(0); } tristate KexiMainWindow::closeWindowForTab(int tabIndex) { KexiWindow* window = windowForTab(tabIndex); if (!window) return false; return closeWindow(window); } tristate KexiMainWindow::closeWindow(KexiWindow *window, bool layoutTaskBar, bool doNotSaveChanges) { //! @todo KEXI3 KexiMainWindow::closeWindow() ///@note Q_UNUSED layoutTaskBar Q_UNUSED(layoutTaskBar); if (!window) return true; if (d->insideCloseWindow) return true; const int previousItemId = window->partItem()->identifier(); #ifndef KEXI_NO_PENDING_DIALOGS d->addItemToPendingWindows(window->partItem(), Private::WindowClosingJob); #endif d->insideCloseWindow = true; if (window == currentWindow() && !window->isAttached()) { if (d->propEditor) { // ah, closing detached window - better switch off property buffer right now... d->propertySet = 0; d->propEditor->editor()->changeSet(0); } } bool remove_on_closing = window->partItem() ? window->partItem()->neverSaved() : false; if (window->isDirty() && !d->forceWindowClosing && !doNotSaveChanges) { //more accurate tool tips and what's this KGuiItem saveChanges(KStandardGuiItem::save()); saveChanges.setToolTip(xi18n("Save changes")); saveChanges.setWhatsThis( xi18nc("@info", "Saves all recent changes made in %1 object.", window->partItem()->name())); KGuiItem discardChanges(KStandardGuiItem::discard()); discardChanges.setWhatsThis( xi18nc("@info", "Discards all recent changes made in %1 object.", window->partItem()->name())); //dialog's data is dirty: //--adidional message, e.g. table designer will return // "Note: This table is already filled with data which will be removed." // if the window is in design view mode. const KLocalizedString additionalMessage( window->part()->i18nMessage(":additional message before saving design", window)); QString additionalMessageString; if (!additionalMessage.isEmpty()) additionalMessageString = additionalMessage.toString(); if (additionalMessageString.startsWith(':')) additionalMessageString.clear(); if (!additionalMessageString.isEmpty()) additionalMessageString = "

    " + additionalMessageString + "

    "; const KMessageBox::ButtonCode questionRes = KMessageBox::warningYesNoCancel(this, "

    " + window->part()->i18nMessage("Design of object %1 has been modified.", window) .subs(window->partItem()->name()).toString() + "

    " + xi18n("Do you want to save changes?") + "

    " + additionalMessageString /*may be empty*/, QString(), saveChanges, discardChanges); if (questionRes == KMessageBox::Cancel) { #ifndef KEXI_NO_PENDING_DIALOGS d->removePendingWindow(window->id()); #endif d->insideCloseWindow = false; d->windowsToClose.clear(); //give up with 'close all' return cancelled; } if (questionRes == KMessageBox::Yes) { //save it tristate res = saveObject(window, QString(), DoNotAsk); if (!res || ~res) { //! @todo show error info; (retry/ignore/cancel) #ifndef KEXI_NO_PENDING_DIALOGS d->removePendingWindow(window->id()); #endif d->insideCloseWindow = false; d->windowsToClose.clear(); //give up with 'close all' return res; } remove_on_closing = false; } } const int window_id = window->id(); //remember now, because removeObject() can destruct partitem object if (remove_on_closing) { //we won't save this object, and it was never saved -remove it if (!removeObject(window->partItem(), true)) { #ifndef KEXI_NO_PENDING_DIALOGS d->removePendingWindow(window->id()); #endif //msg? //! @todo ask if we'd continue and return true/false d->insideCloseWindow = false; d->windowsToClose.clear(); //give up with 'close all' return false; } } else { //not dirty now if (d->navigator) { d->navigator->updateItemName(*window->partItem(), false); } } hideDesignTab(previousItemId, QString()); d->removeWindow(window_id); d->setWindowContainerExistsFor(window->partItem()->identifier(), false); QWidget *windowContainer = window->parentWidget(); d->mainWidget->tabWidget()->removeTab( d->mainWidget->tabWidget()->indexOf(windowContainer)); #ifdef KEXI_QUICK_PRINTING_SUPPORT //also remove from 'print setup dialogs' cache, if needed int printedObjectID = 0; if (d->pageSetupWindowItemID2dataItemID_map.contains(window_id)) printedObjectID = d->pageSetupWindowItemID2dataItemID_map[ window_id ]; d->pageSetupWindows.remove(printedObjectID); #endif delete windowContainer; //focus navigator if nothing else available if (d->openedWindowsCount() == 0) { if (d->navigator) { d->navigator->setFocus(); } d->updatePropEditorVisibility(Kexi::NoViewMode); } invalidateActions(); d->insideCloseWindow = false; if (!d->windowsToClose.isEmpty()) {//continue 'close all' KexiWindow* w = d->windowsToClose.takeAt(0); closeWindow(w, true); } #ifndef KEXI_NO_PENDING_DIALOGS d->removePendingWindow(window_id); //perform pending global action that was suspended: if (!d->pendingWindowsExist()) { d->executeActionWhenPendingJobsAreFinished(); } #endif d->mainWidget->slotCurrentTabIndexChanged(d->mainWidget->tabWidget()->currentIndex()); showDesignTabIfNeeded(0); if (currentWindow()) { restoreDesignTabIfNeeded(currentWindow()->partItem()->pluginId(), currentWindow()->currentViewMode(), 0); } d->tabsToActivateOnShow.remove(previousItemId); return true; } QWidget* KexiMainWindow::findWindow(QWidget *w) { while (w && !acceptsSharedActions(w)) { if (w == d->propEditorDockWidget) return currentWindow(); w = w->parentWidget(); } return w; } KexiWindow* KexiMainWindow::openedWindowFor(int identifier) { return d->openedWindowFor(identifier); } KexiWindow* KexiMainWindow::openedWindowFor(const KexiPart::Item* item) { return item ? openedWindowFor(item->identifier()) : 0; } KDbQuerySchema* KexiMainWindow::unsavedQuery(int queryId) { KexiWindow * queryWindow = openedWindowFor(queryId); if (!queryWindow || !queryWindow->isDirty()) { return 0; } return queryWindow->part()->currentQuery(queryWindow->viewForMode(Kexi::DataViewMode)); } QList KexiMainWindow::currentParametersForQuery(int queryId) const { KexiWindow *queryWindow = d->openedWindowFor(queryId); if (!queryWindow) { return QList(); } KexiView *view = queryWindow->viewForMode(Kexi::DataViewMode); if (!view) { return QList(); } return view->currentParameters(); } bool KexiMainWindow::acceptsSharedActions(QObject *w) { return w->inherits("KexiWindow") || w->inherits("KexiView"); } bool KexiMainWindow::openingAllowed(KexiPart::Item* item, Kexi::ViewMode viewMode, QString* errorMessage) { //qDebug() << viewMode; //! @todo this can be more complex once we deliver ACLs... if (!d->userMode) return true; KexiPart::Part * part = Kexi::partManager().partForPluginId(item->pluginId()); if (!part) { if (errorMessage) { *errorMessage = Kexi::partManager().result().message(); } } //qDebug() << part << item->pluginId(); //if (part) // qDebug() << item->pluginId() << part->info()->supportedUserViewModes(); return part && (part->info()->supportedUserViewModes() & viewMode); } KexiWindow * KexiMainWindow::openObject(const QString& pluginId, const QString& name, Kexi::ViewMode viewMode, bool *openingCancelled, QMap* staticObjectArgs) { KexiPart::Item *item = d->prj->itemForPluginId(pluginId, name); if (!item) return 0; return openObject(item, viewMode, openingCancelled, staticObjectArgs); } KexiWindow * KexiMainWindow::openObject(KexiPart::Item* item, Kexi::ViewMode viewMode, bool *openingCancelled, QMap* staticObjectArgs, QString* errorMessage) { Q_ASSERT(openingCancelled); if (!d->prj || !item) { return 0; } if (!openingAllowed(item, viewMode, errorMessage)) { if (errorMessage) *errorMessage = xi18nc( "opening is not allowed in \"data view/design view/text view\" mode", "opening is not allowed in \"%1\" mode", Kexi::nameForViewMode(viewMode)); *openingCancelled = true; return 0; } //qDebug() << d->prj << item; KexiWindow *prevWindow = currentWindow(); KexiUtils::WaitCursor wait; #ifndef KEXI_NO_PENDING_DIALOGS Private::PendingJobType pendingType; KexiWindow *window = d->openedWindowFor(item, pendingType); if (pendingType != Private::NoJob) { *openingCancelled = true; return 0; } #else KexiWindow *window = openedWindowFor(item); #endif int previousItemId = currentWindow() ? currentWindow()->partItem()->identifier() : 0; *openingCancelled = false; bool alreadyOpened = false; KexiWindowContainer *windowContainer = 0; if (window) { if (viewMode != window->currentViewMode()) { if (true != switchToViewMode(*window, viewMode)) return 0; } else activateWindow(*window); alreadyOpened = true; } else { if (d->windowContainerExistsFor(item->identifier())) { // window not yet present but window container exists: return 0 and wait return 0; } KexiPart::Part *part = Kexi::partManager().partForPluginId(item->pluginId()); d->updatePropEditorVisibility(viewMode, part ? part->info() : 0); //update tabs before opening updateCustomPropertyPanelTabs(currentWindow() ? currentWindow()->part() : 0, currentWindow() ? currentWindow()->currentViewMode() : Kexi::NoViewMode, part, viewMode); // open new tab earlier windowContainer = new KexiWindowContainer(d->mainWidget->tabWidget()); d->setWindowContainerExistsFor(item->identifier(), true); const int tabIndex = d->mainWidget->tabWidget()->addTab( windowContainer, QIcon::fromTheme(part ? part->info()->iconName() : QString()), KexiWindow::windowTitleForItem(*item)); d->mainWidget->tabWidget()->setTabToolTip(tabIndex, KexiPart::fullCaptionForItem(item, part)); QString whatsThisText; if (part) { whatsThisText = xi18nc("@info", "Tab for %1 (%2).", item->captionOrName(), part->info()->name()); } else { whatsThisText = xi18nc("@info", "Tab for %1.", item->captionOrName()); } d->mainWidget->tabWidget()->setTabWhatsThis(tabIndex, whatsThisText); d->mainWidget->tabWidget()->setCurrentWidget(windowContainer); #ifndef KEXI_NO_PENDING_DIALOGS d->addItemToPendingWindows(item, Private::WindowOpeningJob); #endif window = d->prj->openObject(windowContainer, item, viewMode, staticObjectArgs); if (window) { windowContainer->setWindow(window); // update text and icon d->mainWidget->tabWidget()->setTabText( d->mainWidget->tabWidget()->indexOf(windowContainer), window->windowTitle()); d->mainWidget->tabWidget()->setTabIcon( d->mainWidget->tabWidget()->indexOf(windowContainer), window->windowIcon()); } } if (!window || !activateWindow(*window)) { #ifndef KEXI_NO_PENDING_DIALOGS d->removePendingWindow(item->identifier()); #endif d->setWindowContainerExistsFor(item->identifier(), false); d->mainWidget->tabWidget()->removeTab( d->mainWidget->tabWidget()->indexOf(windowContainer)); delete windowContainer; updateCustomPropertyPanelTabs(0, Kexi::NoViewMode); //revert //! @todo add error msg... return 0; } if (viewMode != window->currentViewMode()) invalidateSharedActions(); #ifndef KEXI_NO_PENDING_DIALOGS d->removePendingWindow(window->id()); //perform pending global action that was suspended: if (!d->pendingWindowsExist()) { d->executeActionWhenPendingJobsAreFinished(); } #endif if (window && !alreadyOpened) { // Call switchToViewMode() and propertySetSwitched() again here because // this is the time when then new window is the current one - previous call did nothing. switchToViewMode(*window, window->currentViewMode()); currentWindow()->selectedView()->propertySetSwitched(); } invalidateProjectWideActions(); restoreDesignTabIfNeeded(item->pluginId(), viewMode, previousItemId); activateDesignTabIfNeeded(item->pluginId(), viewMode); QString origTabToActivate; if (prevWindow) { // Save the orig tab for prevWindow that was stored in the restoreDesignTabIfNeeded() call above origTabToActivate = d->tabsToActivateOnShow.value(prevWindow->partItem()->identifier()); } activeWindowChanged(window, prevWindow); if (prevWindow) { // Restore the orig tab d->tabsToActivateOnShow.insert(prevWindow->partItem()->identifier(), origTabToActivate); } return window; } KexiWindow * KexiMainWindow::openObjectFromNavigator(KexiPart::Item* item, Kexi::ViewMode viewMode) { bool openingCancelled; return openObjectFromNavigator(item, viewMode, &openingCancelled); } KexiWindow * KexiMainWindow::openObjectFromNavigator(KexiPart::Item* item, Kexi::ViewMode viewMode, bool *openingCancelled) { Q_ASSERT(openingCancelled); if (!openingAllowed(item, viewMode)) { *openingCancelled = true; return 0; } if (!d->prj || !item) return 0; #ifndef KEXI_NO_PENDING_DIALOGS Private::PendingJobType pendingType; KexiWindow *window = d->openedWindowFor(item, pendingType); if (pendingType != Private::NoJob) { *openingCancelled = true; return 0; } #else KexiWindow *window = openedWindowFor(item); #endif *openingCancelled = false; if (window) { if (activateWindow(*window)) { return window; } } //if DataViewMode is not supported, try Design, then Text mode (currently useful for script part) KexiPart::Part *part = Kexi::partManager().partForPluginId(item->pluginId()); if (!part) return 0; if (viewMode == Kexi::DataViewMode && !(part->info()->supportedViewModes() & Kexi::DataViewMode)) { if (part->info()->supportedViewModes() & Kexi::DesignViewMode) return openObjectFromNavigator(item, Kexi::DesignViewMode, openingCancelled); else if (part->info()->supportedViewModes() & Kexi::TextViewMode) return openObjectFromNavigator(item, Kexi::TextViewMode, openingCancelled); } //do the same as in openObject() return openObject(item, viewMode, openingCancelled); } tristate KexiMainWindow::closeObject(KexiPart::Item* item) { #ifndef KEXI_NO_PENDING_DIALOGS Private::PendingJobType pendingType; KexiWindow *window = d->openedWindowFor(item, pendingType); if (pendingType == Private::WindowClosingJob) return true; else if (pendingType == Private::WindowOpeningJob) return cancelled; #else KexiWindow *window = openedWindowFor(item); #endif if (!window) return cancelled; return closeWindow(window); } bool KexiMainWindow::newObject(KexiPart::Info *info, bool* openingCancelled) { Q_ASSERT(openingCancelled); if (d->userMode) { *openingCancelled = true; return false; } *openingCancelled = false; if (!d->prj || !info) return false; KexiPart::Part *part = Kexi::partManager().part(info); if (!part) return false; KexiPart::Item *it = d->prj->createPartItem(info); if (!it) { //! @todo error return false; } if (!it->neverSaved()) { //only add stored objects to the browser d->navigator->model()->slotAddItem(it); } return openObject(it, Kexi::DesignViewMode, openingCancelled); } tristate KexiMainWindow::removeObject(KexiPart::Item *item, bool dontAsk) { if (d->userMode) return cancelled; if (!d->prj || !item) return false; KexiPart::Part *part = Kexi::partManager().partForPluginId(item->pluginId()); if (!part) return false; if (!dontAsk) { if (KMessageBox::No == KMessageBox::questionYesNo(this, xi18nc("@info Remove ?", "Do you want to permanently delete the following object?" "%1 %2" "If you click Delete, " "you will not be able to undo the deletion.", part->info()->name(), item->name()), xi18nc("@title:window Delete Object %1.", "Delete %1?", item->name()), KStandardGuiItem::del(), KStandardGuiItem::no(), QString(), KMessageBox::Notify | KMessageBox::Dangerous)) { return cancelled; } } tristate res = true; #ifdef KEXI_QUICK_PRINTING_SUPPORT //also close 'print setup' dialog for this item, if any KexiWindow * pageSetupWindow = d->pageSetupWindows[ item->identifier()]; const bool oldInsideCloseWindow = d->insideCloseWindow; { d->insideCloseWindow = false; if (pageSetupWindow) res = closeWindow(pageSetupWindow); } d->insideCloseWindow = oldInsideCloseWindow; if (!res || ~res) { return res; } #endif #ifndef KEXI_NO_PENDING_DIALOGS Private::PendingJobType pendingType; KexiWindow *window = d->openedWindowFor(item, pendingType); if (pendingType != Private::NoJob) { return cancelled; } #else KexiWindow *window = openedWindowFor(item); #endif if (window) {//close existing window const bool tmp = d->forceWindowClosing; d->forceWindowClosing = true; res = closeWindow(window); d->forceWindowClosing = tmp; //restore if (!res || ~res) { return res; } } #ifdef KEXI_QUICK_PRINTING_SUPPORT //in case the dialog is a 'print setup' dialog, also update d->pageSetupWindows int dataItemID = d->pageSetupWindowItemID2dataItemID_map[item->identifier()]; d->pageSetupWindowItemID2dataItemID_map.remove(item->identifier()); d->pageSetupWindows.remove(dataItemID); #endif if (!d->prj->removeObject(item)) { //! @todo better msg showSorryMessage(xi18n("Could not remove object.")); return false; } return true; } void KexiMainWindow::renameObject(KexiPart::Item *item, const QString& _newName, bool *success) { Q_ASSERT(success); if (d->userMode) { *success = false; return; } QString newName = _newName.trimmed(); if (newName.isEmpty()) { showSorryMessage(xi18n("Could not set empty name for this object.")); *success = false; return; } KexiWindow *window = openedWindowFor(item); if (window) { QString msg = xi18nc("@info", "Before renaming object %1 it should be closed." "Do you want to close it?", item->name()); int r = KMessageBox::questionYesNo(this, msg, QString(), KStandardGuiItem::closeWindow(), KStandardGuiItem::cancel()); if (r != KMessageBox::Yes) { *success = false; return; } } setMessagesEnabled(false); //to avoid double messages const bool res = d->prj->renameObject(item, newName); setMessagesEnabled(true); if (!res) { showErrorMessage(xi18nc("@info", "Renaming object %1 failed.", newName), d->prj); *success = false; return; } } void KexiMainWindow::setObjectCaption(KexiPart::Item *item, const QString& _newCaption, bool *success) { Q_ASSERT(success); if (d->userMode) { *success = false; return; } QString newCaption = _newCaption.trimmed(); setMessagesEnabled(false); //to avoid double messages const bool res = d->prj->setObjectCaption(item, newCaption); setMessagesEnabled(true); if (!res) { showErrorMessage(xi18nc("@info", "Setting caption for object %1 failed.", newCaption), d->prj); *success = false; return; } } void KexiMainWindow::slotObjectRenamed(const KexiPart::Item &item, const QString& oldName) { Q_UNUSED(oldName); #ifndef KEXI_NO_PENDING_DIALOGS Private::PendingJobType pendingType; KexiWindow *window = d->openedWindowFor(&item, pendingType); if (pendingType != Private::NoJob) return; #else KexiWindow *window = openedWindowFor(&item); #endif if (!window) return; //change item window->updateCaption(); if (static_cast(currentWindow()) == window)//optionally, update app. caption updateAppCaption(); } void KexiMainWindow::acceptPropertySetEditing() { if (d->propEditor) d->propEditor->editor()->acceptInput(); } void KexiMainWindow::propertySetSwitched(KexiWindow *window, bool force, bool preservePrevSelection, bool sortedProperties, const QByteArray& propertyToSelect) { KexiWindow* _currentWindow = currentWindow(); //qDebug() << "currentWindow(): " // << (_currentWindow ? _currentWindow->windowTitle() : QString("NULL")) // << " window: " << (window ? window->windowTitle() : QString("NULL")); if (_currentWindow && _currentWindow != window) { d->propertySet = 0; //we'll need to move to another prop. set return; } if (d->propEditor) { KPropertySet *newSet = _currentWindow ? _currentWindow->propertySet() : 0; if (!newSet || (force || static_cast(d->propertySet) != newSet)) { d->propertySet = newSet; if (preservePrevSelection || force) { KPropertyEditorView::SetOptions options; if (preservePrevSelection) { options |= KPropertyEditorView::SetOption::PreservePreviousSelection; } if (sortedProperties) { options |= KPropertyEditorView::SetOption::AlphabeticalOrder; } if (propertyToSelect.isEmpty()) { d->propEditor->editor()->changeSet(d->propertySet, options); } else { d->propEditor->editor()->changeSet(d->propertySet, propertyToSelect, options); } } } } } void KexiMainWindow::slotDirtyFlagChanged(KexiWindow* window) { KexiPart::Item *item = window->partItem(); //update text in navigator and app. caption if (!d->userMode) { d->navigator->updateItemName(*item, window->isDirty()); } invalidateActions(); updateAppCaption(); d->mainWidget->tabWidget()->setTabText( d->mainWidget->tabWidget()->indexOf(window->parentWidget()), window->windowTitle()); } void KexiMainWindow::slotTipOfTheDay() { //! @todo } void KexiMainWindow::slotReportBug() { KexiBugReportDialog bugReport(this); bugReport.exec(); } bool KexiMainWindow::userMode() const { return d->userMode; } void KexiMainWindow::setupUserActions() { } void KexiMainWindow::slotToolsImportProject() { if (d->tabbedToolBar) d->tabbedToolBar->hideMainMenu(); showProjectMigrationWizard(QString(), QString()); } void KexiMainWindow::slotToolsImportTables() { if (project()) { QMap args; QDialog *dlg = KexiInternalPart::createModalDialogInstance("org.kexi-project.migration", "importtable", this, 0, &args); if (!dlg) return; //error msg has been shown by KexiInternalPart const int result = dlg->exec(); delete dlg; if (result != QDialog::Accepted) return; QString destinationTableName(args["destinationTableName"]); if (!destinationTableName.isEmpty()) { QString pluginId = "org.kexi-project.table"; bool openingCancelled; KexiMainWindow::openObject(pluginId, destinationTableName, Kexi::DataViewMode, &openingCancelled); } } } void KexiMainWindow::slotToolsCompactDatabase() { KexiProjectData *data = 0; KDbDriver *drv = 0; const bool projectWasOpened = d->prj; if (!d->prj) { KexiStartupDialog dlg( KexiStartupDialog::OpenExisting, 0, Kexi::connset(), this); if (dlg.exec() != QDialog::Accepted) return; if (dlg.selectedFileName().isEmpty()) { //! @todo add support for server based if needed? return; } KDbConnectionData cdata; cdata.setDatabaseName(dlg.selectedFileName()); //detect driver name for the selected file KexiStartupData::Import detectedImportAction; QString detectedDriverId; tristate res = KexiStartupHandler::detectActionForFile( &detectedImportAction, &detectedDriverId, QString() /*suggestedDriverId*/, cdata.databaseName(), 0, KexiStartupHandler::SkipMessages | KexiStartupHandler::ThisIsAProjectFile | KexiStartupHandler::DontConvert); if (true == res && !detectedImportAction) { cdata.setDriverId(detectedDriverId); drv = Kexi::driverManager().driver(cdata.driverId()); } if (!drv || !(drv->features() & KDbDriver::CompactingDatabaseSupported)) { KMessageBox::information(this, xi18n("Compacting database file %1 is not supported.", QDir::toNativeSeparators(cdata.databaseName()))); return; } data = new KexiProjectData(cdata); } else { //sanity if (!(d->prj && d->prj->dbConnection() && (d->prj->dbConnection()->driver()->features() & KDbDriver::CompactingDatabaseSupported))) return; KGuiItem yesItem(KStandardGuiItem::cont()); yesItem.setText(xi18nc("@action:button Compact database", "Compact")); if (KMessageBox::Yes != KMessageBox::questionYesNo(this, xi18n("The current project has to be closed before compacting the database. " "It will be open again after compacting.\n\nDo you want to continue?"), QString(), yesItem, KStandardGuiItem::cancel())) { return; } data = new KexiProjectData(*d->prj->data()); // a copy drv = d->prj->dbConnection()->driver(); const tristate res = closeProject(); if (~res || !res) { delete data; return; } } if (!drv->adminTools().vacuum(*data->connectionData(), data->databaseName())) { showErrorMessage(QString(), &drv->adminTools()); } if (projectWasOpened) openProject(*data); delete data; } tristate KexiMainWindow::showProjectMigrationWizard(const QString& mimeType, const QString& databaseName) { return d->showProjectMigrationWizard(mimeType, databaseName, 0); } tristate KexiMainWindow::showProjectMigrationWizard( const QString& mimeType, const QString& databaseName, const KDbConnectionData &cdata) { return d->showProjectMigrationWizard(mimeType, databaseName, &cdata); } tristate KexiMainWindow::executeItem(KexiPart::Item* item) { KexiPart::Info *info = item ? Kexi::partManager().infoForPluginId(item->pluginId()) : 0; if ((! info) || (! info->isExecuteSupported())) return false; KexiPart::Part *part = Kexi::partManager().part(info); if (!part) return false; return part->execute(item); } void KexiMainWindow::slotProjectImportDataTable() { //! @todo allow data appending (it is not possible now) if (d->userMode) return; QMap args; args.insert("sourceType", "file"); QDialog *dlg = KexiInternalPart::createModalDialogInstance( "org.kexi-project.importexport.csv", "KexiCSVImportDialog", this, 0, &args); if (!dlg) return; //error msg has been shown by KexiInternalPart dlg->exec(); delete dlg; } tristate KexiMainWindow::executeCustomActionForObject(KexiPart::Item* item, const QString& actionName) { if (actionName == "exportToCSV") return exportItemAsDataTable(item); else if (actionName == "copyToClipboardAsCSV") return copyItemToClipboardAsDataTable(item); qWarning() << "no such action:" << actionName; return false; } tristate KexiMainWindow::exportItemAsDataTable(KexiPart::Item* item) { if (!item) return false; QMap args; if (!checkForDirtyFlagOnExport(item, &args)) { return false; } //! @todo: accept record changes... args.insert("destinationType", "file"); args.insert("itemId", QString::number(item->identifier())); QDialog *dlg = KexiInternalPart::createModalDialogInstance( "org.kexi-project.importexport.csv", "KexiCSVExportWizard", this, 0, &args); if (!dlg) return false; //error msg has been shown by KexiInternalPart int result = dlg->exec(); delete dlg; return result == QDialog::Rejected ? tristate(cancelled) : tristate(true); } bool KexiMainWindow::checkForDirtyFlagOnExport(KexiPart::Item *item, QMap *args) { //! @todo: handle tables if (item->pluginId() != "org.kexi-project.query") { return true; } KexiWindow * itemWindow = openedWindowFor(item); if (itemWindow && itemWindow->isDirty()) { tristate result; if (item->neverSaved()) { result = true; } else { int prevWindowId = 0; if (!itemWindow->isVisible()) { prevWindowId = currentWindow()->id(); activateWindow(itemWindow->id()); } result = askOnExportingChangedQuery(item); if (prevWindowId != 0) { activateWindow(prevWindowId); } } if (~result) { return false; } else if (true == result) { args->insert("useTempQuery","1"); } } return true; } tristate KexiMainWindow::askOnExportingChangedQuery(KexiPart::Item *item) const { const KMessageBox::ButtonCode result = KMessageBox::warningYesNoCancel(const_cast(this), xi18nc("@info", "Design of query %1 that you want to export data" " from is changed and has not yet been saved. Do you want to use data" " from the changed query for exporting or from its original (saved)" " version?", item->captionOrName()), QString(), KGuiItem(xi18nc("@action:button Export query data", "Use the Changed Query")), KGuiItem(xi18nc("@action:button Export query data", "Use the Original Query")), KStandardGuiItem::cancel(), QString(), KMessageBox::Notify | KMessageBox::Dangerous); if (result == KMessageBox::Yes) { return true; } else if (result == KMessageBox::No) { return false; } return cancelled; } bool KexiMainWindow::printItem(KexiPart::Item* item, const QString& titleText) { //! @todo printItem(item, KexiSimplePrintingSettings::load(), titleText); Q_UNUSED(item) Q_UNUSED(titleText) return false; } tristate KexiMainWindow::printItem(KexiPart::Item* item) { return printItem(item, QString()); } bool KexiMainWindow::printPreviewForItem(KexiPart::Item* item, const QString& titleText, bool reload) { //! @todo printPreviewForItem(item, KexiSimplePrintingSettings::load(), titleText, reload); Q_UNUSED(item) Q_UNUSED(titleText) Q_UNUSED(reload) return false; } tristate KexiMainWindow::printPreviewForItem(KexiPart::Item* item) { return printPreviewForItem(item, QString(), //! @todo store cached record data? true/*reload*/); } tristate KexiMainWindow::showPageSetupForItem(KexiPart::Item* item) { Q_UNUSED(item) //! @todo check if changes to this object's design are saved, if not: ask for saving //! @todo accept record changes... //! @todo printActionForItem(item, PageSetupForItem); return false; } //! @todo reenable printItem() when ported #if 0 bool KexiMainWindow::printItem(KexiPart::Item* item, const KexiSimplePrintingSettings& settings, const QString& titleText) { //! @todo: check if changes to this object's design are saved, if not: ask for saving //! @todo: accept record changes... KexiSimplePrintingCommand cmd(this, item->identifier()); //modal return cmd.print(settings, titleText); } bool KexiMainWindow::printPreviewForItem(KexiPart::Item* item, const KexiSimplePrintingSettings& settings, const QString& titleText, bool reload) { //! @todo: check if changes to this object's design are saved, if not: ask for saving //! @todo: accept record changes... KexiSimplePrintingCommand* cmd = d->openedCustomObjectsForItem( item, "KexiSimplePrintingCommand"); if (!cmd) { d->addOpenedCustomObjectForItem( item, cmd = new KexiSimplePrintingCommand(this, item->identifier()), "KexiSimplePrintingCommand" ); } return cmd->showPrintPreview(settings, titleText, reload); } tristate KexiMainWindow::printActionForItem(KexiPart::Item* item, PrintActionType action) { if (!item) return false; KexiPart::Info *info = Kexi::partManager().infoForPluginId(item->pluginId()); if (!info->isPrintingSupported()) return false; KexiWindow *printingWindow = d->pageSetupWindows[ item->identifier()]; if (printingWindow) { if (!activateWindow(*printingWindow)) return false; if (action == PreviewItem || action == PrintItem) { QTimer::singleShot(0, printingWindow->selectedView(), (action == PreviewItem) ? SLOT(printPreview()) : SLOT(print())); } return true; } #ifndef KEXI_NO_PENDING_DIALOGS Private::PendingJobType pendingType; KexiWindow *window = d->openedWindowFor(item, pendingType); if (pendingType != Private::NoJob) return cancelled; #else KexiWindow *window = openedWindowFor(item); #endif if (window) { // accept record changes QWidget *prevFocusWidget = focusWidget(); window->setFocus(); d->action_data_save_row->activate(QAction::Trigger); if (prevFocusWidget) prevFocusWidget->setFocus(); // opened: check if changes made to this dialog are saved, if not: ask for saving if (window->neverSaved()) //sanity check return false; if (window->isDirty()) { KGuiItem saveChanges(KStandardGuiItem::save()); saveChanges.setToolTip(futureI18n("Save changes")); saveChanges.setWhatsThis( futureI18n("Pressing this button will save all recent changes made in \"%1\" object.", item->name())); KGuiItem doNotSave(KStandardGuiItem::no()); doNotSave.setWhatsThis( futureI18n("Pressing this button will ignore all unsaved changes made in \"%1\" object.", window->partItem()->name())); QString question; if (action == PrintItem) question = futureI18n("Do you want to save changes before printing?"); else if (action == PreviewItem) question = futureI18n("Do you want to save changes before making print preview?"); else if (action == PageSetupForItem) question = futureI18n("Do you want to save changes before showing page setup?"); else return false; const KMessageBox::ButtonCode questionRes = KMessageBox::warningYesNoCancel(this, "

    " + window->part()->i18nMessage("Design of object %1 has been modified.", window) .subs(item->name()) + "

    " + question + "

    ", QString(), saveChanges, doNotSave); if (KMessageBox::Cancel == questionRes) return cancelled; if (KMessageBox::Yes == questionRes) { tristate savingRes = saveObject(window, QString(), DoNotAsk); if (true != savingRes) return savingRes; } } } KexiPart::Part * printingPart = Kexi::partManager().partForClass("org.kexi-project.simpleprinting"); if (!printingPart) printingPart = new KexiSimplePrintingPart(); //hardcoded as there're no .desktop file KexiPart::Item* printingPartItem = d->prj->createPartItem( printingPart, item->name() //<-- this will look like "table1 : printing" on the window list ); QMap staticObjectArgs; staticObjectArgs["identifier"] = QString::number(item->identifier()); if (action == PrintItem) staticObjectArgs["action"] = "print"; else if (action == PreviewItem) staticObjectArgs["action"] = "printPreview"; else if (action == PageSetupForItem) staticObjectArgs["action"] = "pageSetup"; else return false; bool openingCancelled; printingWindow = openObject(printingPartItem, Kexi::DesignViewMode, &openingCancelled, &staticObjectArgs); if (openingCancelled) return cancelled; if (!printingWindow) //sanity return false; d->pageSetupWindows.insert(item->identifier(), printingWindow); d->pageSetupWindowItemID2dataItemID_map.insert( printingWindow->partItem()->identifier(), item->identifier()); return true; } #endif void KexiMainWindow::slotEditCopySpecialDataTable() { KexiPart::Item* item = d->navigator->selectedPartItem(); if (item) copyItemToClipboardAsDataTable(item); } tristate KexiMainWindow::copyItemToClipboardAsDataTable(KexiPart::Item* item) { if (!item) return false; QMap args; if (!checkForDirtyFlagOnExport(item, &args)) { return false; } args.insert("destinationType", "clipboard"); args.insert("itemId", QString::number(item->identifier())); QDialog *dlg = KexiInternalPart::createModalDialogInstance( "org.kexi-project.importexport.csv", "KexiCSVExportWizard", this, 0, &args); if (!dlg) return false; //error msg has been shown by KexiInternalPart const int result = dlg->exec(); delete dlg; return result == QDialog::Rejected ? tristate(cancelled) : tristate(true); } void KexiMainWindow::slotEditPasteSpecialDataTable() { //! @todo allow data appending (it is not possible now) if (d->userMode) return; QMap args; args.insert("sourceType", "clipboard"); QDialog *dlg = KexiInternalPart::createModalDialogInstance( "org.kexi-project.importexport.csv", "KexiCSVImportDialog", this, 0, &args); if (!dlg) return; //error msg has been shown by KexiInternalPart dlg->exec(); delete dlg; } void KexiMainWindow::slotEditFind() { KexiSearchAndReplaceViewInterface* iface = d->currentViewSupportingSearchAndReplaceInterface(); if (!iface) return; d->updateFindDialogContents(true/*create if does not exist*/); d->findDialog()->setReplaceMode(false); d->findDialog()->show(); d->findDialog()->activateWindow(); d->findDialog()->raise(); } void KexiMainWindow::slotEditFind(bool next) { KexiSearchAndReplaceViewInterface* iface = d->currentViewSupportingSearchAndReplaceInterface(); if (!iface) return; tristate res = iface->find( d->findDialog()->valueToFind(), d->findDialog()->options(), next); if (~res) return; d->findDialog()->updateMessage(true == res); //! @todo result } void KexiMainWindow::slotEditFindNext() { slotEditFind(true); } void KexiMainWindow::slotEditFindPrevious() { slotEditFind(false); } void KexiMainWindow::slotEditReplace() { KexiSearchAndReplaceViewInterface* iface = d->currentViewSupportingSearchAndReplaceInterface(); if (!iface) return; d->updateFindDialogContents(true/*create if does not exist*/); d->findDialog()->setReplaceMode(true); //! @todo slotEditReplace() d->findDialog()->show(); d->findDialog()->activateWindow(); } void KexiMainWindow::slotEditReplaceNext() { slotEditReplace(false); } void KexiMainWindow::slotEditReplace(bool all) { KexiSearchAndReplaceViewInterface* iface = d->currentViewSupportingSearchAndReplaceInterface(); if (!iface) return; //! @todo add question: "Do you want to replace every occurrence of \"%1\" with \"%2\"? //! You won't be able to undo this." + "Do not ask again". tristate res = iface->findNextAndReplace( d->findDialog()->valueToFind(), d->findDialog()->valueToReplaceWith(), d->findDialog()->options(), all); d->findDialog()->updateMessage(true == res); //! @todo result } void KexiMainWindow::slotEditReplaceAll() { slotEditReplace(true); } void KexiMainWindow::highlightObject(const QString& pluginId, const QString& name) { slotShowNavigator(); if (!d->prj) return; KexiPart::Item *item = d->prj->itemForPluginId(pluginId, name); if (!item) return; if (d->navigator) { d->navigator->selectItem(*item); } } void KexiMainWindow::slotPartItemSelectedInNavigator(KexiPart::Item* item) { Q_UNUSED(item); } KToolBar *KexiMainWindow::toolBar(const QString& name) const { return d->tabbedToolBar ? d->tabbedToolBar->toolBar(name) : 0; } void KexiMainWindow::appendWidgetToToolbar(const QString& name, QWidget* widget) { if (d->tabbedToolBar) d->tabbedToolBar->appendWidgetToToolbar(name, widget); } void KexiMainWindow::setWidgetVisibleInToolbar(QWidget* widget, bool visible) { if (d->tabbedToolBar) d->tabbedToolBar->setWidgetVisibleInToolbar(widget, visible); } void KexiMainWindow::addToolBarAction(const QString& toolBarName, QAction *action) { if (d->tabbedToolBar) d->tabbedToolBar->addAction(toolBarName, action); } void KexiMainWindow::updatePropertyEditorInfoLabel(const QString& textToDisplayForNullSet) { d->propEditor->updateInfoLabelForPropertySet(d->propertySet, textToDisplayForNullSet); } void KexiMainWindow::addSearchableModel(KexiSearchableModel *model) { if (d->tabbedToolBar) { d->tabbedToolBar->addSearchableModel(model); } } void KexiMainWindow::setReasonableDialogSize(QDialog *dialog) { dialog->setMinimumSize(600, 400); dialog->resize(size() * 0.8); } void KexiMainWindow::restoreDesignTabAndActivateIfNeeded(const QString &tabName) { if (!d->tabbedToolBar) { return; } d->tabbedToolBar->showTab(tabName); if (currentWindow() && currentWindow()->partItem() && currentWindow()->partItem()->identifier() != 0) // for unstored items id can be < 0 { const QString tabToActivate = d->tabsToActivateOnShow.value( currentWindow()->partItem()->identifier()); //qDebug() << "tabToActivate:" << tabToActivate << "tabName:" << tabName; if (tabToActivate == tabName) { d->tabbedToolBar->setCurrentTab(tabToActivate); } } } void KexiMainWindow::restoreDesignTabIfNeeded(const QString &pluginId, Kexi::ViewMode viewMode, int previousItemId) { //qDebug() << pluginId << viewMode << previousItemId; if (viewMode == Kexi::DesignViewMode) { switch (d->prj->typeIdForPluginId(pluginId)) { case KexiPart::FormObjectType: { hideDesignTab(previousItemId, "org.kexi-project.report"); restoreDesignTabAndActivateIfNeeded("form"); break; } case KexiPart::ReportObjectType: { hideDesignTab(previousItemId, "org.kexi-project.form"); restoreDesignTabAndActivateIfNeeded("report"); break; } default: hideDesignTab(previousItemId); } } else { hideDesignTab(previousItemId); } } void KexiMainWindow::activateDesignTab(const QString &pluginId) { if (!d->tabbedToolBar) { return; } switch (d->prj->typeIdForPluginId(pluginId)) { case KexiPart::FormObjectType: d->tabbedToolBar->setCurrentTab("form"); break; case KexiPart::ReportObjectType: d->tabbedToolBar->setCurrentTab("report"); break; default:; } } void KexiMainWindow::activateDesignTabIfNeeded(const QString &pluginId, Kexi::ViewMode viewMode) { if (!d->tabbedToolBar) { return; } const QString tabToActivate = d->tabsToActivateOnShow.value(currentWindow()->partItem()->identifier()); //qDebug() << pluginId << viewMode << tabToActivate; if (viewMode == Kexi::DesignViewMode && tabToActivate.isEmpty()) { activateDesignTab(pluginId); } else { d->tabbedToolBar->setCurrentTab(tabToActivate); } } void KexiMainWindow::hideDesignTab(int itemId, const QString &pluginId) { if (!d->tabbedToolBar) { return; } //qDebug() << itemId << pluginId; if ( itemId > 0 && d->tabbedToolBar->currentWidget()) { const QString currentTab = d->tabbedToolBar->currentWidget()->objectName(); //qDebug() << "d->tabsToActivateOnShow.insert" << itemId << currentTab; d->tabsToActivateOnShow.insert(itemId, currentTab); } switch (d->prj->typeIdForPluginId(pluginId)) { case KexiPart::FormObjectType: d->tabbedToolBar->hideTab("form"); break; case KexiPart::ReportObjectType: d->tabbedToolBar->hideTab("report"); break; default: d->tabbedToolBar->hideTab("form"); d->tabbedToolBar->hideTab("report"); } } void KexiMainWindow::showDesignTabIfNeeded(int previousItemId) { if (d->insideCloseWindow && d->tabbedToolBar) return; if (currentWindow()) { restoreDesignTabIfNeeded(currentWindow()->partItem()->pluginId(), currentWindow()->currentViewMode(), previousItemId); } else { hideDesignTab(previousItemId); } } KexiUserFeedbackAgent* KexiMainWindow::userFeedbackAgent() const { return &d->userFeedback; } KexiMigrateManagerInterface* KexiMainWindow::migrateManager() { if (!d->migrateManager) { d->migrateManager = dynamic_cast( KexiInternalPart::createObjectInstance( "org.kexi-project.migration", "manager", this, this, nullptr)); } return d->migrateManager; } void KexiMainWindow::toggleFullScreen(bool isFullScreen) { static bool isTabbarRolledDown; if (d->tabbedToolBar) { if (isFullScreen) { isTabbarRolledDown = !d->tabbedToolBar->isRolledUp(); if (isTabbarRolledDown) { d->tabbedToolBar->toggleRollDown(); } } else { if (isTabbarRolledDown && d->tabbedToolBar->isRolledUp()) { d->tabbedToolBar->toggleRollDown(); } } } const Qt::WindowStates s = windowState() & Qt::WindowMaximized; if (isFullScreen) { setWindowState(windowState() | Qt::WindowFullScreen | s); } else { setWindowState((windowState() & ~Qt::WindowFullScreen)); showMaximized(); } } diff --git a/src/main/KexiMainWindow_p.cpp b/src/main/KexiMainWindow_p.cpp index 1f4c88f64..70c38990d 100644 --- a/src/main/KexiMainWindow_p.cpp +++ b/src/main/KexiMainWindow_p.cpp @@ -1,1583 +1,1583 @@ /* This file is part of the KDE project Copyright (C) 2003 Lucijan Busch Copyright (C) 2003-2015 JarosÅ‚aw Staniek 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 "KexiMainWindow_p.h" #include #include #include #include #include #include #include #include KexiWindowContainer::KexiWindowContainer(QWidget* parent) : QWidget(parent) , window(0) , lyr(new QVBoxLayout(this)) { lyr->setContentsMargins(0, 0, 0, 0); } KexiWindowContainer::~KexiWindowContainer() { //! @todo warning if saveSettings() failed? if (window) { window->saveSettings(); delete (KexiWindow*)window; } } void KexiWindowContainer::setWindow(KexiWindow* w) { window = w; if (w) lyr->addWidget(w); } // --- EmptyMenuContentWidget::EmptyMenuContentWidget(QWidget* parent) : QWidget(parent) { setAutoFillBackground(true); alterBackground(); } void EmptyMenuContentWidget::alterBackground() { QPalette pal(palette()); QColor bg(pal.color(QPalette::Window)); bg.setAlpha(200); pal.setColor(QPalette::Window, bg); setPalette(pal); } void EmptyMenuContentWidget::changeEvent(QEvent *e) { if (e->type() == QEvent::PaletteChange) { alterBackground(); } QWidget::changeEvent(e); } //! @todo KEXI3 is KexiMenuWidgetStyle needed? #if 0 KexiMenuWidgetStyle::KexiMenuWidgetStyle(QStyle *style, QObject *parent) : KexiUtils::StyleProxy(style, parent) { } KexiMenuWidgetStyle::~KexiMenuWidgetStyle() { } void KexiMenuWidgetStyle::drawControl(ControlElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget = 0) const { if (element == QStyle::CE_MenuItem && (option->state & QStyle::State_Selected) && (option->state & QStyle::State_Enabled) && parentStyle()->objectName() == QLatin1String("oxygen")) { // Ugly fix for visual glitch of oxygen; no chance for improvement since // we've forked QMenu and oxygen checks for qobject_cast directly. QColor c(option->palette.color(QPalette::Window)); int h, s, v, a; c.getHsv(&h, &s, &v, &a); // Why 0.91208791? I knew you're curious. There are some algorithms in Oxygen // to make color a bit lighter. They are not in the public API nor they are simple. // So the number was computed by me to find the proper value for the color // (the accuracy is quite OK). // It's also related to the fact that Oxygen's menus have gradient background. // A lot of computation happens under the mask... c.setHsv(h, s, v * 0.91208791, a); painter->fillRect(option->rect.x() + 6, option->rect.y() + 6, option->rect.width() - 12, option->rect.height() - 12, c); } KexiUtils::StyleProxy::drawControl(element, option, painter, widget); } #endif KexiMainMenu::KexiMainMenu(KexiTabbedToolBar *toolBar, QWidget* parent) : QWidget(parent), m_toolBar(toolBar), m_initialized(false) { m_content = 0; m_selectFirstItem = false; } KexiMainMenu::~KexiMainMenu() { delete (QWidget*)m_contentWidget; } bool KexiMainMenu::eventFilter(QObject * watched, QEvent* event) { if (event->type() == QEvent::MouseButtonPress && watched == m_content && !m_contentWidget) { emit contentAreaPressed(); } else if (event->type() == QEvent::KeyPress) { QKeyEvent* ke = static_cast(event); if ((ke->key() == Qt::Key_Escape) && ke->modifiers() == Qt::NoModifier) { emit hideContentsRequested(); return true; } } return QWidget::eventFilter(watched, event); } void KexiMainMenu::setContent(QWidget *contentWidget) { if (m_menuWidget && m_persistentlySelectedAction) { m_menuWidget->setPersistentlySelectedAction( m_persistentlySelectedAction, m_persistentlySelectedAction->persistentlySelected()); } /*if (m_menuWidget->persistentlySelectedAction()) qDebug() << "****" << m_menuWidget->persistentlySelectedAction()->objectName();*/ KexiFadeWidgetEffect *fadeEffect = 0; if (m_contentWidget && contentWidget) { fadeEffect = new KexiFadeWidgetEffect(m_content); } if (m_contentWidget) m_contentWidget->deleteLater(); m_contentWidget = contentWidget; if (m_contentWidget) { QPalette contentWidgetPalette(m_contentWidget->palette()); contentWidgetPalette.setBrush(QPalette::Active, QPalette::Window, contentWidgetPalette.brush(QPalette::Active, QPalette::Base)); contentWidgetPalette.setBrush(QPalette::Inactive, QPalette::Window, contentWidgetPalette.brush(QPalette::Inactive, QPalette::Base)); contentWidgetPalette.setBrush(QPalette::Disabled, QPalette::Window, contentWidgetPalette.brush(QPalette::Disabled, QPalette::Base)); contentWidgetPalette.setBrush(QPalette::Active, QPalette::WindowText, contentWidgetPalette.brush(QPalette::Active, QPalette::Text)); contentWidgetPalette.setBrush(QPalette::Inactive, QPalette::WindowText, contentWidgetPalette.brush(QPalette::Inactive, QPalette::Text)); contentWidgetPalette.setBrush(QPalette::Disabled, QPalette::WindowText, contentWidgetPalette.brush(QPalette::Disabled, QPalette::Text)); const QColor highlightDisabled(KexiUtils::blendedColors( contentWidgetPalette.color(QPalette::Active, QPalette::Highlight), contentWidgetPalette.color(QPalette::Disabled, QPalette::Window), 1, 2)); contentWidgetPalette.setBrush(QPalette::Disabled, QPalette::Highlight, highlightDisabled); const QColor highlightedTextDisabled(KexiUtils::blendedColors( contentWidgetPalette.color(QPalette::Active, QPalette::HighlightedText), contentWidgetPalette.color(QPalette::Disabled, QPalette::WindowText), 1, 2)); contentWidgetPalette.setBrush(QPalette::Disabled, QPalette::HighlightedText, highlightedTextDisabled); m_contentWidget->setPalette(contentWidgetPalette); for(QAbstractScrollArea *area : m_contentWidget->findChildren()) { QPalette pal(area->viewport()->palette()); pal.setBrush(QPalette::Disabled, QPalette::Base, contentWidgetPalette.brush(QPalette::Disabled, QPalette::Base)); area->viewport()->setPalette(pal); } m_contentWidget->setAutoFillBackground(true); m_contentWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); m_contentWidget->setContentsMargins(0, 0, 0, 0); m_contentLayout->addWidget(m_contentWidget); m_contentLayout->setCurrentWidget(m_contentWidget); m_contentWidget->setFocus(); m_contentWidget->installEventFilter(this); //connect(m_contentWidget, SIGNAL(destroyed()), this, SLOT(contentWidgetDestroyed())); } if (fadeEffect) { if (m_contentWidget) m_contentLayout->update(); QTimer::singleShot(10, fadeEffect, SLOT(start())); } } const QWidget *KexiMainMenu::contentWidget() const { return m_contentWidget; } void KexiMainMenu::setPersistentlySelectedAction(KexiMenuWidgetAction* action, bool set) { m_persistentlySelectedAction = action; m_persistentlySelectedAction->setPersistentlySelected(set); } /* void KexiMainMenu::setActiveAction(QAction* action = 0) { if (!action && !m_menuWidget->actions().isEmpty()) { action = actions().first(); } if (action) { m_menuWidget->setActiveAction(action); } } */ void KexiMainMenu::selectFirstItem() { m_selectFirstItem = true; } void KexiMainMenu::showEvent(QShowEvent * event) { if (!m_initialized) { m_initialized = true; KActionCollection *ac = KexiMainWindowIface::global()->actionCollection(); QHBoxLayout *hlyr = new QHBoxLayout(this); hlyr->setSpacing(0); hlyr->setMargin(0); m_menuWidget = new KexiMenuWidget; //! @todo KEXI3 is KexiMenuWidgetStyle needed? #if 0 QString styleName(m_menuWidget->style()->objectName()); if (KDE::version() < KDE_MAKE_VERSION(4, 8, 0) // a fix is apparently needed for glitch in KDE < 4.8 && styleName == "oxygen") { KexiMenuWidgetStyle *customStyle = new KexiMenuWidgetStyle(m_menuWidget->style()->objectName(), this); m_menuWidget->setStyle(customStyle); } #endif m_menuWidget->installEventFilter(this); m_menuWidget->setFocusPolicy(Qt::StrongFocus); setFocusProxy(m_menuWidget); m_menuWidget->setFrame(false); m_menuWidget->setAutoFillBackground(true); m_menuWidget->addAction(ac->action("project_welcome")); m_menuWidget->addAction(ac->action("project_open")); m_menuWidget->addAction(ac->action("project_close")); m_menuWidget->addSeparator(); m_menuWidget->addAction(ac->action("project_new")); m_menuWidget->addAction(ac->action("project_import_export_send")); #ifdef KEXI_SHOW_UNIMPLEMENTED m_menuWidget->addAction(ac->action("project_properties")); //! @todo project information m_menuWidget->addAction(ac->action("settings")); #endif m_menuWidget->addSeparator(); m_menuWidget->addAction(ac->action("quit")); hlyr->addWidget(m_menuWidget); m_content = new EmptyMenuContentWidget; m_content->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); m_content->installEventFilter(this); m_mainContentLayout = new QVBoxLayout; hlyr->addLayout(m_mainContentLayout); m_contentLayout = new QStackedLayout(m_content); m_contentLayout->setStackingMode(QStackedLayout::StackAll); m_contentLayout->setContentsMargins(0, 0, 0, 0); m_mainContentLayout->addWidget(m_content); hlyr->setStretchFactor(m_mainContentLayout, 1); } QWidget::showEvent(event); if (m_selectFirstItem && !m_menuWidget->actions().isEmpty()) { QAction* action = m_menuWidget->actions().first(); m_menuWidget->setActiveAction(action); m_selectFirstItem = false; } } // --- KexiTabbedToolBar::Private::Private(KexiTabbedToolBar *t) : q(t), createWidgetToolBar(0) #ifdef KEXI_AUTORISE_TABBED_TOOLBAR , tabToRaise(-1) #endif , rolledUp(false) { #ifdef KEXI_AUTORISE_TABBED_TOOLBAR tabRaiseTimer.setSingleShot(true); tabRaiseTimer.setInterval(300); #endif tabBarAnimation.setPropertyName("opacity"); tabBarAnimation.setDuration(500); connect(&tabBarAnimation, SIGNAL(finished()), q, SLOT(tabBarAnimationFinished())); tabIndex = 0; lowestIndex = 2; } //! @return true if @a style name is specific regarding tab styling static bool isSpecificTabStyle(const QString &styleName) { return styleName == "oxygen" || styleName == "qtcurve" || styleName == "gtk+"; } KexiTabbedToolBarStyle::KexiTabbedToolBarStyle(const QString &baseStyleName) : QProxyStyle(baseStyleName) { } KexiTabbedToolBarStyle::~KexiTabbedToolBarStyle() { } void KexiTabbedToolBarStyle::drawControl(ControlElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const { const QString styleName(baseStyle()->objectName()); qreal origOpacity = -1.0; if (element == CE_TabBarTab) { - const QStyleOptionTabV3* opt - = qstyleoption_cast(option); + const QStyleOptionTab* opt + = qstyleoption_cast(option); const QTabBar* tabBar = qobject_cast(widget); KexiTabbedToolBar* tbar = tabBar ? qobject_cast(tabBar->parentWidget()) : 0; if (opt && tbar) { const int index = tabBar->tabAt(opt->rect.center()); if (index == KEXITABBEDTOOLBAR_SPACER_TAB_INDEX) return; bool mouseOver = opt->state & QStyle::State_MouseOver; bool unselectedOrMenuVisible = !(opt->state & State_Selected) || tbar->mainMenuVisible(); if (unselectedOrMenuVisible) { if (styleName == "bespin") { unselectedOrMenuVisible = false; } } - QStyleOptionTabV3 newOpt(*opt); + QStyleOptionTab newOpt(*opt); const bool specificStyle = isSpecificTabStyle(styleName); newOpt.text = (specificStyle ? " " : "") + tabBar->tabText(index) + (specificStyle ? " " : ""); if (!mouseOver && unselectedOrMenuVisible && index > 0) { if (tbar->mainMenuVisible()) newOpt.state &= ~QStyle::State_HasFocus; QProxyStyle::drawControl(CE_TabBarTabLabel, &newOpt, painter, widget); return; } else if (index == 0) { QBrush bg; newOpt.state |= State_Selected; if (tbar->mainMenuVisible()) { bg = newOpt.palette.brush(QPalette::Active, QPalette::Highlight); if (!specificStyle) { newOpt.palette.setBrush(QPalette::WindowText, newOpt.palette.brush(QPalette::Active, QPalette::HighlightedText)); newOpt.palette.setBrush(QPalette::ButtonText, newOpt.palette.brush(QPalette::Active, QPalette::HighlightedText)); } } else { bg = Qt::transparent; } QFont origFont(painter->font()); QFont f(origFont); f.setBold(true); painter->setFont(f); newOpt.palette.setBrush(QPalette::Window, bg); newOpt.palette.setBrush(QPalette::Button, // needed e.g. for Plastique style bg); QProxyStyle::drawControl(element, &newOpt, painter, widget); painter->setFont(origFont); if (!mouseOver || tbar->mainMenuVisible() || styleName == "gtk+") { return; } } if (index > 0 || mouseOver) { const QPalette::ColorGroup hbGroup = (styleName == "oxygen") ? QPalette::Active : QPalette::Inactive; const QBrush hb(newOpt.palette.brush(hbGroup, QPalette::Highlight)); newOpt.palette.setBrush(QPalette::Window, hb); newOpt.palette.setBrush(QPalette::Button, hb); // needed e.g. for Plastique style if (mouseOver && (index != tbar->currentIndex() || tbar->mainMenuVisible())) { // use lower opacity for diplaying hovered tabs origOpacity = painter->opacity(); painter->setOpacity(styleName == "qtcurve" ? 0.2 : 0.3); newOpt.state |= State_Selected; } else { if (!specificStyle) { newOpt.palette.setBrush(QPalette::WindowText, newOpt.palette.brush(QPalette::Inactive, QPalette::HighlightedText)); newOpt.palette.setBrush(QPalette::ButtonText, newOpt.palette.brush(QPalette::Inactive, QPalette::HighlightedText)); } } if (index == tbar->currentIndex() && styleName == "qtcurve") { origOpacity = painter->opacity(); painter->setOpacity(0.5); } (newOpt.state |= State_Active) ^= State_Active; QProxyStyle::drawControl(element, &newOpt, painter, widget); if (origOpacity != -1.0) { // restore opacity and draw labels using full this opacity painter->setOpacity(origOpacity); if (index > 0) { QProxyStyle::drawControl(CE_TabBarTabLabel, &newOpt, painter, widget); } } return; } } } else if (element == CE_ToolBar) { return; } QProxyStyle::drawControl(element, option, painter, widget); } void KexiTabbedToolBarStyle::drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const { const QString styleName(baseStyle()->objectName()); if (element == PE_FrameTabWidget) { return; } if (element == PE_FrameTabBarBase) { const QTabBar* tabBar = qobject_cast(widget); KexiTabbedToolBar* tbar = tabBar ? qobject_cast(tabBar->parentWidget()) : 0; if (tbar && tbar->mainMenuVisible() && styleName != "bespin") { return; } } if (element == QStyle::PE_PanelToolBar || element == QStyle::PE_FrameMenu) { return; } QProxyStyle::drawPrimitive(element, option, painter, widget); } int KexiTabbedToolBarStyle::pixelMetric(PixelMetric metric, const QStyleOption* option, const QWidget* widget) const { if (metric == QStyle::PM_SmallIconSize) return KIconLoader::SizeMedium; return QProxyStyle::pixelMetric(metric, option, widget); } // --- KexiTabbedToolBarTabBar::KexiTabbedToolBarTabBar(QWidget *parent) : QTabBar(parent) { setObjectName("tabbar"); customStyle = new KexiTabbedToolBarStyle(style()->objectName()); customStyle->setParent(this); setStyle(customStyle); installEventFilter(parent); QWidget *mainWindow = KexiMainWindowIface::global()->thisWidget(); mainWindow->installEventFilter(parent); setAttribute(Qt::WA_Hover, true); } QSize KexiTabbedToolBarTabBar::originalTabSizeHint(int index) const { return QTabBar::tabSizeHint(index); } QSize KexiTabbedToolBarTabBar::tabSizeHint(int index) const { QSize s = QTabBar::tabSizeHint(index); QStyleOptionTab ot; ot.initFrom(this); QFont f(font()); f.setBold(true); ot.text = (isSpecificTabStyle(style()->objectName()) ? " " : "") + tabText(index); ot.fontMetrics = QFontMetrics(f); int w = customStyle->pixelMetric(QStyle::PM_TabBarTabHSpace, &ot, this); if (w <= 0) { // needed e.g. for oxygen w = fontMetrics().width(" "); } if (index == 0) { s.setWidth(QFontMetrics(f).width(ot.text) + w * 2); return s; } else if (index == KEXITABBEDTOOLBAR_SPACER_TAB_INDEX) { // fix width of the spacer tab s.setWidth(w); } return s; } void KexiTabbedToolBar::Private::toggleMainMenu() { if (q->mainMenuVisible()) hideMainMenu(); else showMainMenu(); } void KexiTabbedToolBar::Private::showMainMenu(const char* actionName) { QWidget *mainWindow = KexiMainWindowIface::global()->thisWidget(); if (!mainMenu) { mainMenu = new KexiMainMenu(q, mainWindow); connect(mainMenu, SIGNAL(contentAreaPressed()), this, SLOT(hideMainMenu())); connect(mainMenu, SIGNAL(hideContentsRequested()), this, SLOT(hideContentsOrMainMenu())); } updateMainMenuGeometry(); if (actionName) { q->selectMainMenuItem(actionName); } else { mainMenu->selectFirstItem(); } mainMenu->show(); mainMenu->setFocus(); q->update(); // tab bar could have a line that should be repainted } void KexiTabbedToolBar::Private::updateMainMenuGeometry() { if (!mainMenu) return; QWidget *mainWindow = KexiMainWindowIface::global()->thisWidget(); KexiTabbedToolBarTabBar *tabBar = static_cast(q->tabBar()); QPoint pos = q->mapToGlobal(QPoint(0, tabBar->originalTabSizeHint(0).height() - 1)); // qDebug() << "1." << pos; pos = mainWindow->mapFromGlobal(pos); // qDebug() << "2." << pos; // qDebug() << "3." << q->pos(); QStyleOptionTab ot; ot.initFrom(tabBar); int overlap = tabBar->style()->pixelMetric(QStyle::PM_TabBarBaseOverlap, &ot, tabBar) - tabBar->style()->pixelMetric(QStyle::PM_TabBarBaseHeight, &ot, tabBar); // qDebug() << "4. overlap=" << overlap; mainMenu->setGeometry(0, pos.y() - overlap /*- q->y()*/, mainWindow->width(), mainWindow->height() - pos.y() + overlap /*+ q->y()*/); } void KexiTabbedToolBar::activateSearchLineEdit() { d->searchLineEdit->selectAll(); d->searchLineEdit->setFocus(); } void KexiTabbedToolBar::Private::hideMainMenu() { if (!mainMenu || !mainMenu->isVisible()) return; mainMenu->hide(); mainMenu->setContent(0); q->update(); // tab bar could have a line that should be repainted } void KexiTabbedToolBar::Private::hideContentsOrMainMenu() { if (!mainMenu || !mainMenu->isVisible()) return; if (mainMenu->contentWidget()) { mainMenu->setContent(0); } else { hideMainMenu(); } } KToolBar *KexiTabbedToolBar::Private::createToolBar(const char *name, const QString& caption) { KToolBar *tbar = new KToolBar(q, true /*main toolbar*/, false /*read config*/); tbar->setIconDimensions(22); //!< @todo make configurable or system-dependent? // needed e.g. for Windows style to remove the toolbar's frame tbar->setStyle(customTabBar->customStyle); toolbarsForName.insert(name, tbar); tbar->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); tbar->setObjectName(name); toolbarsCaptionForName.insert(name, caption); tabIndex = q->addTab(tbar, caption); toolbarsVisibleForIndex.append(true); toolbarsIndexForName.insert(name, tabIndex); return tbar; } KexiTabbedToolBar::KexiTabbedToolBar(QWidget *parent) : QTabWidget(parent) , d(new Private(this)) { d->customTabBar = new KexiTabbedToolBarTabBar(this); setTabBar(d->customTabBar); setStyle(d->customTabBar->customStyle); // from ktabwidget.cpp //! @todo QTabWidget::setTabBar() should be added with this code //! @todo KEXI3 Are these tabBar() connections useful to port? #if 0 connect(tabBar(), SIGNAL(contextMenu(int,QPoint)), SLOT(contextMenu(int,QPoint&))); connect(tabBar(), SIGNAL(tabDoubleClicked(int)), SLOT(mouseDoubleClick(int))); connect(tabBar(), SIGNAL(newTabRequest()), this, SIGNAL(mouseDoubleClick())); // #185487 connect(tabBar(), SIGNAL(mouseMiddleClick(int)), SLOT(mouseMiddleClick(int))); connect(tabBar(), SIGNAL(initiateDrag( int )), SLOT(initiateDrag( int ))); connect(tabBar(), SIGNAL(testCanDecode(QDragMoveEvent*,bool*)), SIGNAL(testCanDecode(QDragMoveEvent*,bool*))); connect(tabBar(), SIGNAL(receivedDropEvent(int,QDropEvent*)), SLOT(receivedDropEvent(int,QDropEvent*))); connect(tabBar(), SIGNAL(moveTab(int,int)), SLOT(moveTab(int,int))); connect(tabBar(), SIGNAL(tabCloseRequested(int)), SLOT(closeRequest(int))); #endif setMouseTracking(true); // for mouseMoveEvent() setWhatsThis(xi18n("Task-oriented toolbar. Groups commands using tabs.")); #ifdef KEXI_AUTORISE_TABBED_TOOLBAR connect(&d->tabRaiseTimer, SIGNAL(timeout()), this, SLOT(slotDelayedTabRaise())); #endif connect(tabBar(), SIGNAL(tabBarDoubleClicked(int)), this, SLOT(slotTabDoubleClicked(int))); d->ac = KexiMainWindowIface::global()->actionCollection(); QWidget *mainWin = KexiMainWindowIface::global()->thisWidget(); const bool userMode = KexiMainWindowIface::global()->userMode(); KToolBar *tbar; slotSettingsChanged(0);//KGlobalSettings::FontChanged //! @todo KEXI3 port from KGlobalSettings::Private::_k_slotNotifyChange: //! connect(KGlobalSettings::self(), SIGNAL(settingsChanged(int)), this, SLOT(slotSettingsChanged(int))); // help area QWidget *helpWidget = new QWidget(this); helpWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); QHBoxLayout *helpLyr = new QHBoxLayout(helpWidget); helpLyr->setContentsMargins(0, 0, 0, 0); // * HELP MENU // add help menu actions... (KexiTabbedToolBar depends on them) d->helpMenu = new KHelpMenu(this, KAboutData::applicationData(), true/*showWhatsThis*/); QAction* help_report_bug_action = d->helpMenu->action(KHelpMenu::menuReportBug); d->ac->addAction(help_report_bug_action->objectName(), help_report_bug_action); QObject::disconnect(help_report_bug_action, 0, 0, 0); QObject::connect(help_report_bug_action, SIGNAL(triggered()), mainWin, SLOT(slotReportBug())); help_report_bug_action->setText(xi18nc("Report a bug or wish for Kexi application", "Report a &Bug or Wish...")); help_report_bug_action->setIcon(koIcon("tools-report-bug")); // good icon for toolbar help_report_bug_action->setWhatsThis(xi18n("Files a bug or wish for Kexi application.")); QAction* help_whats_this_action = d->helpMenu->action(KHelpMenu::menuWhatsThis); d->ac->addAction(help_whats_this_action->objectName(), help_whats_this_action); help_whats_this_action->setWhatsThis(xi18n("Activates a \"What's This?\" tool.")); QAction* help_contents_action = d->helpMenu->action(KHelpMenu::menuHelpContents); d->ac->addAction(help_contents_action->objectName(), help_contents_action); help_contents_action->setText(xi18n("Help")); help_contents_action->setWhatsThis(xi18n("Shows Kexi Handbook.")); QAction* help_about_app_action = d->helpMenu->action(KHelpMenu::menuAboutApp); d->ac->addAction(help_about_app_action->objectName(), help_about_app_action); help_about_app_action->setWhatsThis(xi18n("Shows information about Kexi application.")); QAction* help_about_kde_action = d->helpMenu->action(KHelpMenu::menuAboutKDE); d->ac->addAction(help_about_kde_action->objectName(), help_about_kde_action); help_about_kde_action->setWhatsThis(xi18n("Shows information about KDE.")); QAction* help_switch_language_action = d->helpMenu->action(KHelpMenu::menuSwitchLanguage); if (help_switch_language_action) { d->ac->addAction(help_switch_language_action->objectName(), help_switch_language_action); } // extra action such as help_donate may be confusing for the user or conflicting with existing so hide it QAction *extraAction = d->helpMenu->action(static_cast(KHelpMenu::menuSwitchLanguage + 1)); if (extraAction) { extraAction->setVisible(false); } QAction *action_show_help_menu = d->ac->action("help_show_menu"); KexiSmallToolButton *btn = new KexiSmallToolButton(koIcon("help-about"), QString(), helpWidget); btn->setToolButtonStyle(Qt::ToolButtonIconOnly); btn->setPopupMode(QToolButton::InstantPopup); btn->setToolTip(action_show_help_menu->toolTip()); btn->setWhatsThis(action_show_help_menu->whatsThis()); btn->setFocusPolicy(Qt::NoFocus); QStyleOptionToolButton opt; opt.initFrom(btn); int w = btn->sizeHint().width(); int wAdd = btn->style()->pixelMetric(QStyle::PM_MenuButtonIndicator, &opt, btn); if (w <= (2 * (wAdd + 1))) { w += wAdd + 2; } btn->setMinimumWidth(w); connect(action_show_help_menu, SIGNAL(triggered()), btn, SLOT(showMenu())); helpLyr->addWidget(btn); btn->setMenu(d->helpMenu->menu()); setCornerWidget(helpWidget, Qt::TopRightCorner); d->searchLineEdit = new KexiSearchLineEdit; kexiTester() << KexiTestObject(d->searchLineEdit, "globalSearch.lineEdit"); d->searchLineEdit->installEventFilter(this); helpLyr->addWidget(d->searchLineEdit); // needed e.g. for Windows style to remove the toolbar's frame QWidget *dummyWidgetForMainMenu = new QWidget(this); dummyWidgetForMainMenu->setObjectName("kexi"); addTab(dummyWidgetForMainMenu, KAboutData::applicationData().displayName()); d->toolbarsVisibleForIndex.append(true); addTab(new QWidget(this), QString()); // dummy for spacer d->toolbarsVisibleForIndex.append(true); if (!userMode) { d->createWidgetToolBar = d->createToolBar("create", xi18n("Create")); } tbar = d->createToolBar("data", xi18n("Data")); addAction(tbar, "edit_cut"); addAction(tbar, "edit_copy"); addAction(tbar, "edit_paste"); if (!userMode) addAction(tbar, "edit_paste_special_data_table"); //! @todo move undo/redo to quickbar: tbar = d->createToolBar("external", xi18n("External Data")); if (!userMode) { addAction(tbar, "project_import_data_table"); addAction(tbar, "tools_import_tables"); } tbar = d->createToolBar("tools", xi18n("Tools")); addAction(tbar, "tools_compact_database"); //! @todo move to form plugin tbar = d->createToolBar("form", xi18n("Form Design")); //! @todo move to report plugin tbar = d->createToolBar("report", xi18n("Report Design")); connect(this, SIGNAL(currentChanged(int)), this, SLOT(slotCurrentChanged(int))); setCurrentWidget(widget(KEXITABBEDTOOLBAR_SPACER_TAB_INDEX + 1)); // the default setFocusPolicy(Qt::NoFocus); } void KexiTabbedToolBar::Private::setCurrentTab(const QString& name) { q->setCurrentWidget(q->toolBar(name)); } void KexiTabbedToolBar::Private::hideTab(const QString& name) { q->removeTab(q->indexOf(toolbarsForName.value(name))); toolbarsVisibleForIndex[toolbarsIndexForName.value(name)] = false; } bool KexiTabbedToolBar::Private::isTabVisible(const QString& name) const { return q->indexOf(toolbarsForName.value(name)) != -1 && toolbarsVisibleForIndex[toolbarsIndexForName.value(name)]; } #ifndef NDEBUG void KexiTabbedToolBar::Private::debugToolbars() const { qDebug() << "QHash toolbarsForName:"; for (QHash::ConstIterator it(toolbarsForName.constBegin()); it!=toolbarsForName.constEnd(); ++it) { qDebug() << it.key() << "->" << it.value(); } qDebug() << "QHash toolbarsIndexForName:"; for (QHash::ConstIterator it(toolbarsIndexForName.constBegin()); it!=toolbarsIndexForName.constEnd(); ++it) { qDebug() << it.key() << "->" << it.value(); } qDebug() << "QHash toolbarsCaptionForName:"; for (QHash::ConstIterator it(toolbarsCaptionForName.constBegin()); it!=toolbarsCaptionForName.constEnd(); ++it) { qDebug() << it.key() << "->" << it.value(); } qDebug() << "QVector toolbarsVisibleForIndex:"; for (int i = 0; i < toolbarsVisibleForIndex.size(); i++) { qDebug() << i << "->" << toolbarsVisibleForIndex[i]; } } #endif void KexiTabbedToolBar::Private::showTab(const QString& name) { // qDebug() << "name:" << name; // qDebug() << "toolbarsForName.value(name):" << toolbarsForName.value(name); // qDebug() << "toolbarsIndexForName.value(name):" << toolbarsIndexForName.value(name); // qDebug() << "q->indexOf(toolbarsForName.value(name))" << q->indexOf(toolbarsForName.value(name)); #ifndef NDEBUG //debugToolbars(); #endif if (q->indexOf(toolbarsForName.value(name)) == -1) { int h = 0; // count h = invisible tabs before this for (int i = lowestIndex; i < toolbarsIndexForName.value(name); i++) { if (!toolbarsVisibleForIndex.at(i)) h++; } q->insertTab(toolbarsIndexForName.value(name) - h, toolbarsForName.value(name), toolbarsCaptionForName.value(name)); toolbarsVisibleForIndex[toolbarsIndexForName.value(name)] = true; } } // --- KexiTabbedToolBar::~KexiTabbedToolBar() { delete d; } bool KexiTabbedToolBar::mainMenuVisible() const { return d->mainMenu && d->mainMenu->isVisible(); } QRect KexiTabbedToolBar::tabRect(int index) const { return tabBar()->tabRect(index); } KHelpMenu* KexiTabbedToolBar::helpMenu() const { return d->helpMenu; } void KexiTabbedToolBar::slotSettingsChanged(int category) { Q_UNUSED(category); //! @todo if (category == KGlobalSettings::FontChanged) { //! @todo KEXI3 KGlobalSettings::menuFont() not available (using QFontDatabase::systemFont(QFontDatabase::GeneralFont) for now) //! https://community.kde.org/Frameworks/Porting_Notes#Global_Settings setFont(QFontDatabase::systemFont(QFontDatabase::GeneralFont)); // the toolbar acts like a menu //} } KToolBar* KexiTabbedToolBar::createWidgetToolBar() const { return d->createWidgetToolBar; } void KexiTabbedToolBar::mouseMoveEvent(QMouseEvent* event) { #ifdef KEXI_AUTORISE_TABBED_TOOLBAR QPoint p = event->pos(); int tab = tabBar()->tabAt(p); if (d->tabToRaise != -1 && (tab == -1 || tab == currentIndex())) { d->tabRaiseTimer.stop(); d->tabToRaise = -1; } else if (d->tabToRaise != tab) { d->tabRaiseTimer.start(); d->tabToRaise = tab; } #endif QTabWidget::mouseMoveEvent(event); } void KexiTabbedToolBar::leaveEvent(QEvent* event) { #ifdef KEXI_AUTORISE_TABBED_TOOLBAR d->tabRaiseTimer.stop(); d->tabToRaise = -1; #endif QTabWidget::leaveEvent(event); } bool KexiTabbedToolBar::eventFilter(QObject* watched, QEvent* event) { switch (event->type()) { case QEvent::MouseButtonPress: { QWidget *mainWin = KexiMainWindowIface::global()->thisWidget(); // qDebug() << "MouseButtonPress: watched:" << watched << "window()->focusWidget():" << window()->focusWidget(); if (watched == d->searchLineEdit) { activateSearchLineEdit(); // custom setFocus() for search box, so it's possible to focus // back on Escape key press return false; } else if (watched == tabBar()) { QMouseEvent* me = static_cast(event); QPoint p = me->pos(); KexiTabbedToolBarTabBar *tb = static_cast(tabBar()); int index = tb->tabAt(p); if (index == 0) { d->toggleMainMenu(); return true; } d->hideMainMenu(); if (index == KEXITABBEDTOOLBAR_SPACER_TAB_INDEX) { return true; } } else if (watched == mainWin && d->mainMenu) { QMouseEvent* me = static_cast(event); if (!QRect(d->mainMenu->mapToGlobal(QPoint(0,0)), d->mainMenu->size()) .contains(mainWin->mapToGlobal(me->pos()))) { // hide if clicked outside of the menu d->hideMainMenu(); } } } break; case QEvent::KeyPress: { QKeyEvent* ke = static_cast(event); // qDebug() << "**********" << QString::number(ke->key(), 16) // << QKeySequence::mnemonic(tabText(0))[0]; if (QKeySequence::mnemonic(tabText(0)) == QKeySequence(ke->key())) { // qDebug() << "eat the &File accel"; if (!d->mainMenu || !d->mainMenu->isVisible()) { d->showMainMenu(); } /*this could be unexpected: else if (d->mainMenu && d->mainMenu->isVisible()) { d->hideMainMenu(); }*/ return true; } if (d->mainMenu && d->mainMenu->isVisible() && (ke->key() == Qt::Key_Escape) && ke->modifiers() == Qt::NoModifier) { d->hideContentsOrMainMenu(); return true; } break; } case QEvent::Resize: if (watched == KexiMainWindowIface::global()->thisWidget()) { d->updateMainMenuGeometry(); } break; case QEvent::Shortcut: { QShortcutEvent *se = static_cast(event); if (watched == tabBar() && QKeySequence::mnemonic(tabText(0)) == se->key()) { // qDebug() << "eat the &File accel"; if (!d->mainMenu || !d->mainMenu->isVisible()) { d->showMainMenu(); return true; } } break; } default:; } return QTabWidget::eventFilter(watched, event); } void KexiTabbedToolBar::slotCurrentChanged(int index) { if (index == indexOf(d->createWidgetToolBar) && index != -1) { if (d->createWidgetToolBar->actions().isEmpty()) { QTimer::singleShot(10, this, SLOT(setupCreateWidgetToolbar())); } } if (d->rolledUp) { // switching the tab rolls down slotTabDoubleClicked(index); } if (index == 0) { // main menu d->showMainMenu(); } else { d->hideMainMenu(); } } void KexiTabbedToolBar::slotTabDoubleClicked(int index) { if (index <= 0) { return; // main item does not count here } d->rolledUp = !d->rolledUp; d->tabBarAnimation.stop(); QWidget *w = widget(index); if (!w) { return; } w->setGraphicsEffect(&d->tabBarOpacityEffect); if (d->rolledUp) { d->tabBarOpacityEffect.setOpacity(1.0); d->tabBarAnimation.setTargetObject(&d->tabBarOpacityEffect); d->tabBarAnimation.setStartValue(1.0); d->tabBarAnimation.setEndValue(0.0); d->tabBarAnimation.start(); } else { // roll down d->tabBarOpacityEffect.setOpacity(0.0); setMaximumHeight(QWIDGETSIZE_MAX); widget(d->rolledUpIndex)->show(); widget(d->rolledUpIndex)->setMaximumHeight(QWIDGETSIZE_MAX); w->setMaximumHeight(QWIDGETSIZE_MAX); w->show(); d->tabBarAnimation.setTargetObject(&d->tabBarOpacityEffect); d->tabBarAnimation.setStartValue(0.0); d->tabBarAnimation.setEndValue(1.0); d->tabBarAnimation.start(); } } void KexiTabbedToolBar::tabBarAnimationFinished() { if (d->rolledUp) { // hide and collapse the area widget(currentIndex())->hide(); KexiTabbedToolBarTabBar *tb = static_cast(tabBar()); setFixedHeight(tb->tabSizeHint(currentIndex()).height()); widget(currentIndex())->setFixedHeight(0); d->rolledUpIndex = currentIndex(); } } void KexiTabbedToolBar::setupCreateWidgetToolbar() { if (!d->createWidgetToolBar->actions().isEmpty()) return; //! @todo separate core object types from custom.... KexiPart::PartInfoList *plist = Kexi::partManager().infoList(); //this list is properly sorted if (plist) { foreach(KexiPart::Info *info, *plist) { QAction* a = info->newObjectAction(); if (a) { d->createWidgetToolBar->addAction(a); } else { //! @todo err } } } } void KexiTabbedToolBar::slotDelayedTabRaise() { #ifdef KEXI_AUTORISE_TABBED_TOOLBAR QPoint p = mapFromGlobal(QCursor::pos()); // make sure cursor is still over the tab int tab = tabBar()->tabAt(p); if (tab != d->tabToRaise) { d->tabToRaise = -1; } else if (d->tabToRaise != -1) { setCurrentIndex(d->tabToRaise); d->tabToRaise = -1; } #endif } KToolBar *KexiTabbedToolBar::toolBar(const QString& name) const { return d->toolbarsForName[name]; } void KexiTabbedToolBar::addAction(KToolBar *tbar, const char* actionName) { QAction *a = d->ac->action(actionName); if (a) tbar->addAction(a); } void KexiTabbedToolBar::addAction(const QString& toolBarName, QAction *action) { if (!action) return; KToolBar *tbar = d->toolbarsForName[toolBarName]; if (!tbar) return; tbar->addAction(action); } void KexiTabbedToolBar::addSeparatorAndAction(KToolBar *tbar, const char* actionName) { QAction *a = d->ac->action(actionName); if (a) { tbar->addSeparator(); tbar->addAction(a); } } void KexiTabbedToolBar::appendWidgetToToolbar(const QString& name, QWidget* widget) { KToolBar *tbar = d->toolbarsForName[name]; if (!tbar) { return; } QAction *action = tbar->addWidget(widget); d->extraActions.insert(widget, action); } void KexiTabbedToolBar::setWidgetVisibleInToolbar(QWidget* widget, bool visible) { QAction *action = d->extraActions[widget]; if (!action) { return; } action->setVisible(visible); } void KexiTabbedToolBar::showMainMenu(const char* actionName) { d->showMainMenu(actionName); } void KexiTabbedToolBar::hideMainMenu() { d->hideMainMenu(); } void KexiTabbedToolBar::toggleMainMenu() { d->toggleMainMenu(); } void KexiTabbedToolBar::setMainMenuContent(QWidget *w) { d->mainMenu->setContent(w); } void KexiTabbedToolBar::selectMainMenuItem(const char *actionName) { if (actionName) { KActionCollection *ac = KexiMainWindowIface::global()->actionCollection(); KexiMenuWidgetAction *a = qobject_cast(ac->action(actionName)); if (a) { d->mainMenu->setPersistentlySelectedAction(a, true); } } } void KexiTabbedToolBar::addSearchableModel(KexiSearchableModel *model) { d->searchLineEdit->addSearchableModel(model); } KToolBar* KexiTabbedToolBar::createToolBar(const char* name, const QString& caption) { return d->createToolBar(name, caption); } void KexiTabbedToolBar::setCurrentTab(const QString& name) { //qDebug() << name; d->setCurrentTab(name); } void KexiTabbedToolBar::setCurrentTab(int index) { setCurrentIndex(d->lowestIndex + index); } void KexiTabbedToolBar::hideTab(const QString& name) { //qDebug() << name; d->hideTab(name); } void KexiTabbedToolBar::showTab(const QString& name) { //qDebug() << name; d->showTab(name); } bool KexiTabbedToolBar::isTabVisible(const QString& name) const { return d->isTabVisible(name); } bool KexiTabbedToolBar::isRolledUp() { return d->rolledUp; } void KexiTabbedToolBar::toggleRollDown() { slotTabDoubleClicked(-1);//use -1 just to rolldown/up the tabbar } // --- KexiMainWidget::KexiMainWidget() : KMainWindow(0, Qt::Widget) , m_mainWindow(0) { setupCentralWidget(); } KexiMainWidget::~KexiMainWidget() { } void KexiMainWidget::setParent(KexiMainWindow* mainWindow) { KMainWindow::setParent(mainWindow); m_mainWindow = mainWindow; } KexiMainWindowTabWidget* KexiMainWidget::tabWidget() const { return m_tabWidget; } void KexiMainWidget::setupCentralWidget() { QWidget *centralWidget = new QWidget(this); QVBoxLayout *centralWidgetLyr = new QVBoxLayout(centralWidget); m_tabWidget = new KexiMainWindowTabWidget(centralWidget, this); connect(m_tabWidget, SIGNAL(currentChanged(int)), this, SLOT(slotCurrentTabIndexChanged(int))); centralWidgetLyr->setContentsMargins(0, 0, 0, 0); centralWidgetLyr->setSpacing(0); centralWidgetLyr->addWidget(m_tabWidget); setCentralWidget(centralWidget); layout()->setContentsMargins(0, 0, 0, 0); layout()->setSpacing(0); } bool KexiMainWidget::queryClose() { return m_mainWindow ? m_mainWindow->queryClose() : true; } void KexiMainWidget::slotCurrentTabIndexChanged(int index) { KexiWindowContainer* cont = dynamic_cast(m_tabWidget->widget(index)); if (! cont || (KexiWindow*)m_previouslyActiveWindow == cont->window) return; if (m_mainWindow) m_mainWindow->activeWindowChanged(cont->window, (KexiWindow*)m_previouslyActiveWindow); m_previouslyActiveWindow = cont->window; emit currentTabIndexChanged(index); } //------------------------------------------ KexiMainWindow::Private::Private(KexiMainWindow* w) : wnd(w) { actionCollection = new KActionCollection(w); propEditor = 0; propEditorDockWidget = 0; navDockWidget = 0; propEditorTabWidget = 0; KexiProjectData *pdata = KexiStartupHandler::global()->projectData(); userMode = KexiStartupHandler::global()->forcedUserMode() /* <-- simply forced the user mode */ /* project has 'user mode' set as default and not 'design mode' override is found: */ || (pdata && pdata->userMode() && !KexiStartupHandler::global()->forcedDesignMode()); isProjectNavigatorVisible = KexiStartupHandler::global()->isProjectNavigatorVisible(); isMainMenuVisible = KexiStartupHandler::global()->isMainMenuVisible(); navigator = 0; prj = 0; config = KSharedConfig::openConfig(); nameDialog = 0; m_findDialog = 0; focus_before_popup = 0; action_show_nav = 0; action_show_propeditor = 0; action_activate_nav = 0; action_activate_propeditor = 0; action_welcome_projects_title_id = -1; action_welcome_connections_title_id = -1; forceWindowClosing = false; insideCloseWindow = false; #ifndef KEXI_NO_PENDING_DIALOGS actionToExecuteWhenPendingJobsAreFinished = NoAction; #endif propEditorDockSeparatorPos = -1; navDockSeparatorPos = -1; wasAutoOpen = false; windowExistedBeforeCloseProject = false; #ifndef KEXI_SHOW_UNIMPLEMENTED dummy_action = new KActionMenu(QString(), wnd); #endif forceShowProjectNavigatorOnCreation = false; forceHideProjectNavigatorOnCreation = false; navWasVisibleBeforeProjectClosing = false; saveSettingsForShowProjectNavigator = true; propertyEditorCollapsed = false; enable_slotPropertyEditorVisibilityChanged = true; migrateManager = 0; } KexiMainWindow::Private::~Private() { qDeleteAll(m_openedCustomObjectsForItem); } void KexiMainWindow::Private::insertWindow(KexiWindow *window) { //! @todo (threads) QMutexLocker dialogsLocker( &dialogsMutex ); windows.insert(window->id(), window); #ifndef KEXI_NO_PENDING_DIALOGS pendingWindows.remove(window->id()); #endif } bool KexiMainWindow::Private::windowContainerExistsFor(int identifier) const { return windowContainers.contains(identifier); } void KexiMainWindow::Private::setWindowContainerExistsFor(int identifier, bool set) { if (set) { windowContainers.insert(identifier); } else { windowContainers.remove(identifier); } } void KexiMainWindow::Private::updateWindowId(KexiWindow *window, int oldItemID) { //! @todo (threads) QMutexLocker dialogsLocker( &dialogsMutex ); windows.remove(oldItemID); #ifndef KEXI_NO_PENDING_DIALOGS pendingWindows.remove(oldItemID); #endif windows.insert(window->id(), window); } void KexiMainWindow::Private::removeWindow(int identifier) { //! @todo (threads) QMutexLocker dialogsLocker( &dialogsMutex ); windows.remove(identifier); } int KexiMainWindow::Private::openedWindowsCount() { //! @todo (threads) QMutexLocker dialogsLocker( &dialogsMutex ); return windows.count(); } //! Used in KexiMainWindowe::closeProject() void KexiMainWindow::Private::clearWindows() { //! @todo (threads) QMutexLocker dialogsLocker( &dialogsMutex ); windows.clear(); #ifndef KEXI_NO_PENDING_DIALOGS pendingWindows.clear(); #endif } void KexiMainWindow::Private::showStartProcessMsg(const QStringList& args) { wnd->showErrorMessage(xi18nc("@info", "Could not start %1 application.", QString::fromLatin1(KEXI_APP_NAME)), xi18nc("@info", "Command %1 failed.", args.join(" "))); } void KexiMainWindow::Private::updatePropEditorVisibility(Kexi::ViewMode viewMode, KexiPart::Info *info) { if (!propEditorDockWidget) return; KexiWindow *currentWindow = wnd->currentWindow(); if (!info && currentWindow) { info = currentWindow->part()->info(); } const bool visible = (viewMode == Kexi::DesignViewMode) && ((currentWindow && currentWindow->propertySet()) || (info && info->isPropertyEditorAlwaysVisibleInDesignMode())); //qDebug() << "visible == " << visible; enable_slotPropertyEditorVisibilityChanged = false; if (visible && propertyEditorCollapsed) { // used when we're switching back to a window with propeditor available but collapsed propEditorDockWidget->setVisible(!visible); setPropertyEditorTabBarVisible(true); } else { propEditorDockWidget->setVisible(visible); setPropertyEditorTabBarVisible(false); } enable_slotPropertyEditorVisibilityChanged = true; } void KexiMainWindow::Private::setTabBarVisible(KMultiTabBar::KMultiTabBarPosition position, int id, KexiDockWidget *dockWidget, bool visible) { KMultiTabBar *mtbar = multiTabBars.value(position); if (!mtbar) { return; } if (!visible) { mtbar->removeTab(id); } else if (!mtbar->tab(id)) { mtbar->appendTab(koIcon("document-properties"), id, dockWidget->tabText); KMultiTabBarTab *tab = mtbar->tab(id); QObject::connect(tab, SIGNAL(clicked(int)), wnd, SLOT(slotMultiTabBarTabClicked(int)), Qt::UniqueConnection); } } void KexiMainWindow::Private::setPropertyEditorTabBarVisible(bool visible) { setTabBarVisible(KMultiTabBar::Right, PROPERTY_EDITOR_TABBAR_ID, propEditorDockWidget, visible); } QObject *KexiMainWindow::Private::openedCustomObjectsForItem(KexiPart::Item* item, const char* name) { if (!item || !name) { qWarning() << "!item || !name"; return 0; } QByteArray key(QByteArray::number(item->identifier()) + name); return m_openedCustomObjectsForItem.value(key); } void KexiMainWindow::Private::addOpenedCustomObjectForItem(KexiPart::Item* item, QObject* object, const char* name) { QByteArray key(QByteArray::number(item->identifier()) + name); m_openedCustomObjectsForItem.insert(key, object); } KexiFindDialog *KexiMainWindow::Private::findDialog() { if (!m_findDialog) { m_findDialog = new KexiFindDialog(wnd); m_findDialog->setActions(action_edit_findnext, action_edit_findprev, action_edit_replace, action_edit_replace_all); } return m_findDialog; } void KexiMainWindow::Private::updateFindDialogContents(bool createIfDoesNotExist) { if (!wnd->currentWindow()) return; if (!createIfDoesNotExist && (!m_findDialog || !m_findDialog->isVisible())) return; KexiSearchAndReplaceViewInterface* iface = currentViewSupportingSearchAndReplaceInterface(); if (!iface) { if (m_findDialog) { m_findDialog->setButtonsEnabled(false); m_findDialog->setLookInColumnList(QStringList(), QStringList()); } return; } //! @todo use ->caption() here, depending on global settings related to displaying captions findDialog()->setObjectNameForCaption(wnd->currentWindow()->partItem()->name()); QStringList columnNames; QStringList columnCaptions; QString currentColumnName; // for 'look in' if (!iface->setupFindAndReplace(columnNames, columnCaptions, currentColumnName)) { m_findDialog->setButtonsEnabled(false); m_findDialog->setLookInColumnList(QStringList(), QStringList()); return; } m_findDialog->setButtonsEnabled(true); const QString prevColumnName(m_findDialog->currentLookInColumnName()); m_findDialog->setLookInColumnList(columnNames, columnCaptions); m_findDialog->setCurrentLookInColumnName(prevColumnName); } KexiView *KexiMainWindow::Private::currentViewSupportingAction(const char* actionName) const { if (!wnd->currentWindow()) return 0; KexiView *view = wnd->currentWindow()->selectedView(); if (!view) return 0; QAction *action = view->sharedAction(actionName); if (!action || !action->isEnabled()) return 0; return view; } KexiSearchAndReplaceViewInterface* KexiMainWindow::Private::currentViewSupportingSearchAndReplaceInterface() const { if (!wnd->currentWindow()) return 0; KexiView *view = wnd->currentWindow()->selectedView(); if (!view) return 0; return dynamic_cast(view); } tristate KexiMainWindow::Private::showProjectMigrationWizard( const QString& mimeType, const QString& databaseName, const KDbConnectionData *cdata) { //pass arguments QMap args; args.insert("mimeType", mimeType); args.insert("databaseName", databaseName); if (cdata) { //pass KDbConnectionData serialized as a string... QString str; KDbUtils::serializeMap(cdata->toMap(), &str); args.insert("connectionData", str); } QDialog *dlg = KexiInternalPart::createModalDialogInstance("org.kexi-project.migration", "migration", wnd, 0, &args); if (!dlg) return false; //error msg has been shown by KexiInternalPart const int result = dlg->exec(); delete dlg; if (result != QDialog::Accepted) return cancelled; //open imported project in a new Kexi instance QString destinationDatabaseName(args["destinationDatabaseName"]); QString fileName, destinationConnectionShortcut; if (!destinationDatabaseName.isEmpty()) { if (args.contains("destinationConnectionShortcut")) { // server-based destinationConnectionShortcut = args["destinationConnectionShortcut"]; } else { // file-based fileName = destinationDatabaseName; destinationDatabaseName.clear(); } tristate res = wnd->openProject(fileName, destinationConnectionShortcut, destinationDatabaseName); wnd->raise(); return res; } return true; } #ifndef KEXI_NO_PENDING_DIALOGS void KexiMainWindow::Private::executeActionWhenPendingJobsAreFinished() { ActionToExecuteWhenPendingJobsAreFinished a = actionToExecuteWhenPendingJobsAreFinished; actionToExecuteWhenPendingJobsAreFinished = NoAction; switch (a) { case QuitAction: qApp->quit(); break; case CloseProjectAction: wnd->closeProject(); break; default:; } } KexiWindow *KexiMainWindow::Private::openedWindowFor(const KexiPart::Item* item, PendingJobType &pendingType) { return openedWindowFor(item->identifier(), pendingType); } KexiWindow *KexiMainWindow::Private::openedWindowFor(int identifier, PendingJobType &pendingType) { //! @todo (threads) QMutexLocker dialogsLocker( &dialogsMutex ); QHash::ConstIterator it = pendingWindows.find(identifier); if (it == pendingWindows.end()) pendingType = NoJob; else pendingType = it.value(); if (pendingType == WindowOpeningJob) { return 0; } return windows.contains(identifier) ? (KexiWindow*)windows.value(identifier) : 0; } void KexiMainWindow::Private::addItemToPendingWindows(const KexiPart::Item* item, PendingJobType jobType) { //! @todo (threads) QMutexLocker dialogsLocker( &dialogsMutex ); pendingWindows.insert(item->identifier(), jobType); } bool KexiMainWindow::Private::pendingWindowsExist() { if (pendingWindows.begin() != pendingWindows.end()) qDebug() << pendingWindows.constBegin().key() << " " << (int)pendingWindows.constBegin().value(); //! @todo (threads) QMutexLocker dialogsLocker( &dialogsMutex ); return !pendingWindows.isEmpty(); } void KexiMainWindow::Private::removePendingWindow(int identifier) { //! @todo (threads) QMutexLocker dialogsLocker( &dialogsMutex ); pendingWindows.remove(identifier); } #else // KEXI_NO_PENDING_DIALOGS KexiWindow *KexiMainWindow::Private::openedWindowFor(int identifier) { //! @todo (threads) QMutexLocker dialogsLocker( &dialogsMutex ); return windows.contains(identifier) ? (KexiWindow*)windows.value(identifier) : 0; } #endif diff --git a/src/main/KexiMenuWidget.cpp b/src/main/KexiMenuWidget.cpp index 247ff808c..3df2fc4ae 100644 --- a/src/main/KexiMenuWidget.cpp +++ b/src/main/KexiMenuWidget.cpp @@ -1,3145 +1,3145 @@ /* This file is part of the KDE project Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). Copyright (C) 2011-2014 JarosÅ‚aw Staniek Based on qmenu.cpp from Qt 4.7 Based on oxygenhelper.cpp Copyright 2009-2010 Hugo Pereira Da Costa Copyright 2008 Long Huynh Huu Copyright 2007 Matthew Woehlke Copyright 2007 C. Boemann Copyright 2007 Fredrik Höglund 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 "KexiMenuWidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef QT_NO_ACCESSIBILITY # include #endif #ifndef QT_NO_WHATSTHIS # include #endif #include #include #include #include #include #include #include #include "KexiMenuWidget_p.h" const int calligraLogoPixmapInternalWidth = 100; const int calligraLogoPixmapInternalHeight = 71; const char calligraUrl[] = "https://www.calligra.org"; //! @todo KEXI3 port OxygenHelper #if 0 // from oxygenhelper.cpp: OxygenHelper::OxygenHelper() : _componentData("oxygen", 0, KComponentData::SkipMainComponentRegistration) { _config = _componentData.config(); _contrast = KColorScheme::contrastF(_config); // background contrast is calculated so that it is 0.9 // when KGlobalSettings contrast value of 0.7 _bgcontrast = qMin(1.0, 0.9*_contrast/0.7); } OxygenHelper::~OxygenHelper() { } bool OxygenHelper::lowThreshold(const QColor &color) { const quint32 key( color.rgba() ); ColorMap::iterator iter( m_lowThreshold.find( key ) ); if( iter != m_lowThreshold.end() ) return iter.value(); else { const QColor darker( KColorScheme::shade(color, KColorScheme::MidShade, 0.5 ) ); const bool result( KColorUtils::luma(darker) > KColorUtils::luma(color) ); m_lowThreshold.insert( key, result ); return result; } } bool OxygenHelper::highThreshold(const QColor &color) { const quint32 key( color.rgba() ); ColorMap::iterator iter( m_highThreshold.find( key ) ); if( iter != m_highThreshold.end() ) return iter.value(); else { const QColor lighter( KColorScheme::shade(color, KColorScheme::LightShade, 0.5 ) ); const bool result( KColorUtils::luma(lighter) < KColorUtils::luma(color) ); m_highThreshold.insert( key, result ); return result; } } const QColor& OxygenHelper::backgroundTopColor(const QColor &color) { const quint64 key( color.rgba() ); QColor* out( m_backgroundTopColorCache.object( key ) ); if( !out ) { if( lowThreshold(color) ) out = new QColor( KColorScheme::shade(color, KColorScheme::MidlightShade, 0.0) ); else { const qreal my( KColorUtils::luma( KColorScheme::shade(color, KColorScheme::LightShade, 0.0) ) ); const qreal by( KColorUtils::luma(color) ); out = new QColor( KColorUtils::shade(color, (my - by) * _bgcontrast) ); } m_backgroundTopColorCache.insert( key, out ); } return *out; } const QColor& OxygenHelper::backgroundBottomColor(const QColor &color) { const quint64 key( color.rgba() ); QColor* out( m_backgroundBottomColorCache.object( key ) ); if( !out ) { const QColor midColor( KColorScheme::shade(color, KColorScheme::MidShade, 0.0) ); if( lowThreshold(color) ) out = new QColor( midColor ); else { const qreal by( KColorUtils::luma(color) ); const qreal my( KColorUtils::luma(midColor) ); out = new QColor( KColorUtils::shade(color, (my - by) * _bgcontrast) ); } m_backgroundBottomColorCache.insert( key, out ); } return *out; } const QColor& OxygenHelper::backgroundRadialColor(const QColor &color) { const quint64 key( color.rgba() ); QColor* out( m_backgroundRadialColorCache.object( key ) ); if( !out ) { if( lowThreshold(color) ) out = new QColor( KColorScheme::shade(color, KColorScheme::LightShade, 0.0) ); else if( highThreshold( color ) ) out = new QColor( color ); else out = new QColor( KColorScheme::shade(color, KColorScheme::LightShade, _bgcontrast) ); m_backgroundRadialColorCache.insert( key, out ); } return *out; } QPixmap OxygenHelper::verticalGradient(const QColor &color, int height, int offset) { const quint64 key( (quint64(color.rgba()) << 32) | height | 0x8000 ); QPixmap *pixmap( m_backgroundCache.object( key ) ); if (!pixmap) { pixmap = new QPixmap(1, height); pixmap->fill( Qt::transparent ); QLinearGradient gradient(0, offset, 0, height+offset); gradient.setColorAt(0.0, backgroundTopColor(color)); gradient.setColorAt(0.5, color); gradient.setColorAt(1.0, backgroundBottomColor(color)); QPainter p(pixmap); p.setCompositionMode(QPainter::CompositionMode_Source); p.fillRect(pixmap->rect(), gradient); p.end(); m_backgroundCache.insert(key, pixmap); } return *pixmap; } QPixmap OxygenHelper::radialGradient(const QColor &color, int width, int height) { const quint64 key( ( quint64(color.rgba()) << 32) | width | 0xb000 ); QPixmap *pixmap( m_backgroundCache.object( key ) ); if (!pixmap) { pixmap = new QPixmap(width, height); pixmap->fill(Qt::transparent); QColor radialColor = backgroundRadialColor(color); radialColor.setAlpha(255); QRadialGradient gradient(64, height-64, 64); gradient.setColorAt(0, radialColor); radialColor.setAlpha(101); gradient.setColorAt(0.5, radialColor); radialColor.setAlpha(37); gradient.setColorAt(0.75, radialColor); radialColor.setAlpha(0); gradient.setColorAt(1, radialColor); QPainter p(pixmap); p.scale(width/128.0,1); p.fillRect(QRect(0,0,128,height), gradient); p.end(); m_backgroundCache.insert(key, pixmap); } return *pixmap; } void OxygenHelper::renderOxygenWindowBackground(QPainter *p, const QRect &clipRect, const QWidget *widget, const QWidget* window, const QColor& color, int y_shift, int gradientHeight) { // get coordinates relative to the client area // this is stupid. One could use mapTo if this was taking const QWidget* and not // QWidget* as argument. const QWidget* w( widget ); int x(0); int y(-y_shift); while ( w != window && !w->isWindow() && w != w->parentWidget() ) { x += w->geometry().x(); y += w->geometry().y(); w = w->parentWidget(); } if (clipRect.isValid()) { p->save(); p->setClipRegion(clipRect,Qt::IntersectClip); } // calculate upper part height // special tricks are needed // to handle both window contents and window decoration const QRect r = window->rect(); int height( window->frameGeometry().height() ); int width( window->frameGeometry().width() ); if( y_shift > 0 ) { height -= 2*y_shift; width -= 2*y_shift; } const int splitY( qMin(300, (3*height)/4) ); // draw upper linear gradient const QRect upperRect(-x, -y, r.width(), splitY); QPixmap tile( verticalGradient(color, splitY, gradientHeight-64) ); p->drawTiledPixmap(upperRect, tile); // draw lower flat part const QRect lowerRect(-x, splitY-y, r.width(), r.height() - splitY-y_shift); p->fillRect(lowerRect, backgroundBottomColor(color)); // draw upper radial gradient const int radialW( qMin(600, width) ); const QRect radialRect( (r.width() - radialW) / 2-x, -y, radialW, gradientHeight); if (clipRect.intersects(radialRect)) { tile = radialGradient(color, radialW, gradientHeight); p->drawPixmap(radialRect, tile); } if (clipRect.isValid()) { p->restore(); } } //!@name window background gradients //@{ /*! \par y_shift: shift the background gradient upwards, to fit with the windec \par gradientHeight: the height of the generated gradient. for different heights, the gradient is translated so that it is always at the same position from the bottom */ void OxygenHelper::renderWindowBackground(QPainter *p, const QRect &clipRect, const QWidget *widget, const QPalette & pal, int y_shift, int gradientHeight) { renderOxygenWindowBackground( p, clipRect, widget, widget->window(), pal.color( widget->window()->backgroundRole() ), y_shift, gradientHeight ); } void OxygenHelper::renderMenuBackground( QPainter* p, const QRect& clipRect, const QWidget* widget, const QPalette& pal) { renderMenuBackground( p, clipRect, widget, pal.color( widget->window()->backgroundRole() ) ); } void OxygenHelper::renderMenuBackground( QPainter* p, const QRect& clipRect, const QWidget* widget, const QColor& color ) { // get coordinates relative to the client area // this is stupid. One could use mapTo if this was taking const QWidget* and not // QWidget* as argument. const QWidget* w( widget ); int x(0); int y(0); while( !w->isWindow() && w != w->parentWidget() ) { x += w->geometry().x(); y += w->geometry().y(); w = w->parentWidget(); } if (clipRect.isValid()) { p->save(); p->setClipRegion(clipRect,Qt::IntersectClip); } // calculate upper part height // special tricks are needed // to handle both window contents and window decoration QRect r = w->rect(); const int height( w->frameGeometry().height() ); const int splitY( qMin(200, (3*height)/4) ); const QRect upperRect( QRect(0, 0, r.width(), splitY) ); const QPixmap tile( verticalGradient(color, splitY) ); p->drawTiledPixmap(upperRect, tile); const QRect lowerRect( 0,splitY, r.width(), r.height() - splitY ); p->fillRect(lowerRect, backgroundBottomColor(color)); if (clipRect.isValid()) { p->restore(); } } // #endif // 0 class KexiMenuWidgetActionPrivate { public: KexiMenuWidgetActionPrivate() : persistentlySelected(false) { } bool persistentlySelected; }; KexiMenuWidgetAction::KexiMenuWidgetAction(QObject *parent) : QAction(parent) , d(new KexiMenuWidgetActionPrivate) { } KexiMenuWidgetAction::KexiMenuWidgetAction(const QString &text, QObject *parent) : QAction(text, parent) , d(new KexiMenuWidgetActionPrivate) { } KexiMenuWidgetAction::KexiMenuWidgetAction(const QIcon &icon, const QString &text, QObject *parent) : QAction(icon, text, parent) , d(new KexiMenuWidgetActionPrivate) { } KexiMenuWidgetAction::KexiMenuWidgetAction(KStandardAction::StandardAction id, QObject *parent) : QAction(parent) , d(new KexiMenuWidgetActionPrivate) { QScopedPointer tmp(KStandardAction::create(id, 0, 0, 0)); setIcon(tmp->icon()); setText(tmp->text()); setShortcut(tmp->shortcut()); setToolTip(tmp->toolTip()); } KexiMenuWidgetAction::~KexiMenuWidgetAction() { } void KexiMenuWidgetAction::setPersistentlySelected(bool set) { if (set == d->persistentlySelected) return; //qDebug() << "^^^^" << objectName() << set; d->persistentlySelected = set; } bool KexiMenuWidgetAction::persistentlySelected() const { return d->persistentlySelected; } KexiMenuWidget *KexiMenuWidgetPrivate::mouseDown = 0; int KexiMenuWidgetPrivate::sloppyDelayTimer = 0; void KexiMenuWidgetPrivate::init() { //! @todo KEXI3 port OxygenHelper #if 0 oxygenHelper = q->style()->objectName() == "oxygen" ? new OxygenHelper : 0; bespin = oxygenHelper ? false : q->style()->objectName() == "bespin"; qtcurve = oxygenHelper ? false : q->style()->objectName() == "qtcurve"; #else bespin = q->style()->objectName() == "bespin"; qtcurve = q->style()->objectName() == "qtcurve"; #endif #ifndef QT_NO_WHATSTHIS //q->setAttribute(Qt::WA_CustomWhatsThis); #endif //q->setAttribute(Qt::WA_X11NetWmWindowTypePopupMenu); defaultMenuAction = menuAction = new QAction(q); //menuAction->d_func()->menu = q; q->setMouseTracking(q->style()->styleHint(QStyle::SH_Menu_MouseTracking, 0, q)); if (q->style()->styleHint(QStyle::SH_Menu_Scrollable, 0, q)) { scroll = new KexiMenuWidgetPrivate::QMenuScroller; scroll->scrollFlags = KexiMenuWidgetPrivate::QMenuScroller::ScrollNone; } #ifdef QT_SOFTKEYS_ENABLED selectAction = QSoftKeyManager::createKeyedAction(QSoftKeyManager::SelectSoftKey, Qt::Key_Select, q); cancelAction = QSoftKeyManager::createKeyedAction(QSoftKeyManager::CancelSoftKey, Qt::Key_Back, q); selectAction->setPriority(QAction::HighPriority); cancelAction->setPriority(QAction::HighPriority); q->addAction(selectAction); q->addAction(cancelAction); #endif q->setFocusPolicy(Qt::StrongFocus); } int KexiMenuWidgetPrivate::scrollerHeight() const { return qMax(QApplication::globalStrut().height(), q->style()->pixelMetric(QStyle::PM_MenuScrollerHeight, 0, q)); } //Windows and KDE allows menus to cover the taskbar, while GNOME and Mac don't QRect KexiMenuWidgetPrivate::popupGeometry(const QWidget *widget) const { return QApplication::desktop()->screenGeometry(widget); } //Windows and KDE allows menus to cover the taskbar, while GNOME and Mac don't QRect KexiMenuWidgetPrivate::popupGeometry(int screen) const { return QApplication::desktop()->screenGeometry(screen); } QList > KexiMenuWidgetPrivate::calcCausedStack() const { QList > ret; for(QWidget *widget = causedPopup.widget; widget; ) { ret.append(widget); if (KexiMenuWidget *qmenu = qobject_cast(widget)) widget = qmenu->causedPopup().widget; else break; } return ret; } void KexiMenuWidgetPrivate::updateActionRects() const { if (!itemsDirty) return; q->ensurePolished(); //let's reinitialize the buffer QList actionsList = q->actions(); actionRects.resize(actionsList.count()); actionRects.fill(QRect()); //let's try to get the last visible action int lastVisibleAction = actionsList.count() - 1; for(;lastVisibleAction >= 0; --lastVisibleAction) { const QAction *action = actionsList.at(lastVisibleAction); if (action->isVisible()) { //removing trailing separators if (action->isSeparator() && collapsibleSeparators) continue; break; } } int max_column_width = 0, dh = popupGeometry(q).height(), y = 0; QStyle *style = q->style(); QStyleOption opt; opt.initFrom(q); const int hmargin = style->pixelMetric(QStyle::PM_MenuHMargin, &opt, q), vmargin = style->pixelMetric(QStyle::PM_MenuVMargin, &opt, q), icone = KIconLoader::SizeMedium; //! @todo KEXI3 adjust this size for smaller displays //style->pixelMetric(QStyle::PM_SmallIconSize, &opt, q); const int fw = style->pixelMetric(QStyle::PM_MenuPanelWidth, &opt, q); const int deskFw = frameWidth(&opt); //const int tearoffHeight = tearoff ? style->pixelMetric(QStyle::PM_MenuTearoffHeight, &opt, q) : 0; //for compatibility now - will have to refactor this away tabWidth = 0; maxIconWidth = 0; hasCheckableItems = false; ncols = 1; sloppyAction = 0; for (int i = 0; i < actionsList.count(); ++i) { QAction *action = actionsList.at(i); if (action->isSeparator() || !action->isVisible() || widgetItems.contains(action)) continue; //..and some members hasCheckableItems |= action->isCheckable(); QIcon is = action->icon(); if (!is.isNull()) { maxIconWidth = qMax(maxIconWidth, icone + 4); } } //calculate size QFontMetrics qfm = q->fontMetrics(); bool previousWasSeparator = true; // this is true to allow removing the leading separators for(int i = 0; i <= lastVisibleAction; i++) { QAction *action = actionsList.at(i); if (!action->isVisible() || (collapsibleSeparators && previousWasSeparator && action->isSeparator())) continue; // we continue, this action will get an empty QRect previousWasSeparator = action->isSeparator(); //let the style modify the above size.. QStyleOptionMenuItem opt; q->initStyleOption(&opt, action); const QFontMetrics &fm = opt.fontMetrics; QSize sz; if (QWidget *w = widgetItems.value(action)) { sz = w->sizeHint().expandedTo(w->minimumSize()).expandedTo(w->minimumSizeHint()).boundedTo(w->maximumSize()); } else { //calc what I think the size is.. if (action->isSeparator()) { sz = QSize(2, 2); } else { QString s = action->text(); int t = s.indexOf(QLatin1Char('\t')); if (t != -1) { tabWidth = qMax(int(tabWidth), qfm.width(s.mid(t+1))); s = s.left(t); #ifndef QT_NO_SHORTCUT } else { QKeySequence seq = action->shortcut(); if (!seq.isEmpty()) tabWidth = qMax(int(tabWidth), qfm.width(seq.toString())); #endif } sz.setWidth(fm.boundingRect(QRect(), Qt::TextSingleLine | Qt::TextShowMnemonic, s).width()); sz.setHeight(qMax(fm.height(), qfm.height())); //(not need because iconless items have to be of the same size as icon ones): if (!is.isNull()) { QSize is_sz = QSize(icone, icone); if (is_sz.height() > sz.height()) sz.setHeight(is_sz.height()); //} } sz = style->sizeFromContents(QStyle::CT_MenuItem, &opt, sz, q); } if (!sz.isEmpty()) { max_column_width = qMax(max_column_width, sz.width()); //wrapping if (!scroll && y+sz.height()+vmargin > dh - (deskFw * 2)) { ncols++; y = vmargin; } y += sz.height(); //update the item actionRects[i] = QRect(0, 0, sz.width(), sz.height()); } } max_column_width += tabWidth; //finally add in the tab width const int sfcMargin = style->sizeFromContents(QStyle::CT_Menu, &opt, QApplication::globalStrut(), q).width() - QApplication::globalStrut().width(); int leftmargin, topmargin, rightmargin, bottommargin; q->getContentsMargins(&leftmargin, &topmargin, &rightmargin, &bottommargin); const int min_column_width = q->minimumWidth() - (sfcMargin + leftmargin + rightmargin + 2 * (fw + hmargin)); max_column_width = qMax(min_column_width, max_column_width); //calculate position const int base_y = vmargin + fw + topmargin + (scroll ? scroll->scrollOffset : 0) /*+ tearoffHeight*/; int x = hmargin + fw + leftmargin; y = base_y; for(int i = 0; i < actionsList.count(); i++) { QRect &rect = actionRects[i]; if (rect.isNull()) continue; if (!scroll && y+rect.height() > dh - deskFw * 2) { x += max_column_width + hmargin; y = base_y; } rect.translate(x, y); //move rect.setWidth(max_column_width); //uniform width //we need to update the widgets geometry if (QWidget *widget = widgetItems.value(actionsList.at(i))) { widget->setGeometry(rect); widget->setVisible(actionsList.at(i)->isVisible()); } y += rect.height(); } itemsDirty = 0; } QRect KexiMenuWidgetPrivate::actionRect(QAction *act) const { QList actionsList = q->actions(); int index = actionsList.indexOf(act); if (index == -1) return QRect(); updateActionRects(); //we found the action return actionRects.at(index); } void KexiMenuWidgetPrivate::hideUpToMenuBar() { bool fadeMenus = q->style()->styleHint(QStyle::SH_Menu_FadeOutOnHide); QWidget *caused = causedPopup.widget; hideMenu(q); //hide after getting causedPopup while(caused) { if (KexiMenuWidget *m = qobject_cast(caused)) { caused = m->causedPopup().widget; hideMenu(m, fadeMenus); if (!fadeMenus) // Mac doesn't clear the action until after hidden. m->setCurrentAction(0); } else { caused = 0; } } setCurrentAction(0); } void KexiMenuWidgetPrivate::hideMenu(KexiMenuWidget *menu, bool justRegister) { Q_UNUSED(menu); Q_UNUSED(justRegister); } #if 0 void KexiMenuWidgetPrivate::popupAction(QAction *action, int delay, bool activateFirst) { Q_UNUSED(activateFirst); if (action && action->isEnabled()) { if (!delay) q->internalDelayedPopup(); else if (!menuDelayTimer.isActive() && (!action->menu() || !action->menu()->isVisible())) menuDelayTimer.start(delay, q); /* if (activateFirst && action->menu()) action->menu()->d_func()->setFirstActionActive();*/ } else if (KexiMenuWidget *menu = activeMenu) { //hide the current item activeMenu = 0; hideMenu(menu); } } #endif void KexiMenuWidgetPrivate::setSyncAction() { QAction *current = currentAction; if(current && (!current->isEnabled() || current->menu() || current->isSeparator())) current = 0; for(QWidget *caused = q; caused;) { if (KexiMenuWidget *m = qobject_cast(caused)) { caused = m->causedPopup().widget; if (m->eventLoop()) m->setSyncAction(current); // synchronous operation } else { break; } } } void KexiMenuWidgetPrivate::setFirstActionActive() { updateActionRects(); QList actionsList = q->actions(); for(int i = 0, saccum = 0; i < actionsList.count(); i++) { const QRect &rect = actionRects.at(i); if (rect.isNull()) continue; if (scroll && scroll->scrollFlags & QMenuScroller::ScrollUp) { saccum -= rect.height(); if (saccum > scroll->scrollOffset - scrollerHeight()) continue; } QAction *act = actionsList.at(i); if (!act->isSeparator() && (q->style()->styleHint(QStyle::SH_Menu_AllowActiveAndDisabled, 0, q) || act->isEnabled())) { setCurrentAction(act); break; } } } // popup == -1 means do not popup, 0 means immediately, others mean use a timer void KexiMenuWidgetPrivate::setCurrentAction(QAction *action, int popup, KexiMenuWidget::SelectionReason reason, bool activateFirst) { Q_UNUSED(activateFirst); //tearoffHighlighted = 0; if (currentAction) q->update(actionRect(currentAction)); sloppyAction = 0; if (!sloppyRegion.isEmpty()) sloppyRegion = QRegion(); KexiMenuWidget *hideActiveMenu = activeMenu; #ifndef QT_NO_STATUSTIP QAction *previousAction = currentAction; #endif currentAction = action; if (action) { if (!action->isSeparator()) { activateAction(action, QAction::Hover); /*if (popup != -1) { hideActiveMenu = 0; //will be done "later" // if the menu is visible then activate the required action, // otherwise we just mark the action as currentAction // and activate it when the menu will be popuped. if (q->isVisible()) popupAction(currentAction, popup, activateFirst); }*/ q->update(actionRect(action)); if (reason == KexiMenuWidget::SelectedFromKeyboard) { QWidget *widget = widgetItems.value(action); if (widget) { if (widget->focusPolicy() != Qt::NoFocus) widget->setFocus(Qt::TabFocusReason); } else { //when the action has no QWidget, the KexiMenuWidget itself should // get the focus // Since the menu is a pop-up, it uses the popup reason. if (!q->hasFocus()) { q->setFocus(Qt::PopupFocusReason); } } } } else { //action is a separator if (popup != -1) hideActiveMenu = 0; //will be done "later" } #ifndef QT_NO_STATUSTIP } else if (previousAction) { //previousAction->d_func()->showStatusText(topCausedWidget(), QString()); #endif } if (hideActiveMenu) { activeMenu = 0; hideMenu(hideActiveMenu); } } //return the top causedPopup.widget that is not a KexiMenuWidget QWidget *KexiMenuWidgetPrivate::topCausedWidget() const { QWidget* top = causedPopup.widget; while (KexiMenuWidget* m = qobject_cast(top)) top = m->causedPopup().widget; return top; } QAction *KexiMenuWidgetPrivate::actionAt(QPoint p) const { if (!q->rect().contains(p)) //sanity check return 0; QList actionsList = q->actions(); for(int i = 0; i < actionRects.count(); i++) { if (actionRects.at(i).contains(p)) return actionsList.at(i); } return 0; } void KexiMenuWidgetPrivate::setOverrideMenuAction(QAction *a) { QObject::disconnect(menuAction, SIGNAL(destroyed()), q, SLOT(overrideMenuActionDestroyed())); if (a) { menuAction = a; QObject::connect(a, SIGNAL(destroyed()), q, SLOT(overrideMenuActionDestroyed())); } else { //we revert back to the default action created by the KexiMenuWidget itself menuAction = defaultMenuAction; } } void KexiMenuWidget::overrideMenuActionDestroyed() { d->menuAction = d->defaultMenuAction; } void KexiMenuWidgetPrivate::updateLayoutDirection() { //we need to mimic the cause of the popup's layout direction //to allow setting it on a mainwindow for example //we call setLayoutDirection_helper to not overwrite a user-defined value if (!q->testAttribute(Qt::WA_SetLayoutDirection)) { if (QWidget *w = causedPopup.widget) setLayoutDirection_helper(w, w->layoutDirection()); else if (QWidget *w = q->parentWidget()) setLayoutDirection_helper(w, w->layoutDirection()); else setLayoutDirection_helper(w, QApplication::layoutDirection()); } } bool KexiMenuWidgetPrivate::actionPersistentlySelected(const QAction* action) const { const KexiMenuWidgetAction* kaction = qobject_cast(action); return kaction ? kaction->persistentlySelected() : false; } void KexiMenuWidgetPrivate::setActionPersistentlySelected(QAction* action, bool set) { KexiMenuWidgetAction* kaction = qobject_cast(action); if (previousPersistentlySelectedAction) { previousPersistentlySelectedAction->setPersistentlySelected(false); q->update(actionRect(previousPersistentlySelectedAction)); } if (kaction) kaction->setPersistentlySelected(set); previousPersistentlySelectedAction = kaction; } void KexiMenuWidgetPrivate::toggleActionPersistentlySelected(QAction* action) { KexiMenuWidgetAction* kaction = qobject_cast(action); if (!kaction) return; setActionPersistentlySelected(kaction, !kaction->persistentlySelected()); } /*! Returns the action associated with this menu. */ QAction *KexiMenuWidget::menuAction() const { return d->menuAction; } /*! \property KexiMenuWidget::title \brief The title of the menu This is equivalent to the QAction::text property of the menuAction(). By default, this property contains an empty string. */ QString KexiMenuWidget::title() const { return d->menuAction->text(); } void KexiMenuWidget::setTitle(const QString &text) { d->menuAction->setText(text); } /*! \property KexiMenuWidget::icon \brief The icon of the menu This is equivalent to the QAction::icon property of the menuAction(). By default, if no icon is explicitly set, this property contains a null icon. */ QIcon KexiMenuWidget::icon() const { return d->menuAction->icon(); } void KexiMenuWidget::setIcon(const QIcon &icon) { d->menuAction->setIcon(icon); } //actually performs the scrolling void KexiMenuWidgetPrivate::scrollMenu(QAction *action, QMenuScroller::ScrollLocation location, bool active) { Q_UNUSED(action) Q_UNUSED(location) Q_UNUSED(active) #if 0 if (!scroll || !scroll->scrollFlags) return; updateActionRects(); int newOffset = 0; const int topScroll = (scroll->scrollFlags & QMenuScroller::ScrollUp) ? scrollerHeight() : 0; const int botScroll = (scroll->scrollFlags & QMenuScroller::ScrollDown) ? scrollerHeight() : 0; const int vmargin = q->style()->pixelMetric(QStyle::PM_MenuVMargin, 0, q); const int fw = q->style()->pixelMetric(QStyle::PM_MenuPanelWidth, 0, q); QList actionsList = q->actions(); if (location == QMenuScroller::ScrollTop) { for(int i = 0, saccum = 0; i < actionsList.count(); i++) { if (actionsList.at(i) == action) { newOffset = topScroll - saccum; break; } saccum += actionRects.at(i).height(); } } else { for(int i = 0, saccum = 0; i < actionsList.count(); i++) { saccum += actionRects.at(i).height(); if (actionsList.at(i) == action) { if (location == QMenuScroller::ScrollCenter) newOffset = ((q->height() / 2) - botScroll) - (saccum - topScroll); else newOffset = (q->height() - botScroll) - saccum; break; } } if(newOffset) newOffset -= fw * 2; } //figure out which scroll flags int newScrollFlags = QMenuScroller::ScrollNone; if (newOffset < 0) //easy and cheap one newScrollFlags |= QMenuScroller::ScrollUp; int saccum = newOffset; for(int i = 0; i < actionRects.count(); i++) { saccum += actionRects.at(i).height(); if (saccum > q->height()) { newScrollFlags |= QMenuScroller::ScrollDown; break; } } if (!(newScrollFlags & QMenuScroller::ScrollDown) && (scroll->scrollFlags & QMenuScroller::ScrollDown)) { newOffset = q->height() - (saccum - newOffset) - fw*2 - vmargin; //last item at bottom } if (!(newScrollFlags & QMenuScroller::ScrollUp) && (scroll->scrollFlags & QMenuScroller::ScrollUp)) { newOffset = 0; //first item at top } if (newScrollFlags & QMenuScroller::ScrollUp) newOffset -= vmargin; QRect screen = popupGeometry(q); const int desktopFrame = frameWidth(); if (q->height() < screen.height()-(desktopFrame*2)-1) { QRect geom = q->geometry(); if (newOffset > scroll->scrollOffset && (scroll->scrollFlags & newScrollFlags & QMenuScroller::ScrollUp)) { //scroll up const int newHeight = geom.height()-(newOffset-scroll->scrollOffset); if(newHeight > geom.height()) geom.setHeight(newHeight); } else if(scroll->scrollFlags & newScrollFlags & QMenuScroller::ScrollDown) { int newTop = geom.top() + (newOffset-scroll->scrollOffset); if (newTop < desktopFrame+screen.top()) newTop = desktopFrame+screen.top(); if (newTop < geom.top()) { geom.setTop(newTop); newOffset = 0; newScrollFlags &= ~QMenuScroller::ScrollUp; } } if (geom.bottom() > screen.bottom() - desktopFrame) geom.setBottom(screen.bottom() - desktopFrame); if (geom.top() < desktopFrame+screen.top()) geom.setTop(desktopFrame+screen.top()); if (geom != q->geometry()) { #if 0 if (newScrollFlags & QMenuScroller::ScrollDown && q->geometry().top() - geom.top() >= -newOffset) newScrollFlags &= ~QMenuScroller::ScrollDown; #endif q->setGeometry(geom); } } //actually update flags const int delta = qMin(0, newOffset) - scroll->scrollOffset; //make sure the new offset is always negative if (!itemsDirty && delta) { //we've scrolled so we need to update the action rects for (int i = 0; i < actionRects.count(); ++i) { QRect ¤t = actionRects[i]; current.moveTop(current.top() + delta); //we need to update the widgets geometry if (QWidget *w = widgetItems.value(actionsList.at(i))) w->setGeometry(current); } } scroll->scrollOffset += delta; scroll->scrollFlags = newScrollFlags; if (active) setCurrentAction(action); q->update(); //issue an update so we see all the new state.. #endif } void KexiMenuWidgetPrivate::scrollMenu(QMenuScroller::ScrollLocation location, bool active) { Q_UNUSED(location) Q_UNUSED(active) #if 0 updateActionRects(); QList actionsList = q->actions(); if(location == QMenuScroller::ScrollBottom) { for(int i = actionsList.size()-1; i >= 0; --i) { QAction *act = actionsList.at(i); if (actionRects.at(i).isNull()) continue; if (!act->isSeparator() && (q->style()->styleHint(QStyle::SH_Menu_AllowActiveAndDisabled, 0, q) || act->isEnabled())) { if(scroll->scrollFlags & KexiMenuWidgetPrivate::QMenuScroller::ScrollDown) scrollMenu(act, KexiMenuWidgetPrivate::QMenuScroller::ScrollBottom, active); else if(active) setCurrentAction(act, /*popup*/-1, KexiMenuWidget::SelectedFromKeyboard); break; } } } else if(location == QMenuScroller::ScrollTop) { for(int i = 0; i < actionsList.size(); ++i) { QAction *act = actionsList.at(i); if (actionRects.at(i).isNull()) continue; if (!act->isSeparator() && (q->style()->styleHint(QStyle::SH_Menu_AllowActiveAndDisabled, 0, q) || act->isEnabled())) { if(scroll->scrollFlags & KexiMenuWidgetPrivate::QMenuScroller::ScrollUp) scrollMenu(act, KexiMenuWidgetPrivate::QMenuScroller::ScrollTop, active); else if(active) setCurrentAction(act, /*popup*/-1, KexiMenuWidget::SelectedFromKeyboard); break; } } } #endif } //only directional void KexiMenuWidgetPrivate::scrollMenu(QMenuScroller::ScrollDirection direction, bool page, bool active) { Q_UNUSED(direction) Q_UNUSED(page) Q_UNUSED(active) #if 0 if (!scroll || !(scroll->scrollFlags & direction)) //not really possible... return; updateActionRects(); const int topScroll = (scroll->scrollFlags & QMenuScroller::ScrollUp) ? scrollerHeight() : 0; const int botScroll = (scroll->scrollFlags & QMenuScroller::ScrollDown) ? scrollerHeight() : 0; const int vmargin = q->style()->pixelMetric(QStyle::PM_MenuVMargin, 0, q); const int fw = q->style()->pixelMetric(QStyle::PM_MenuPanelWidth, 0, q); const int offset = topScroll ? topScroll-vmargin : 0; QList actionsList = q->actions(); if (direction == QMenuScroller::ScrollUp) { for(int i = 0, saccum = 0; i < actionsList.count(); i++) { saccum -= actionRects.at(i).height(); if (saccum <= scroll->scrollOffset-offset) { scrollMenu(actionsList.at(i), page ? QMenuScroller::ScrollBottom : QMenuScroller::ScrollTop, active); break; } } } else if (direction == QMenuScroller::ScrollDown) { bool scrolled = false; for(int i = 0, saccum = 0; i < actionsList.count(); i++) { const int iHeight = actionRects.at(i).height(); saccum -= iHeight; if (saccum <= scroll->scrollOffset-offset) { const int scrollerArea = q->height() - botScroll - fw*2; int visible = (scroll->scrollOffset-offset) - saccum; for(i++ ; i < actionsList.count(); i++) { visible += actionRects.at(i).height(); if (visible > scrollerArea - topScroll) { scrolled = true; scrollMenu(actionsList.at(i), page ? QMenuScroller::ScrollTop : QMenuScroller::ScrollBottom, active); break; } } break; } } if(!scrolled) { scroll->scrollFlags &= ~QMenuScroller::ScrollDown; q->update(); } } #endif } /* This is poor-mans eventfilters. This avoids the use of eventFilter (which can be nasty for users of QMenuBar's). */ bool KexiMenuWidgetPrivate::mouseEventTaken(QMouseEvent *e) { QPoint pos = q->mapFromGlobal(e->globalPos()); if (scroll && !activeMenu) { //let the scroller "steal" the event bool isScroll = false; if (pos.x() >= 0 && pos.x() < q->width()) { for(int dir = QMenuScroller::ScrollUp; dir <= QMenuScroller::ScrollDown; dir = dir << 1) { if (scroll->scrollFlags & dir) { if (dir == QMenuScroller::ScrollUp) isScroll = (pos.y() <= scrollerHeight()); else if (dir == QMenuScroller::ScrollDown) isScroll = (pos.y() >= q->height() - scrollerHeight()); if (isScroll) { scroll->scrollDirection = dir; break; } } } } if (isScroll) { scroll->scrollTimer.start(50, q); return true; } else { scroll->scrollTimer.stop(); } } // if (tearoff) { //let the tear off thingie "steal" the event.. // QRect tearRect(0, 0, q->width(), q->style()->pixelMetric(QStyle::PM_MenuTearoffHeight, 0, q)); // if (scroll && scroll->scrollFlags & KexiMenuWidgetPrivate::QMenuScroller::ScrollUp) // tearRect.translate(0, scrollerHeight()); // q->update(tearRect); // if (tearRect.contains(pos) && hasMouseMoved(e->globalPos())) { // setCurrentAction(0); // tearoffHighlighted = 1; // if (e->type() == QEvent::MouseButtonRelease) { // hideUpToMenuBar(); // } // return true; // } // tearoffHighlighted = 0; // } if (q->frameGeometry().contains(e->globalPos())) //otherwise if the event is in our rect we want it.. return false; for(QWidget *caused = causedPopup.widget; caused;) { bool passOnEvent = false; QWidget *next_widget = 0; QPoint cpos = caused->mapFromGlobal(e->globalPos()); if (KexiMenuWidget *m = qobject_cast(caused)) { passOnEvent = m->rect().contains(cpos); next_widget = m->causedPopup().widget; } if (passOnEvent) { if(e->type() != QEvent::MouseButtonRelease || mouseDown == caused) { QMouseEvent new_e(e->type(), cpos, e->button(), e->buttons(), e->modifiers()); QApplication::sendEvent(caused, &new_e); return true; } } if (!next_widget) break; caused = next_widget; } return false; } void KexiMenuWidgetPrivate::activateCausedStack(const QList > &causedStack, QAction *action, QAction::ActionEvent action_e, bool self) { KexiUtils::BoolBlocker guard(&activationRecursionGuard, true); if(self) action->activate(action_e); for(int i = 0; i < causedStack.size(); ++i) { QPointer widget = causedStack.at(i); if (!widget) continue; //fire if (KexiMenuWidget *qmenu = qobject_cast(widget)) { widget = qmenu->causedPopup().widget; if (action_e == QAction::Trigger) { emit qmenu->triggered(action); } else if (action_e == QAction::Hover) { emit qmenu->hovered(action); } } } } void KexiMenuWidgetPrivate::activateAction(QAction *action, QAction::ActionEvent action_e, bool self) { #ifndef QT_NO_WHATSTHIS bool inWhatsThisMode = QWhatsThis::inWhatsThisMode(); #endif if (!action || !q->isEnabled() || (action_e == QAction::Trigger #ifndef QT_NO_WHATSTHIS && !inWhatsThisMode #endif && (action->isSeparator() ||!action->isEnabled()))) return; /* I have to save the caused stack here because it will be undone after popup execution (ie in the hide). Then I iterate over the list to actually send the events. --Sam */ const QList > causedStack = calcCausedStack(); if (action_e == QAction::Trigger) { #ifndef QT_NO_WHATSTHIS if (!inWhatsThisMode) actionAboutToTrigger = action; #endif if (q->testAttribute(Qt::WA_DontShowOnScreen)) { hideUpToMenuBar(); } else { for(QWidget *widget = QApplication::activePopupWidget(); widget; ) { if (KexiMenuWidget *qmenu = qobject_cast(widget)) { if(qmenu == q) hideUpToMenuBar(); widget = qmenu->causedPopup().widget; } else { break; } } } #ifndef QT_NO_WHATSTHIS if (inWhatsThisMode) { QString s = action->whatsThis(); if (s.isEmpty()) s = q->whatsThis(); QWhatsThis::showText(q->mapToGlobal(actionRect(action).center()), s, q); return; } #endif } activateCausedStack(causedStack, action, action_e, self); if (action_e == QAction::Hover) { #ifndef QT_NO_ACCESSIBILITY if (QAccessible::isActive()) { int actionIndex = indexOf(action) + 1; QAccessibleEvent focusEvent(q, QAccessible::Focus); focusEvent.setChild(actionIndex); QAccessible::updateAccessibility(&focusEvent); QAccessibleEvent selectionEvent(q, QAccessible::Selection); selectionEvent.setChild(actionIndex); QAccessible::updateAccessibility(&selectionEvent); } #endif action->showStatusText(topCausedWidget()); } else { actionAboutToTrigger = 0; } } void KexiMenuWidget::actionTriggered() { if (QAction *action = qobject_cast(sender())) { QPointer actionGuard = action; emit triggered(action); if (!d->activationRecursionGuard && actionGuard) { //in case the action has not been activated by the mouse //we check the parent hierarchy QList< QPointer > list; for(QWidget *widget = parentWidget(); widget; ) { if (qobject_cast(widget)) { list.append(widget); widget = widget->parentWidget(); } else { break; } } d->activateCausedStack(list, action, QAction::Trigger, false); } } } void KexiMenuWidget::actionHovered() { if (QAction * action = qobject_cast(sender())) { emit hovered(action); } } bool KexiMenuWidgetPrivate::hasMouseMoved(const QPoint &globalPos) { //determines if the mouse has moved (ie its initial position has //changed by more than QApplication::startDragDistance() //or if there were at least 6 mouse motions) return motions > 6 || QApplication::startDragDistance() < (mousePopupPos - globalPos).manhattanLength(); } // from QWidgetPrivate void KexiMenuWidgetPrivate::setLayoutDirection_helper(QWidget* w, Qt::LayoutDirection direction) { if (!w || (direction == Qt::RightToLeft) == w->testAttribute(Qt::WA_RightToLeft)) return; w->setAttribute(Qt::WA_RightToLeft, (direction == Qt::RightToLeft)); foreach (QObject *obj, w->children()) { QWidget *widget = qobject_cast(obj); if (widget && !widget->isWindow() && !widget->testAttribute(Qt::WA_SetLayoutDirection)) setLayoutDirection_helper(widget, direction); } QEvent e(QEvent::LayoutDirectionChange); QApplication::sendEvent(w, &e); } int KexiMenuWidgetPrivate::frameWidth(const QStyleOption* opt) const { if (!hasFrame) return 0; return q->style()->pixelMetric(QStyle::PM_MenuPanelWidth, opt, q); } bool KexiMenuWidget::hasFrame() const { return d->hasFrame; } void KexiMenuWidget::setFrame(bool set) { d->hasFrame = set; } // void QWidgetPrivate::resolveLayoutDirection() // { // Q_Q(const QWidget); // if (!q->testAttribute(Qt::WA_SetLayoutDirection)) // setLayoutDirection_helper(q->isWindow() ? QApplication::layoutDirection() : q->parentWidget()->layoutDirection()); // } /*! Initialize \a option with the values from this menu and information from \a action. This method is useful for subclasses when they need a QStyleOptionMenuItem, but don't want to fill in all the information themselves. \sa QStyleOption::initFrom() QMenuBar::initStyleOption() */ void KexiMenuWidget::initStyleOption(QStyleOptionMenuItem *option, const QAction *action) const { if (!option || !action) return; option->initFrom(this); option->palette = palette(); option->state = QStyle::State_None; if (window()->isActiveWindow()) option->state |= QStyle::State_Active; if (isEnabled() && action->isEnabled() && (!action->menu() || action->menu()->isEnabled())) option->state |= QStyle::State_Enabled; else option->palette.setCurrentColorGroup(QPalette::Disabled); option->font = action->font().resolve(font()); option->fontMetrics = QFontMetrics(option->font); if (d->currentAction && d->currentAction == action && !d->currentAction->isSeparator()) { option->state |= QStyle::State_Selected | (d->mouseDown ? QStyle::State_Sunken : QStyle::State_None); } option->menuHasCheckableItems = d->hasCheckableItems; if (!action->isCheckable()) { option->checkType = QStyleOptionMenuItem::NotCheckable; } else { option->checkType = (action->actionGroup() && action->actionGroup()->isExclusive()) ? QStyleOptionMenuItem::Exclusive : QStyleOptionMenuItem::NonExclusive; option->checked = action->isChecked(); } if (action->menu()) option->menuItemType = QStyleOptionMenuItem::SubMenu; else if (action->isSeparator()) option->menuItemType = QStyleOptionMenuItem::Separator; else if (d->defaultAction == action) option->menuItemType = QStyleOptionMenuItem::DefaultItem; else option->menuItemType = QStyleOptionMenuItem::Normal; if (action->isIconVisibleInMenu()) option->icon = action->icon(); QString textAndAccel = action->text(); #ifndef QT_NO_SHORTCUT if (textAndAccel.indexOf(QLatin1Char('\t')) == -1) { QKeySequence seq = action->shortcut(); if (!seq.isEmpty()) textAndAccel += QLatin1Char('\t') + seq.toString(); } #endif option->text = textAndAccel; option->tabWidth = d->tabWidth; option->maxIconWidth = d->maxIconWidth; option->menuRect = rect(); } /*! Constructs a menu with parent \a parent. Although a popup menu is always a top-level widget, if a parent is passed the popup menu will be deleted when that parent is destroyed (as with any other QObject). */ KexiMenuWidget::KexiMenuWidget(QWidget *parent) : QWidget(parent), d(new KexiMenuWidgetPrivate(this)) { d->init(); } /*! Constructs a menu with a \a title and a \a parent. Although a popup menu is always a top-level widget, if a parent is passed the popup menu will be deleted when that parent is destroyed (as with any other QObject). \sa title */ KexiMenuWidget::KexiMenuWidget(const QString &title, QWidget *parent) : QWidget(parent), d(new KexiMenuWidgetPrivate(this)) { d->init(); d->menuAction->setText(title); } /*! \internal */ // KexiMenuWidget::KexiMenuWidget(KexiMenuWidgetPrivate &dd, QWidget *parent) // : QWidget(dd, parent, Qt::Popup) // { // d->init(); // } /*! Destroys the menu. */ KexiMenuWidget::~KexiMenuWidget() { if (!d->widgetItems.isEmpty()) { // avoid detach on shared null hash QHash::iterator it = d->widgetItems.begin(); for (; it != d->widgetItems.end(); ++it) { if (QWidget *widget = it.value()) { QWidgetAction *action = static_cast(it.key()); action->releaseWidget(widget); *it = 0; } } } if (d->eventLoop) d->eventLoop->exit(); //hideTearOffMenu(); delete d; } KexiMenuWidgetAction* KexiMenuWidget::persistentlySelectedAction() const { if (d->previousPersistentlySelectedAction && d->previousPersistentlySelectedAction->persistentlySelected()) { return d->previousPersistentlySelectedAction; } return 0; } void KexiMenuWidget::setPersistentlySelectedAction(KexiMenuWidgetAction* action, bool set) { d->setActionPersistentlySelected(action, set); } const KexiMenuWidgetCaused& KexiMenuWidget::causedPopup() const { return d->causedPopup; } void KexiMenuWidget::setCurrentAction(QAction *a, int popup, KexiMenuWidget::SelectionReason reason, bool activateFirst) { d->setCurrentAction(a, popup, reason, activateFirst); } QEventLoop *KexiMenuWidget::eventLoop() const { return d->eventLoop; } void KexiMenuWidget::setSyncAction(QAction *a) { d->syncAction = a; } /*! \overload This convenience function creates a new action with \a text. The function adds the newly created action to the menu's list of actions, and returns it. \sa QWidget::addAction() */ QAction *KexiMenuWidget::addAction(const QString &text) { QAction *ret = new QAction(text, this); addAction(ret); return ret; } /*! \overload This convenience function creates a new action with an \a icon and some \a text. The function adds the newly created action to the menu's list of actions, and returns it. \sa QWidget::addAction() */ QAction *KexiMenuWidget::addAction(const QIcon &icon, const QString &text) { QAction *ret = new QAction(icon, text, this); addAction(ret); return ret; } /*! \overload This convenience function creates a new action with the text \a text and an optional shortcut \a shortcut. The action's \l{QAction::triggered()}{triggered()} signal is connected to the \a receiver's \a member slot. The function adds the newly created action to the menu's list of actions and returns it. \sa QWidget::addAction() */ QAction *KexiMenuWidget::addAction(const QString &text, const QObject *receiver, const char* member, const QKeySequence &shortcut) { QAction *action = new QAction(text, this); #ifdef QT_NO_SHORTCUT Q_UNUSED(shortcut); #else action->setShortcut(shortcut); #endif QObject::connect(action, SIGNAL(triggered(bool)), receiver, member); addAction(action); return action; } /*! \overload This convenience function creates a new action with an \a icon and some \a text and an optional shortcut \a shortcut. The action's \l{QAction::triggered()}{triggered()} signal is connected to the \a member slot of the \a receiver object. The function adds the newly created action to the menu's list of actions, and returns it. \sa QWidget::addAction() */ QAction *KexiMenuWidget::addAction(const QIcon &icon, const QString &text, const QObject *receiver, const char* member, const QKeySequence &shortcut) { QAction *action = new QAction(icon, text, this); #ifdef QT_NO_SHORTCUT Q_UNUSED(shortcut); #else action->setShortcut(shortcut); #endif QObject::connect(action, SIGNAL(triggered(bool)), receiver, member); addAction(action); return action; } /*! This convenience function creates a new separator action, i.e. an action with QAction::isSeparator() returning true, and adds the new action to this menu's list of actions. It returns the newly created action. \sa QWidget::addAction() */ QAction *KexiMenuWidget::addSeparator() { QAction *action = new QAction(this); action->setSeparator(true); addAction(action); return action; } /*! This convenience function creates a new separator action, i.e. an action with QAction::isSeparator() returning true. The function inserts the newly created action into this menu's list of actions before action \a before and returns it. \sa QWidget::insertAction(), addSeparator() */ QAction *KexiMenuWidget::insertSeparator(QAction *before) { QAction *action = new QAction(this); action->setSeparator(true); insertAction(before, action); return action; } /*! This sets the default action to \a act. The default action may have a visual cue, depending on the current QStyle. A default action usually indicates what will happen by default when a drop occurs. \sa defaultAction() */ void KexiMenuWidget::setDefaultAction(QAction *act) { d->defaultAction = act; } /*! Returns the current default action. \sa setDefaultAction() */ QAction *KexiMenuWidget::defaultAction() const { return d->defaultAction; } /*! Sets the currently highlighted action to \a act. */ void KexiMenuWidget::setActiveAction(QAction *act) { d->setCurrentAction(act, 0); if (d->scroll) d->scrollMenu(act, KexiMenuWidgetPrivate::QMenuScroller::ScrollCenter); } /*! Returns the currently highlighted action, or 0 if no action is currently highlighted. */ QAction *KexiMenuWidget::activeAction() const { return d->currentAction; } /*! \since 4.2 Returns true if there are no visible actions inserted into the menu, false otherwise. \sa QWidget::actions() */ bool KexiMenuWidget::isEmpty() const { bool ret = true; QList actionsList = this->actions(); for(int i = 0; ret && i < actionsList.count(); ++i) { const QAction *action = actionsList.at(i); if (!action->isSeparator() && action->isVisible()) { ret = false; } } return ret; } /*! Removes all the menu's actions. Actions owned by the menu and not shown in any other widget are deleted. \sa removeAction() */ void KexiMenuWidget::clear() { QList acts = actions(); for(int i = 0; i < acts.size(); i++) { #ifdef QT_SOFTKEYS_ENABLED // Lets not touch to our internal softkey actions if(acts[i] == d->selectAction || acts[i] == d->cancelAction) continue; #endif removeAction(acts[i]); if (acts[i]->parent() == this && acts[i]->associatedWidgets().isEmpty()) delete acts[i]; } } /*! If a menu does not fit on the screen it lays itself out so that it does fit. It is style dependent what layout means (for example, on Windows it will use multiple columns). This functions returns the number of columns necessary. */ int KexiMenuWidget::columnCount() const { return d->ncols; } /*! Returns the item at \a pt; returns 0 if there is no item there. */ QAction *KexiMenuWidget::actionAt(const QPoint &pt) const { if (QAction *ret = d->actionAt(pt)) return ret; return 0; } /*! Returns the geometry of action \a act. */ QRect KexiMenuWidget::actionGeometry(QAction *act) const { return d->actionRect(act); } /*! \reimp */ QSize KexiMenuWidget::sizeHint() const { d->updateActionRects(); QSize s; for (int i = 0; i < d->actionRects.count(); ++i) { const QRect &rect = d->actionRects.at(i); if (rect.isNull()) continue; if (rect.bottom() >= s.height()) s.setHeight(rect.y() + rect.height()); if (rect.right() >= s.width()) s.setWidth(rect.x() + rect.width()); } // Note that the action rects calculated above already include // the top and left margins, so we only need to add margins for // the bottom and right. QStyleOption opt(0); opt.initFrom(this); const int fw = d->frameWidth(&opt); int leftmargin, topmargin, rightmargin, bottommargin; getContentsMargins(&leftmargin, &topmargin, &rightmargin, &bottommargin); s.rwidth() += style()->pixelMetric(QStyle::PM_MenuHMargin, &opt, this) + fw + rightmargin + 2 /*frame*/; s.rheight() += style()->pixelMetric(QStyle::PM_MenuVMargin, &opt, this) + fw + bottommargin; return style()->sizeFromContents(QStyle::CT_Menu, &opt, s.expandedTo(QApplication::globalStrut()), this); } /*! Displays the menu so that the action \a atAction will be at the specified \e global position \a p. To translate a widget's local coordinates into global coordinates, use QWidget::mapToGlobal(). When positioning a menu with exec() or popup(), bear in mind that you cannot rely on the menu's current size(). For performance reasons, the menu adapts its size only when necessary, so in many cases, the size before and after the show is different. Instead, use sizeHint() which calculates the proper size depending on the menu's current contents. \sa QWidget::mapToGlobal(), exec() */ void KexiMenuWidget::popup(const QPoint &p, QAction *atAction) { #ifndef Q_OS_SYMBIAN if (d->scroll) { // reset scroll state from last popup d->scroll->scrollOffset = 0; d->scroll->scrollFlags = KexiMenuWidgetPrivate::QMenuScroller::ScrollNone; } #endif //d->tearoffHighlighted = 0; d->motions = 0; //d->doChildEffects = true; d->updateLayoutDirection(); ensurePolished(); // Get the right font emit aboutToShow(); //const bool actionListChanged = d->itemsDirty; d->updateActionRects(); QPoint pos; /*QPushButton *causedButton = qobject_cast(d->causedPopup().widget); if (actionListChanged && causedButton) pos = QPushButtonPrivate::get(causedButton)->adjustedMenuPosition(); else*/ pos = p; QSize size = sizeHint(); QRect screen; #ifndef QT_NO_GRAPHICSVIEW /*bool isEmbedded = !bypassGraphicsProxyWidget(this) && d->nearestGraphicsProxyWidget(this); if (isEmbedded) screen = d->popupGeometry(this); else*/ #endif screen = d->popupGeometry(QApplication::desktop()->screenNumber(p)); const int desktopFrame = d->frameWidth(); bool adjustToDesktop = !window()->testAttribute(Qt::WA_DontShowOnScreen); QList actionsList = this->actions(); #ifdef QT_KEYPAD_NAVIGATION if (!atAction && QApplication::keypadNavigationEnabled()) { // Try to have one item activated if (d->defaultAction && d->defaultAction->isEnabled()) { atAction = d->defaultAction; // TODO: This works for first level menus, not yet sub menus } else { foreach (QAction *action, actionsList) if (action->isEnabled()) { atAction = action; break; } } d->currentAction = atAction; } #endif if (d->ncols > 1) { pos.setY(screen.top() + desktopFrame); } else if (atAction) { for (int i = 0, above_height = 0; i < actionsList.count(); i++) { QAction *action = actionsList.at(i); if (action == atAction) { int newY = pos.y() - above_height; if (d->scroll && newY < desktopFrame) { d->scroll->scrollFlags = d->scroll->scrollFlags | KexiMenuWidgetPrivate::QMenuScroller::ScrollUp; d->scroll->scrollOffset = newY; newY = desktopFrame; } pos.setY(newY); if (d->scroll && d->scroll->scrollFlags != KexiMenuWidgetPrivate::QMenuScroller::ScrollNone && !style()->styleHint(QStyle::SH_Menu_FillScreenWithScroll, 0, this)) { int below_height = above_height + d->scroll->scrollOffset; for (int i2 = i; i2 < d->actionRects.count(); i2++) below_height += d->actionRects.at(i2).height(); size.setHeight(below_height); } break; } else { above_height += d->actionRects.at(i).height(); } } } QPoint mouse = QCursor::pos(); d->mousePopupPos = mouse; const bool snapToMouse = (QRect(p.x() - 3, p.y() - 3, 6, 6).contains(mouse)); if (adjustToDesktop) { // handle popup falling "off screen" if (isRightToLeft()) { if (snapToMouse) // position flowing left from the mouse pos.setX(mouse.x() - size.width()); if (pos.x() < screen.left() + desktopFrame) pos.setX(qMax(p.x(), screen.left() + desktopFrame)); if (pos.x() + size.width() - 1 > screen.right() - desktopFrame) pos.setX(qMax(p.x() - size.width(), screen.right() - desktopFrame - size.width() + 1)); } else { if (pos.x() + size.width() - 1 > screen.right() - desktopFrame) pos.setX(screen.right() - desktopFrame - size.width() + 1); if (pos.x() < screen.left() + desktopFrame) pos.setX(screen.left() + desktopFrame); } if (pos.y() + size.height() - 1 > screen.bottom() - desktopFrame) { if(snapToMouse) pos.setY(qMin(mouse.y() - (size.height() + desktopFrame), screen.bottom()-desktopFrame-size.height()+1)); else pos.setY(qMax(p.y() - (size.height() + desktopFrame), screen.bottom()-desktopFrame-size.height()+1)); } else if (pos.y() < screen.top() + desktopFrame) { pos.setY(screen.top() + desktopFrame); } if (pos.y() < screen.top() + desktopFrame) pos.setY(screen.top() + desktopFrame); if (pos.y() + size.height() - 1 > screen.bottom() - desktopFrame) { if (d->scroll) { d->scroll->scrollFlags |= int(KexiMenuWidgetPrivate::QMenuScroller::ScrollDown); int y = qMax(screen.y(),pos.y()); size.setHeight(screen.bottom() - (desktopFrame * 2) - y); } else { // Too big for screen, bias to see bottom of menu (for some reason) pos.setY(screen.bottom() - size.height() + 1); } } } //setGeometry(QRect(pos, size)); //resize(size); show(); #ifndef QT_NO_ACCESSIBILITY //QAccessible::updateAccessibility(this, 0, QAccessible::PopupMenuStart); #endif } #if 0 /*! Executes this menu synchronously. This is equivalent to \c{exec(pos())}. This returns the triggered QAction in either the popup menu or one of its submenus, or 0 if no item was triggered (normally because the user pressed Esc). In most situations you'll want to specify the position yourself, for example, the current mouse position: \snippet doc/src/snippets/code/src_gui_widgets_qmenu.cpp 0 or aligned to a widget: \snippet doc/src/snippets/code/src_gui_widgets_qmenu.cpp 1 or in reaction to a QMouseEvent *e: \snippet doc/src/snippets/code/src_gui_widgets_qmenu.cpp 2 */ QAction *KexiMenuWidget::exec() { return exec(pos()); } /*! \overload Executes this menu synchronously. Pops up the menu so that the action \a action will be at the specified \e global position \a p. To translate a widget's local coordinates into global coordinates, use QWidget::mapToGlobal(). This returns the triggered QAction in either the popup menu or one of its submenus, or 0 if no item was triggered (normally because the user pressed Esc). Note that all signals are emitted as usual. If you connect a QAction to a slot and call the menu's exec(), you get the result both via the signal-slot connection and in the return value of exec(). Common usage is to position the menu at the current mouse position: \snippet doc/src/snippets/code/src_gui_widgets_qmenu.cpp 3 or aligned to a widget: \snippet doc/src/snippets/code/src_gui_widgets_qmenu.cpp 4 or in reaction to a QMouseEvent *e: \snippet doc/src/snippets/code/src_gui_widgets_qmenu.cpp 5 When positioning a menu with exec() or popup(), bear in mind that you cannot rely on the menu's current size(). For performance reasons, the menu adapts its size only when necessary. So in many cases, the size before and after the show is different. Instead, use sizeHint() which calculates the proper size depending on the menu's current contents. \sa popup(), QWidget::mapToGlobal() */ QAction *KexiMenuWidget::exec(const QPoint &p, QAction *action) { createWinId(); QEventLoop eventLoop; d->eventLoop = &eventLoop; popup(p, action); QPointer guard = this; (void) eventLoop.exec(); if (guard.isNull()) return 0; action = d->syncAction; d->syncAction = 0; d->eventLoop = 0; return action; } /*! \overload Executes a menu synchronously. The menu's actions are specified by the list of \a actions. The menu will pop up so that the specified action, \a at, appears at global position \a pos. If \a at is not specified then the menu appears at position \a pos. \a parent is the menu's parent widget; specifying the parent will provide context when \a pos alone is not enough to decide where the menu should go (e.g., with multiple desktops or when the parent is embedded in QGraphicsView). The function returns the triggered QAction in either the popup menu or one of its submenus, or 0 if no item was triggered (normally because the user pressed Esc). This is equivalent to: \snippet doc/src/snippets/code/src_gui_widgets_qmenu.cpp 6 \sa popup(), QWidget::mapToGlobal() */ QAction *KexiMenuWidget::exec(QList actions, const QPoint &pos, QAction *at, QWidget *parent) { KexiMenuWidget menu(parent); menu.addActions(actions); return menu.exec(pos, at); } /*! \overload Executes a menu synchronously. The menu's actions are specified by the list of \a actions. The menu will pop up so that the specified action, \a at, appears at global position \a pos. If \a at is not specified then the menu appears at position \a pos. The function returns the triggered QAction in either the popup menu or one of its submenus, or 0 if no item was triggered (normally because the user pressed Esc). This is equivalent to: \snippet doc/src/snippets/code/src_gui_widgets_qmenu.cpp 6 \sa popup(), QWidget::mapToGlobal() */ QAction *KexiMenuWidget::exec(QList actions, const QPoint &pos, QAction *at) { // ### Qt 5: merge return exec(actions, pos, at, 0); } #endif ClickableLogoArea::ClickableLogoArea(QWidget *parent) : QAbstractButton(parent) { connect(this, SIGNAL(clicked()), this, SLOT(slotClicked())); } void ClickableLogoArea::slotClicked() { QDesktopServices::openUrl(QUrl(calligraUrl)); } void ClickableLogoArea::paintEvent(QPaintEvent*) { } void KexiMenuWidgetPrivate::updateLogoPixmap() { bool isLight; if (bespin) { isLight = q->palette().color(QPalette::Shadow).lightness() >= 128; } else { isLight = KexiUtils::isLightColorScheme(); } calligraLogoPixmap = QPixmap(isLight ? ":/calligra-logo-white-glow" : ":/calligra-logo-black-glow"); } int KexiMenuWidgetPrivate::bottomOfLastItem() const { if (actionRects.isEmpty()) { return 0; } return actionRects.last().bottom(); } /* Logo's pixmap is consisted of logo and some glow (cutted-off vertically). (without the glow the math would be simpler) +--------+ + | glow | |--- glowHeight - cutOffGlow | +----+ | + | |logo| | |--- calligraLogoPixmapInternalHeight | +----+ | + | glow | |--- glowHeight - cutOffGlow +--------+ + */ const int glowHeight = 64; const int cutOffGlow = 12; const int spacingAfterLastItem = 10; //! @return distance between bottom border of the logo and bottom border of the entire menu widget int KexiMenuWidgetPrivate::logoBottomMargin() const { /* +----------------+ | Menu item 1 | | | ... | | | Last menu item | | q->height() +----------------+ <--- bottomOfLastItem() | | | |--- spacingAfterLastItem | +----------------+ | glow | | +----+ | | |logo| | | +----+ | + | glow | |--- bottomMargin +----------------+ + */ int bottomMargin = glowHeight - cutOffGlow; if ((q->height() - bottomMargin - calligraLogoPixmapInternalHeight - cutOffGlow) <= (bottomOfLastItem() + spacingAfterLastItem)) { /* Special case when the last menu item would cover the logo: keep the logo below +----------------+ | +----+ | | |logo| | +----------------+ |--- bottomMargin (can be 0 or negative) | */ bottomMargin = q->height() - bottomOfLastItem() - spacingAfterLastItem - cutOffGlow - calligraLogoPixmapInternalHeight; } return bottomMargin; } void KexiMenuWidgetPrivate::updateLogo() { const QRect logoRect((q->width() - 2 - calligraLogoPixmapInternalWidth) / 2, q->height() - logoBottomMargin() - calligraLogoPixmapInternalHeight - cutOffGlow, calligraLogoPixmapInternalWidth, calligraLogoPixmapInternalHeight); if (!clickableLogoArea) { updateLogoPixmap(); clickableLogoArea = new ClickableLogoArea(q); clickableLogoArea->setCursor(Qt::PointingHandCursor); clickableLogoArea->setToolTip(xi18n("Visit Calligra home page at %1", QLatin1String(calligraUrl))); } clickableLogoArea->setGeometry(logoRect); } /*! \reimp */ void KexiMenuWidget::showEvent(QShowEvent* event) { QWidget::showEvent(event); d->updateLogo(); d->clickableLogoArea->show(); } /*! \reimp */ void KexiMenuWidget::hideEvent(QHideEvent *) { emit aboutToHide(); if (d->eventLoop) d->eventLoop->exit(); d->setCurrentAction(0); #ifndef QT_NO_ACCESSIBILITY QAccessibleEvent event(this, QAccessible::PopupMenuEnd); event.setChild(0); QAccessible::updateAccessibility(&event); #endif d->mouseDown = 0; d->hasHadMouse = false; d->causedPopup.widget = 0; d->causedPopup.action = 0; if (d->scroll) d->scroll->scrollTimer.stop(); //make sure the timer stops // this unmarks the previous persistent action d->setActionPersistentlySelected(0, false); } /*! \reimp */ void KexiMenuWidget::paintEvent(QPaintEvent *e) { d->updateActionRects(); QPainter p(this); QRegion emptyArea = QRegion(rect()); //! @todo KEXI3 port OxygenHelper #if 0 if (d->oxygenHelper) { //d->oxygenHelper->renderWindowBackground(&p, rect(), this, palette()); d->oxygenHelper->renderMenuBackground(&p, rect(), this, palette()); // d->oxygenHelper->drawFloatFrame(&p, rect(), palette().window().color(), true); } #endif QStyleOptionMenuItem menuOpt; menuOpt.initFrom(this); menuOpt.state = QStyle::State_None; menuOpt.checkType = QStyleOptionMenuItem::NotCheckable; menuOpt.maxIconWidth = 0; menuOpt.tabWidth = 0; if (d->bespin) { p.fillRect(rect(), palette().shadow()); } else { style()->drawPrimitive(QStyle::PE_PanelMenu, &menuOpt, &p, this); } { - QStyleOptionFrameV3 opt; + QStyleOptionFrame opt; opt.initFrom(this); opt.frameShape = QFrame::VLine; opt.state |= QStyle::State_Raised; opt.lineWidth = 1; opt.midLineWidth = 0; //opt.rect = QRect(opt.rect.topRight() - QPoint(0, 0), opt.rect.bottomRight() + QPoint(10, 0)); //const QRect cr = style()->subElementRect(QStyle::SE_ShapedFrameContents, &opt); //opt.lineWidth = opt.rect.right() - cr.right(); opt.rect.setX(opt.rect.width() - opt.lineWidth); opt.rect.setWidth(opt.lineWidth); style()->drawControl(QStyle::CE_ShapedFrame, &opt, &p); /* QStyleOption opt; opt.initFrom(this); opt.state |= QStyle::State_Horizontal; opt.rect = QRect(opt.rect.topRight() - QPoint(10, 0), opt.rect.bottomRight() + QPoint(10, 0)); style()->drawPrimitive(QStyle::PE_IndicatorToolBarSeparator, &opt, &p, this); */ } //draw the items that need updating.. QList actionsList = this->actions(); for (int i = 0; i < actionsList.count(); ++i) { QAction *action = actionsList.at(i); QRect adjustedActionRect = d->actionRects.at(i); if (!e->rect().intersects(adjustedActionRect) || d->widgetItems.value(action)) continue; //set the clip region to be extra safe (and adjust for the scrollers) QRegion adjustedActionReg(adjustedActionRect); emptyArea -= adjustedActionReg; p.setClipRegion(adjustedActionReg); QStyleOptionMenuItem opt; initStyleOption(&opt, action); opt.rect = adjustedActionRect; if (d->actionPersistentlySelected(action)) { opt.state |= QStyle::State_Selected; if (!d->bespin && !d->qtcurve) { opt.palette.setBrush(QPalette::Window, opt.palette.brush(QPalette::Highlight)); opt.palette.setBrush(QPalette::WindowText, opt.palette.brush(QPalette::HighlightedText)); } } else if (!action->isSeparator()) { if (!d->bespin && opt.state & QStyle::State_Selected) { // lighten the highlight to make it different from // the persistently selected item opt.palette.setColor(QPalette::Highlight, KColorUtils::mix( opt.palette.color(QPalette::Highlight), opt.palette.color(QPalette::Window))); opt.palette.setColor(QPalette::HighlightedText, opt.palette.color(QPalette::Text)); } } // Depending on style Button or Background brush may be used // to fill background of deselected items. Make it transparent. bool transparentBackground = !(opt.state & QStyle::State_Selected); //! @todo KEXI3 port OxygenHelper #if 0 if (d->oxygenHelper && action->isSeparator()) { transparentBackground = false; } #endif if (transparentBackground) { opt.palette.setBrush(QPalette::Button, QBrush(Qt::transparent)); if (!d->bespin) { opt.palette.setBrush(QPalette::Background, QBrush(Qt::transparent)); } } style()->drawControl(QStyle::CE_MenuItem, &opt, &p, this); } const int fw = d->frameWidth(); //draw the scroller regions.. if (d->scroll) { menuOpt.menuItemType = QStyleOptionMenuItem::Scroller; menuOpt.state |= QStyle::State_Enabled; if (d->scroll->scrollFlags & KexiMenuWidgetPrivate::QMenuScroller::ScrollUp) { menuOpt.rect.setRect(fw, fw, width() - (fw * 2), d->scrollerHeight()); emptyArea -= QRegion(menuOpt.rect); p.setClipRect(menuOpt.rect); style()->drawControl(QStyle::CE_MenuScroller, &menuOpt, &p, this); } if (d->scroll->scrollFlags & KexiMenuWidgetPrivate::QMenuScroller::ScrollDown) { menuOpt.rect.setRect(fw, height() - d->scrollerHeight() - fw, width() - (fw * 2), d->scrollerHeight()); emptyArea -= QRegion(menuOpt.rect); menuOpt.state |= QStyle::State_DownArrow; p.setClipRect(menuOpt.rect); style()->drawControl(QStyle::CE_MenuScroller, &menuOpt, &p, this); } } //paint the tear off.. /* if (d->tearoff) { menuOpt.menuItemType = QStyleOptionMenuItem::TearOff; menuOpt.rect.setRect(fw, fw, width() - (fw * 2), style()->pixelMetric(QStyle::PM_MenuTearoffHeight, 0, this)); if (d->scroll && d->scroll->scrollFlags & KexiMenuWidgetPrivate::QMenuScroller::ScrollUp) menuOpt.rect.translate(0, d->scrollerHeight()); emptyArea -= QRegion(menuOpt.rect); p.setClipRect(menuOpt.rect); menuOpt.state = QStyle::State_None; if (d->tearoffHighlighted) menuOpt.state |= QStyle::State_Selected; style()->drawControl(QStyle::CE_MenuTearoff, &menuOpt, &p, this); }*/ //draw border if (fw > 0) { QRegion borderReg; borderReg += QRect(0, 0, fw, height()); //left borderReg += QRect(width()-fw, 0, fw, height()); //right borderReg += QRect(0, 0, width(), fw); //top borderReg += QRect(0, height()-fw, width(), fw); //bottom p.setClipRegion(borderReg); emptyArea -= borderReg; QStyleOptionFrame frame; frame.rect = rect(); frame.palette = palette(); frame.state = QStyle::State_None; frame.lineWidth = style()->pixelMetric(QStyle::PM_MenuPanelWidth); frame.midLineWidth = 0; style()->drawPrimitive(QStyle::PE_FrameMenu, &frame, &p, this); // full frame: style()->drawPrimitive(QStyle::PE_FrameWindow, &frame, &p, this); } //finally the rest of the space p.setClipRegion(emptyArea); menuOpt.state = QStyle::State_None; menuOpt.menuItemType = QStyleOptionMenuItem::EmptyArea; menuOpt.checkType = QStyleOptionMenuItem::NotCheckable; menuOpt.rect = rect(); menuOpt.menuRect = rect(); style()->drawControl(QStyle::CE_MenuEmptyArea, &menuOpt, &p, this); // version p.setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); QColor textColor; textColor = palette().color(QPalette::Base); p.setPen(QPen(textColor)); const int logoBottomMargin = d->logoBottomMargin(); p.drawText(0, height() - logoBottomMargin + 1, width(), logoBottomMargin - 1, Qt::AlignHCenter | Qt::AlignTop, QLatin1String(Kexi::versionString())); textColor = palette().color(QPalette::WindowText); textColor.setAlpha(180); p.setPen(QPen(textColor)); p.drawText(0, height() - logoBottomMargin, width(), logoBottomMargin, Qt::AlignHCenter | Qt::AlignTop, QLatin1String(Kexi::versionString())); // logo p.drawPixmap((width() - d->calligraLogoPixmap.width()) / 2, height() - logoBottomMargin - d->calligraLogoPixmap.height() + calligraLogoPixmapInternalHeight - 20, d->calligraLogoPixmap); } #ifndef QT_NO_WHEELEVENT /*! \reimp */ void KexiMenuWidget::wheelEvent(QWheelEvent *e) { if (d->scroll && rect().contains(e->pos())) d->scrollMenu(e->delta() > 0 ? KexiMenuWidgetPrivate::QMenuScroller::ScrollUp : KexiMenuWidgetPrivate::QMenuScroller::ScrollDown); } #endif /*! \reimp */ void KexiMenuWidget::mousePressEvent(QMouseEvent *e) { if (d->aboutToHide || d->mouseEventTaken(e)) return; if (!rect().contains(e->pos())) { if (d->noReplayFor && QRect(d->noReplayFor->mapToGlobal(QPoint()), d->noReplayFor->size()).contains(e->globalPos())) setAttribute(Qt::WA_NoMouseReplay); if (d->eventLoop) // synchronous operation d->syncAction = 0; d->hideUpToMenuBar(); return; } d->mouseDown = this; QAction *action = d->actionAt(e->pos()); d->setCurrentAction(action, 20); update(); } /*! \reimp */ void KexiMenuWidget::mouseReleaseEvent(QMouseEvent *e) { if (d->aboutToHide || d->mouseEventTaken(e)) return; if(d->mouseDown != this) { d->mouseDown = 0; return; } d->mouseDown = 0; d->setSyncAction(); QAction *action = d->actionAt(e->pos()); //qDebug() << "action:" << action << "d->currentAction:" << d->currentAction; if (action && action == d->currentAction) { if (!action->menu()){ #if defined(Q_OS_WIN) //On Windows only context menus can be activated with the right button if (e->button() == Qt::LeftButton || d->topCausedWidget() == 0) #endif { if (!d->actionPersistentlySelected(action)) { d->toggleActionPersistentlySelected(action); update(); d->activateAction(action, QAction::Trigger); } } } } else if (d->hasMouseMoved(e->globalPos())) { d->hideUpToMenuBar(); } } /*! \reimp */ void KexiMenuWidget::changeEvent(QEvent *e) { if (e->type() == QEvent::StyleChange || e->type() == QEvent::FontChange || e->type() == QEvent::LayoutDirectionChange) { d->itemsDirty = 1; setMouseTracking(style()->styleHint(QStyle::SH_Menu_MouseTracking, 0, this)); if (isVisible()) resize(sizeHint()); if (!style()->styleHint(QStyle::SH_Menu_Scrollable, 0, this)) { delete d->scroll; d->scroll = 0; } else if (!d->scroll) { d->scroll = new KexiMenuWidgetPrivate::QMenuScroller; d->scroll->scrollFlags = KexiMenuWidgetPrivate::QMenuScroller::ScrollNone; } } else if (e->type() == QEvent::EnabledChange) { d->menuAction->setEnabled(isEnabled()); } else if (e->type() == QEvent::PaletteChange) { d->updateLogoPixmap(); d->updateLogo(); } QWidget::changeEvent(e); } /*! \reimp */ bool KexiMenuWidget::event(QEvent *e) { switch (e->type()) { case QEvent::Polish: d->updateLayoutDirection(); break; case QEvent::ShortcutOverride: { QKeyEvent *kev = static_cast(e); if (kev->key() == Qt::Key_Up || kev->key() == Qt::Key_Down || kev->key() == Qt::Key_Left || kev->key() == Qt::Key_Right || kev->key() == Qt::Key_Enter || kev->key() == Qt::Key_Return || kev->key() == Qt::Key_Escape) { e->accept(); return true; } } break; case QEvent::KeyPress: { QKeyEvent *ke = (QKeyEvent*)e; if (ke->key() == Qt::Key_Tab || ke->key() == Qt::Key_Backtab) { keyPressEvent(ke); return true; } } break; /*case QEvent::ContextMenu: if(d->menuDelayTimer.isActive()) { d->menuDelayTimer.stop(); internalDelayedPopup(); } break;*/ case QEvent::Resize: { QStyleHintReturnMask menuMask; QStyleOption option; option.initFrom(this); if (style()->styleHint(QStyle::SH_Menu_Mask, &option, this, &menuMask)) { setMask(menuMask.region); } d->itemsDirty = 1; d->updateActionRects(); d->updateLogo(); break; } case QEvent::Show: d->mouseDown = 0; d->updateActionRects(); show(); // if (d->currentAction) // d->popupAction(d->currentAction, 0, false); break; #ifndef QT_NO_WHATSTHIS case QEvent::QueryWhatsThis: e->setAccepted(whatsThis().size()); if (QAction *action = d->actionAt(static_cast(e)->pos())) { if (action->whatsThis().size() || action->menu()) e->accept(); } return true; #endif #ifdef QT_SOFTKEYS_ENABLED case QEvent::LanguageChange: { d->selectAction->setText(QSoftKeyManager::standardSoftKeyText(QSoftKeyManager::SelectSoftKey)); d->cancelAction->setText(QSoftKeyManager::standardSoftKeyText(QSoftKeyManager::CancelSoftKey)); } break; #endif default: break; } return QWidget::event(e); } /*! \reimp */ bool KexiMenuWidget::focusNextPrevChild(bool next) { setFocus(); QKeyEvent ev(QEvent::KeyPress, next ? Qt::Key_Tab : Qt::Key_Backtab, Qt::NoModifier); keyPressEvent(&ev); return true; } /*! \reimp */ void KexiMenuWidget::keyPressEvent(QKeyEvent *e) { d->updateActionRects(); int key = e->key(); if (isRightToLeft()) { // in reverse mode open/close key for submenues are reversed if (key == Qt::Key_Left) key = Qt::Key_Right; else if (key == Qt::Key_Right) key = Qt::Key_Left; } #ifndef Q_OS_MACOS if (key == Qt::Key_Tab) //means down key = Qt::Key_Down; if (key == Qt::Key_Backtab) //means up key = Qt::Key_Up; #endif bool key_consumed = false; QList actionsList = this->actions(); switch(key) { case Qt::Key_Home: key_consumed = true; if (d->scroll) d->scrollMenu(KexiMenuWidgetPrivate::QMenuScroller::ScrollTop, true); break; case Qt::Key_End: key_consumed = true; if (d->scroll) d->scrollMenu(KexiMenuWidgetPrivate::QMenuScroller::ScrollBottom, true); break; case Qt::Key_PageUp: key_consumed = true; if (d->currentAction && d->scroll) { if(d->scroll->scrollFlags & KexiMenuWidgetPrivate::QMenuScroller::ScrollUp) d->scrollMenu(KexiMenuWidgetPrivate::QMenuScroller::ScrollUp, true, true); else d->scrollMenu(KexiMenuWidgetPrivate::QMenuScroller::ScrollTop, true); } break; case Qt::Key_PageDown: key_consumed = true; if (d->currentAction && d->scroll) { if(d->scroll->scrollFlags & KexiMenuWidgetPrivate::QMenuScroller::ScrollDown) d->scrollMenu(KexiMenuWidgetPrivate::QMenuScroller::ScrollDown, true, true); else d->scrollMenu(KexiMenuWidgetPrivate::QMenuScroller::ScrollBottom, true); } break; case Qt::Key_Up: case Qt::Key_Down: { key_consumed = true; QAction *nextAction = 0; KexiMenuWidgetPrivate::QMenuScroller::ScrollLocation scroll_loc = KexiMenuWidgetPrivate::QMenuScroller::ScrollStay; if (!d->currentAction) { if(key == Qt::Key_Down) { for(int i = 0; i < actionsList.count(); ++i) { QAction *act = actionsList.at(i); if (d->actionRects.at(i).isNull()) continue; if (!act->isSeparator() && (style()->styleHint(QStyle::SH_Menu_AllowActiveAndDisabled, 0, this) || act->isEnabled())) { nextAction = act; break; } } } else { for(int i = actionsList.count()-1; i >= 0; --i) { QAction *act = actionsList.at(i); if (d->actionRects.at(i).isNull()) continue; if (!act->isSeparator() && (style()->styleHint(QStyle::SH_Menu_AllowActiveAndDisabled, 0, this) || act->isEnabled())) { nextAction = act; break; } } } } else { for(int i = 0, y = 0; !nextAction && i < actionsList.count(); i++) { QAction *act = actionsList.at(i); if (act == d->currentAction) { if (key == Qt::Key_Up) { for(int next_i = i-1; true; next_i--) { if (next_i == -1) { if(!style()->styleHint(QStyle::SH_Menu_SelectionWrap, 0, this)) break; if (d->scroll) scroll_loc = KexiMenuWidgetPrivate::QMenuScroller::ScrollBottom; next_i = d->actionRects.count()-1; } QAction *next = actionsList.at(next_i); if (next == d->currentAction) break; if (d->actionRects.at(next_i).isNull()) continue; if (next->isSeparator() || (!next->isEnabled() && !style()->styleHint(QStyle::SH_Menu_AllowActiveAndDisabled, 0, this))) continue; nextAction = next; if (d->scroll && (d->scroll->scrollFlags & KexiMenuWidgetPrivate::QMenuScroller::ScrollUp)) { int topVisible = d->scrollerHeight(); /* if (d->tearoff) topVisible += style()->pixelMetric(QStyle::PM_MenuTearoffHeight, 0, this);*/ if (((y + d->scroll->scrollOffset) - topVisible) <= d->actionRects.at(next_i).height()) scroll_loc = KexiMenuWidgetPrivate::QMenuScroller::ScrollTop; } break; } /* if (!nextAction && d->tearoff) d->tearoffHighlighted = 1;*/ } else { y += d->actionRects.at(i).height(); for(int next_i = i+1; true; next_i++) { if (next_i == d->actionRects.count()) { if(!style()->styleHint(QStyle::SH_Menu_SelectionWrap, 0, this)) break; if (d->scroll) scroll_loc = KexiMenuWidgetPrivate::QMenuScroller::ScrollTop; next_i = 0; } QAction *next = actionsList.at(next_i); if (next == d->currentAction) break; if (d->actionRects.at(next_i).isNull()) continue; if (next->isSeparator() || (!next->isEnabled() && !style()->styleHint(QStyle::SH_Menu_AllowActiveAndDisabled, 0, this))) continue; nextAction = next; if (d->scroll && (d->scroll->scrollFlags & KexiMenuWidgetPrivate::QMenuScroller::ScrollDown)) { int bottomVisible = height() - d->scrollerHeight(); if (d->scroll->scrollFlags & KexiMenuWidgetPrivate::QMenuScroller::ScrollUp) bottomVisible -= d->scrollerHeight(); /* if (d->tearoff) bottomVisible -= style()->pixelMetric(QStyle::PM_MenuTearoffHeight, 0, this);*/ if ((y + d->scroll->scrollOffset + d->actionRects.at(next_i).height()) > bottomVisible) scroll_loc = KexiMenuWidgetPrivate::QMenuScroller::ScrollBottom; } break; } } break; } y += d->actionRects.at(i).height(); } } if (nextAction) { if (d->scroll && scroll_loc != KexiMenuWidgetPrivate::QMenuScroller::ScrollStay) { d->scroll->scrollTimer.stop(); d->scrollMenu(nextAction, scroll_loc); } d->setCurrentAction(nextAction, /*popup*/-1, KexiMenuWidget::SelectedFromKeyboard); } break; } /*case Qt::Key_Right: if (d->currentAction && d->currentAction->isEnabled() && d->currentAction->menu()) { d->popupAction(d->currentAction, 0, true); key_consumed = true; break; } //FALL THROUGH*/ case Qt::Key_Left: { if (d->currentAction && !d->scroll) { QAction *nextAction = 0; QRect actionR = d->actionRect(d->currentAction); for(int x = actionR.left()-1; !nextAction && x >= 0; x--) nextAction = d->actionAt(QPoint(x, actionR.center().y())); if (nextAction) { d->setCurrentAction(nextAction, /*popup*/-1, KexiMenuWidget::SelectedFromKeyboard); key_consumed = true; } } if (!key_consumed && key == Qt::Key_Left && qobject_cast(d->causedPopup.widget)) { QPointer caused = d->causedPopup.widget; d->hideMenu(this); if (caused) caused->setFocus(); key_consumed = true; } break; } case Qt::Key_Alt: key_consumed = true; if (style()->styleHint(QStyle::SH_MenuBar_AltKeyNavigation, 0, this)) { d->hideMenu(this); } break; case Qt::Key_Escape: #ifdef QT_KEYPAD_NAVIGATION case Qt::Key_Back: #endif key_consumed = true; { QPointer caused = d->causedPopup.widget; d->hideMenu(this); // hide after getting causedPopup } break; case Qt::Key_Space: if (!style()->styleHint(QStyle::SH_Menu_SpaceActivatesItem, 0, this)) break; // for motif, fall through #ifdef QT_KEYPAD_NAVIGATION case Qt::Key_Select: #endif case Qt::Key_Return: case Qt::Key_Enter: { if (!d->currentAction) { d->setFirstActionActive(); key_consumed = true; break; } d->setSyncAction(); if (d->currentAction->menu()) { /*d->popupAction(d->currentAction, 0, true);*/ } else { d->activateAction(d->currentAction, QAction::Trigger); } key_consumed = true; break; } #ifndef QT_NO_WHATSTHIS case Qt::Key_F1: if (!d->currentAction || d->currentAction->whatsThis().isNull()) break; QWhatsThis::enterWhatsThisMode(); d->activateAction(d->currentAction, QAction::Trigger); return; #endif default: key_consumed = false; } if (!key_consumed) { // send to menu bar if ((!e->modifiers() || e->modifiers() == Qt::AltModifier || e->modifiers() == Qt::ShiftModifier) && e->text().length()==1) { bool activateAction = false; QAction *nextAction = 0; if (style()->styleHint(QStyle::SH_Menu_KeyboardSearch, 0, this) && !e->modifiers()) { int best_match_count = 0; d->searchBufferTimer.start(2000, this); d->searchBuffer += e->text(); for(int i = 0; i < actionsList.size(); ++i) { int match_count = 0; if (d->actionRects.at(i).isNull()) continue; QAction *act = actionsList.at(i); const QString act_text = act->text(); for(int c = 0; c < d->searchBuffer.size(); ++c) { if(act_text.indexOf(d->searchBuffer.at(c), 0, Qt::CaseInsensitive) != -1) ++match_count; } if(match_count > best_match_count) { best_match_count = match_count; nextAction = act; } } } #ifndef QT_NO_SHORTCUT else { int clashCount = 0; QAction *first = 0, *currentSelected = 0, *firstAfterCurrent = 0; QChar c = e->text().at(0).toUpper(); for(int i = 0; i < actionsList.size(); ++i) { if (d->actionRects.at(i).isNull()) continue; QAction *act = actionsList.at(i); QKeySequence sequence = QKeySequence::mnemonic(act->text()); int key = sequence[0] & 0xffff; if (key == c.unicode()) { clashCount++; if (!first) first = act; if (act == d->currentAction) currentSelected = act; else if (!firstAfterCurrent && currentSelected) firstAfterCurrent = act; } } if (clashCount == 1) activateAction = true; if (clashCount >= 1) { if (clashCount == 1 || !currentSelected || !firstAfterCurrent) nextAction = first; else nextAction = firstAfterCurrent; } } #endif if (nextAction) { key_consumed = true; if(d->scroll) d->scrollMenu(nextAction, KexiMenuWidgetPrivate::QMenuScroller::ScrollCenter, false); d->setCurrentAction(nextAction, 20, KexiMenuWidget::SelectedFromElsewhere, true); if (!nextAction->menu() && activateAction) { d->setSyncAction(); d->activateAction(nextAction, QAction::Trigger); } } } if (!key_consumed) { } #ifdef Q_OS_WIN32 if (key_consumed && (e->key() == Qt::Key_Control || e->key() == Qt::Key_Shift || e->key() == Qt::Key_Meta)) QApplication::beep(); #endif // Q_OS_WIN32 } if (key_consumed) e->accept(); else e->ignore(); } /*! \reimp */ void KexiMenuWidget::mouseMoveEvent(QMouseEvent *e) { if (!isVisible() || d->aboutToHide || d->mouseEventTaken(e)) return; d->motions++; if (d->motions == 0) // ignore first mouse move event (see enterEvent()) return; d->hasHadMouse = d->hasHadMouse || rect().contains(e->pos()); QAction *action = d->actionAt(e->pos()); if (!action) { if (d->hasHadMouse && (!d->currentAction || !(d->currentAction->menu() && d->currentAction->menu()->isVisible()))) d->setCurrentAction(0); return; } else if(e->buttons()) { d->mouseDown = this; } if (d->sloppyRegion.contains(e->pos())) { d->sloppyAction = action; KexiMenuWidgetPrivate::sloppyDelayTimer = startTimer(style()->styleHint(QStyle::SH_Menu_SubMenuPopupDelay, 0, this)*6); } else { d->setCurrentAction(action, style()->styleHint(QStyle::SH_Menu_SubMenuPopupDelay, 0, this)); } } /*! \reimp */ void KexiMenuWidget::enterEvent(QEvent *) { d->motions = -1; // force us to ignore the generate mouse move in mouseMoveEvent() } /*! \reimp */ void KexiMenuWidget::leaveEvent(QEvent *) { d->sloppyAction = 0; if (!d->sloppyRegion.isEmpty()) d->sloppyRegion = QRegion(); if (!d->activeMenu && d->currentAction) setActiveAction(0); } /*! \reimp */ void KexiMenuWidget::timerEvent(QTimerEvent *e) { if (d->scroll && d->scroll->scrollTimer.timerId() == e->timerId()) { d->scrollMenu((KexiMenuWidgetPrivate::QMenuScroller::ScrollDirection)d->scroll->scrollDirection); if (d->scroll->scrollFlags == KexiMenuWidgetPrivate::QMenuScroller::ScrollNone) d->scroll->scrollTimer.stop(); }/* else if(d->menuDelayTimer.timerId() == e->timerId()) { d->menuDelayTimer.stop(); internalDelayedPopup(); }*/ else if(KexiMenuWidgetPrivate::sloppyDelayTimer == e->timerId()) { killTimer(KexiMenuWidgetPrivate::sloppyDelayTimer); KexiMenuWidgetPrivate::sloppyDelayTimer = 0; internalSetSloppyAction(); } else if(d->searchBufferTimer.timerId() == e->timerId()) { d->searchBuffer.clear(); } } /*! \reimp */ void KexiMenuWidget::actionEvent(QActionEvent *e) { d->itemsDirty = 1; //setAttribute(Qt::WA_Resized, false); if (e->type() == QEvent::ActionAdded) { connect(e->action(), SIGNAL(triggered()), this, SLOT(actionTriggered())); connect(e->action(), SIGNAL(hovered()), this, SLOT(actionHovered())); if (QWidgetAction *wa = qobject_cast(e->action())) { QWidget *widget = wa->requestWidget(this); if (widget) d->widgetItems.insert(wa, widget); } } else if (e->type() == QEvent::ActionRemoved) { e->action()->disconnect(this); if (e->action() == d->currentAction) d->currentAction = 0; if (QWidgetAction *wa = qobject_cast(e->action())) { if (QWidget *widget = d->widgetItems.value(wa)) wa->releaseWidget(widget); } d->widgetItems.remove(e->action()); } if (isVisible()) { d->updateActionRects(); resize(sizeHint()); update(); } } /*! \internal */ void KexiMenuWidget::internalSetSloppyAction() { if (d->sloppyAction) d->setCurrentAction(d->sloppyAction, 0); } #if 0 /*! \internal */ void KexiMenuWidget::internalDelayedPopup() { //hide the current item if (KexiMenuWidget *menu = d->activeMenu) { d->activeMenu = 0; d->hideMenu(menu); } if (!d->currentAction || !d->currentAction->isEnabled() || !d->currentAction->menu() || !d->currentAction->menu()->isEnabled() || d->currentAction->menu()->isVisible()) return; //setup d->activeMenu = d->currentAction->menu(); d->activeMenu->causedPopup().widget = this; d->activeMenu->causedPopup().action = d->currentAction; int subMenuOffset = style()->pixelMetric(QStyle::PM_SubMenuOverlap, 0, this); const QRect actionRect(d->actionRect(d->currentAction)); const QSize menuSize(d->activeMenu->sizeHint()); const QPoint rightPos(mapToGlobal(QPoint(actionRect.right() + subMenuOffset + 1, actionRect.top()))); const QPoint leftPos(mapToGlobal(QPoint(actionRect.left() - subMenuOffset - menuSize.width(), actionRect.top()))); QPoint pos(rightPos); KexiMenuWidget *caused = qobject_cast(d->activeMenu->causedPopup().widget); const QRect availGeometry(d->popupGeometry(caused)); if (isRightToLeft()) { pos = leftPos; if ((caused && caused->x() < x()) || pos.x() < availGeometry.left()) { if(rightPos.x() + menuSize.width() < availGeometry.right()) pos = rightPos; else pos.rx() = availGeometry.left(); } } else { if ((caused && caused->x() > x()) || pos.x() + menuSize.width() > availGeometry.right()) { if(leftPos.x() < availGeometry.left()) pos.rx() = availGeometry.right() - menuSize.width(); else pos = leftPos; } } //calc sloppy focus buffer if (style()->styleHint(QStyle::SH_Menu_SloppySubMenus, 0, this)) { QPoint cur = QCursor::pos(); if (actionRect.contains(mapFromGlobal(cur))) { QPoint pts[4]; pts[0] = QPoint(cur.x(), cur.y() - 2); pts[3] = QPoint(cur.x(), cur.y() + 2); if (pos.x() >= cur.x()) { pts[1] = QPoint(geometry().right(), pos.y()); pts[2] = QPoint(geometry().right(), pos.y() + menuSize.height()); } else { pts[1] = QPoint(pos.x() + menuSize.width(), pos.y()); pts[2] = QPoint(pos.x() + menuSize.width(), pos.y() + menuSize.height()); } QPolygon points(4); for(int i = 0; i < 4; i++) points.setPoint(i, mapFromGlobal(pts[i])); d->sloppyRegion = QRegion(points); } } //do the popup d->activeMenu->popup(pos); } #endif /*!\internal */ void KexiMenuWidget::setNoReplayFor(QWidget *noReplayFor) { #ifdef Q_OS_WIN d->noReplayFor = noReplayFor; #else Q_UNUSED(noReplayFor); #endif } /*! \property KexiMenuWidget::separatorsCollapsible \since 4.2 \brief whether consecutive separators should be collapsed This property specifies whether consecutive separators in the menu should be visually collapsed to a single one. Separators at the beginning or the end of the menu are also hidden. By default, this property is true. */ bool KexiMenuWidget::separatorsCollapsible() const { return d->collapsibleSeparators; } void KexiMenuWidget::setSeparatorsCollapsible(bool collapse) { if (d->collapsibleSeparators == collapse) return; d->collapsibleSeparators = collapse; d->itemsDirty = 1; if (isVisible()) { d->updateActionRects(); update(); } } diff --git a/src/main/KexiSearchLineEdit.cpp b/src/main/KexiSearchLineEdit.cpp index 5f6111981..5c342b707 100644 --- a/src/main/KexiSearchLineEdit.cpp +++ b/src/main/KexiSearchLineEdit.cpp @@ -1,844 +1,844 @@ /* This file is part of the KDE project Copyright (C) 2011-2016 JarosÅ‚aw Staniek Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). 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 "KexiSearchLineEdit.h" #include #include #include #include #include #include #include #include #include #include #include #include #include class SearchableObject { public: KexiSearchableModel *model; int index; }; class KexiSearchLineEditCompleterPopupModel : public QAbstractListModel { Q_OBJECT public: explicit KexiSearchLineEditCompleterPopupModel(QObject *parent = 0); ~KexiSearchLineEditCompleterPopupModel(); virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; virtual QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; void addSearchableModel(KexiSearchableModel *model); private: class Private; Private * const d; }; class Q_DECL_HIDDEN KexiSearchLineEditCompleterPopupModel::Private { public: Private() : cachedCount(-1) { } ~Private() { qDeleteAll(searchableObjects); } void updateCachedCount() { if (searchableModels.isEmpty()) { return; } cachedCount = 0; foreach (KexiSearchableModel* searchableModel, searchableModels) { cachedCount += searchableModel->searchableObjectCount(); } } int cachedCount; QList searchableModels; QMap searchableObjects; }; KexiSearchLineEditCompleterPopupModel::KexiSearchLineEditCompleterPopupModel(QObject *parent) : QAbstractListModel(parent), d(new Private) { } KexiSearchLineEditCompleterPopupModel::~KexiSearchLineEditCompleterPopupModel() { delete d; } int KexiSearchLineEditCompleterPopupModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); if (d->cachedCount < 0) { d->updateCachedCount(); } return d->cachedCount; } QVariant KexiSearchLineEditCompleterPopupModel::data(const QModelIndex &index, int role) const { const int row = index.row(); if (d->cachedCount <= row) { return QVariant(); } SearchableObject *object = static_cast(index.internalPointer()); QModelIndex sourceIndex = object->model->sourceIndexForSearchableObject(object->index); return object->model->searchableData(sourceIndex, role); } QModelIndex KexiSearchLineEditCompleterPopupModel::index(int row, int column, const QModelIndex &parent) const { //qDebug() << row; if (!hasIndex(row, column, parent)) { qDebug() << "!hasIndex"; return QModelIndex(); } int r = row; SearchableObject *sobject = d->searchableObjects.value(row); if (!sobject) { foreach (KexiSearchableModel* searchableModel, d->searchableModels) { const int count = searchableModel->searchableObjectCount(); if (r < count) { sobject = new SearchableObject; sobject->model = searchableModel; sobject->index = r; d->searchableObjects.insert(row, sobject); break; } else { r -= count; } } } if (!sobject) { return QModelIndex(); } return createIndex(row, column, sobject); } void KexiSearchLineEditCompleterPopupModel::addSearchableModel(KexiSearchableModel *model) { d->searchableModels.removeAll(model); d->searchableModels.append(model); d->updateCachedCount(); } // ---- class KexiSearchLineEditCompleter : public KexiCompleter { Q_OBJECT public: explicit KexiSearchLineEditCompleter(QObject *parent = 0) : KexiCompleter(parent) { setCompletionRole(Qt::DisplayRole); } virtual QString pathFromIndex(const QModelIndex &index) const { if (!index.isValid()) return QString(); SearchableObject *object = static_cast(index.internalPointer()); QModelIndex sourceIndex = object->model->sourceIndexForSearchableObject(object->index); return object->model->pathFromIndex(sourceIndex); } }; // ---- class KexiSearchLineEditPopupItemDelegate; class Q_DECL_HIDDEN KexiSearchLineEdit::Private { public: explicit Private(KexiSearchLineEdit *_q) : q(_q), clearShortcut(QKeySequence(Qt::Key_Escape), _q), recentlyHighlightedModel(0) { // make Escape key clear the search box QObject::connect(&clearShortcut, SIGNAL(activated()), q, SLOT(slotClearShortcutActivated())); } void highlightSearchableObject(const QPair &source) { source.second->highlightSearchableObject(source.first); recentlyHighlightedModel = source.second; } void removeHighlightingForSearchableObject() { if (recentlyHighlightedModel) { recentlyHighlightedModel->highlightSearchableObject(QModelIndex()); recentlyHighlightedModel = 0; } } KexiSearchLineEditCompleter *completer; QTreeView *popupTreeView; KexiSearchLineEditCompleterPopupModel *model; KexiSearchLineEditPopupItemDelegate *delegate; QPointer previouslyFocusedWidget; private: KexiSearchLineEdit *q; QShortcut clearShortcut; KexiSearchableModel *recentlyHighlightedModel; }; // ---- static QSizeF viewItemTextLayout(QTextLayout &textLayout, int lineWidth) { qreal height = 0; qreal widthUsed = 0; textLayout.beginLayout(); while (true) { QTextLine line = textLayout.createLine(); if (!line.isValid()) break; line.setLineWidth(lineWidth); line.setPosition(QPointF(0, height)); height += line.height(); widthUsed = qMax(widthUsed, line.naturalTextWidth()); } textLayout.endLayout(); return QSizeF(widthUsed, height); } class KexiSearchLineEditPopupItemDelegate : public QStyledItemDelegate { Q_OBJECT public: KexiSearchLineEditPopupItemDelegate(QObject *parent, KexiCompleter *completer) : QStyledItemDelegate(parent), highlightMatchingSubstrings(true), m_completer(completer) { } //! Implemented to improve width hint QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { QSize size(QStyledItemDelegate::sizeHint(option, index)); - QStyleOptionViewItemV4 v4 = option; + QStyleOptionViewItem v4 = option; QStyledItemDelegate::initStyleOption(&v4, index); const QSize s = v4.widget->style()->sizeFromContents(QStyle::CT_ItemViewItem, &v4, size, v4.widget); size.setWidth(s.width()); return size; } virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { QStyledItemDelegate::paint(painter, option, index); - QStyleOptionViewItemV4 v4 = option; + QStyleOptionViewItem v4 = option; QStyledItemDelegate::initStyleOption(&v4, index); // like in QCommonStyle::paint(): if (!v4.text.isEmpty()) { painter->save(); painter->setClipRect(v4.rect); QPalette::ColorGroup cg = v4.state & QStyle::State_Enabled ? QPalette::Normal : QPalette::Disabled; if (cg == QPalette::Normal && !(v4.state & QStyle::State_Active)) { cg = QPalette::Inactive; } if (v4.state & QStyle::State_Selected) { painter->setPen(v4.palette.color(cg, QPalette::HighlightedText)); } else { painter->setPen(v4.palette.color(cg, QPalette::Text)); } QRect textRect = v4.widget->style()->subElementRect(QStyle::SE_ItemViewItemText, &v4, v4.widget); viewItemDrawText(painter, &v4, textRect); painter->restore(); } } bool highlightMatchingSubstrings; protected: // bits from qcommonstyle.cpp - void viewItemDrawText(QPainter *p, const QStyleOptionViewItemV4 *option, const QRect &rect) const + void viewItemDrawText(QPainter *p, const QStyleOptionViewItem *option, const QRect &rect) const { const QWidget *widget = option->widget; const int textMargin = widget->style()->pixelMetric(QStyle::PM_FocusFrameHMargin, 0, widget) + 1; QRect textRect = rect.adjusted(textMargin, 0, -textMargin, 0); // remove width padding - const bool wrapText = option->features & QStyleOptionViewItemV2::WrapText; + const bool wrapText = option->features & QStyleOptionViewItem::WrapText; QTextOption textOption; textOption.setWrapMode(wrapText ? QTextOption::WordWrap : QTextOption::ManualWrap); textOption.setTextDirection(option->direction); textOption.setAlignment(QStyle::visualAlignment(option->direction, option->displayAlignment)); QTextLayout textLayout; textLayout.setTextOption(textOption); textLayout.setFont(option->font); QString text = option->text; textLayout.setText(text); if (highlightMatchingSubstrings) { QList formats; QString substring = m_completer->completionPrefix(); QColor underLineColor(p->pen().color()); underLineColor.setAlpha(128); QTextLayout::FormatRange formatRange; formatRange.format.setFontUnderline(true); formatRange.format.setUnderlineColor(underLineColor); for (int i = 0; i < text.length();) { i = text.indexOf(substring, i, Qt::CaseInsensitive); if (i == -1) break; formatRange.length = substring.length(); formatRange.start = i; formats.append(formatRange); i += formatRange.length; } textLayout.setAdditionalFormats(formats); } viewItemTextLayout(textLayout, textRect.width()); const int lineCount = textLayout.lineCount(); QPointF position = textRect.topLeft(); for (int i = 0; i < lineCount; ++i) { const QTextLine line = textLayout.lineAt(i); const QPointF adjustPos(0, qreal(textRect.height() - line.rect().height()) / 2.0); line.draw(p, position + adjustPos); position.setY(position.y() + line.y() + line.ascent()); } } virtual void initStyleOption(QStyleOptionViewItem *option, const QModelIndex &index) const { QStyledItemDelegate::initStyleOption(option, index); - QStyleOptionViewItemV4 *v4 = qstyleoption_cast(option); + QStyleOptionViewItem *v4 = qstyleoption_cast(option); if (v4) { v4->text.clear(); } } KexiCompleter *m_completer; }; // ---- //! @internal Style-dependent fixes for the left margin, probably needed because of the limited //! width of the line edit - it's placed in tab bar's corner widget. static void fixLeftMargin(QLineEdit *lineEdit) { int add = 0; const QByteArray st(lineEdit->style()->objectName().toLatin1()); if (st == "breeze" || st == "gtk+") { add = 4; // like QLineEditIconButton::IconMargin } else if (st == "fusion") { add = 2; } if (add != 0) { QMargins margins(lineEdit->textMargins()); margins.setLeft(margins.left() + add); lineEdit->setTextMargins(margins); } } // ---- KexiSearchLineEdit::KexiSearchLineEdit(QWidget *parent) : QLineEdit(parent), d(new Private(this)) { d->completer = new KexiSearchLineEditCompleter(this); d->popupTreeView = new QTreeView; kexiTester() << KexiTestObject(d->popupTreeView, "globalSearch.treeView"); d->completer->setPopup(d->popupTreeView); d->completer->setModel(d->model = new KexiSearchLineEditCompleterPopupModel(d->completer)); d->completer->setCaseSensitivity(Qt::CaseInsensitive); d->completer->setSubstringCompletion(true); d->completer->setMaxVisibleItems(12); // Use unsorted model, sorting is handled in the source model itself. // Moreover, sorting KexiCompleter::CaseInsensitivelySortedModel breaks // filtering so only table names are displayed. d->completer->setModelSorting(KexiCompleter::UnsortedModel); d->popupTreeView->setHeaderHidden(true); d->popupTreeView->setRootIsDecorated(false); d->popupTreeView->setItemDelegate( d->delegate = new KexiSearchLineEditPopupItemDelegate(d->popupTreeView, d->completer)); // forked initialization like in QLineEdit::setCompleter: d->completer->setWidget(this); if (hasFocus()) { connectCompleter(); } setFocusPolicy(Qt::NoFocus); // We cannot focus set any policy here. // Qt::ClickFocus would make it impossible to find // previously focus widget in KexiSearchLineEdit::setFocus(). // We need this information to focus back when pressing Escape key. setClearButtonEnabled(true); setPlaceholderText(xi18n("Search")); fixLeftMargin(this); } KexiSearchLineEdit::~KexiSearchLineEdit() { delete d; } void KexiSearchLineEdit::connectCompleter() { connect(d->completer, SIGNAL(activated(QString)), this, SLOT(setText(QString))); connect(d->completer, SIGNAL(activated(QModelIndex)), this, SLOT(slotCompletionActivated(QModelIndex))); connect(d->completer, SIGNAL(highlighted(QString)), this, SLOT(slotCompletionHighlighted(QString))); connect(d->completer, SIGNAL(highlighted(QModelIndex)), this, SLOT(slotCompletionHighlighted(QModelIndex))); } void KexiSearchLineEdit::disconnectCompleter() { disconnect(d->completer, 0, this, 0); } void KexiSearchLineEdit::slotClearShortcutActivated() { //qDebug() << (QWidget*)d->previouslyFocusedWidget << text(); d->removeHighlightingForSearchableObject(); if (text().isEmpty() && d->previouslyFocusedWidget) { // after second Escape, go back to previously focused widget d->previouslyFocusedWidget->setFocus(); d->previouslyFocusedWidget = 0; } else { clear(); } } void KexiSearchLineEdit::addSearchableModel(KexiSearchableModel *model) { d->model->addSearchableModel(model); } QPair KexiSearchLineEdit::mapCompletionIndexToSource(const QModelIndex &index) const { QModelIndex realIndex = qobject_cast(d->completer->completionModel())->mapToSource(index); if (!realIndex.isValid()) { return qMakePair(QModelIndex(), static_cast(0)); } SearchableObject *object = static_cast(realIndex.internalPointer()); if (!object) { return qMakePair(QModelIndex(), static_cast(0)); } return qMakePair(object->model->sourceIndexForSearchableObject(object->index), object->model); } void KexiSearchLineEdit::slotCompletionHighlighted(const QString &newText) { if (d->completer->completionMode() != KexiCompleter::InlineCompletion) { setText(newText); } else { int p = cursorPosition(); QString t = text(); setText(t.left(p) + newText.mid(p)); end(false); cursorBackward(text().length() - p, true); } } void KexiSearchLineEdit::slotCompletionHighlighted(const QModelIndex &index) { QPair source = mapCompletionIndexToSource(index); if (!source.first.isValid()) return; //qDebug() << source.second->searchableData(source.first, Qt::EditRole); d->highlightSearchableObject(source); } void KexiSearchLineEdit::slotCompletionActivated(const QModelIndex &index) { QPair source = mapCompletionIndexToSource(index); if (!source.first.isValid()) return; //qDebug() << source.second->searchableData(source.first, Qt::EditRole); d->highlightSearchableObject(source); d->removeHighlightingForSearchableObject(); if (source.second->activateSearchableObject(source.first)) { clear(); } } // forked bits from QLineEdit::inputMethodEvent() void KexiSearchLineEdit::inputMethodEvent(QInputMethodEvent *e) { QLineEdit::inputMethodEvent(e); if (isReadOnly() || !e->isAccepted()) return; if (!e->commitString().isEmpty()) { complete(Qt::Key_unknown); } } void KexiSearchLineEdit::setFocus() { //qDebug() << "d->previouslyFocusedWidget:" << (QWidget*)d->previouslyFocusedWidget // << "window()->focusWidget():" << window()->focusWidget(); if (!d->previouslyFocusedWidget && window()->focusWidget() != this) { d->previouslyFocusedWidget = window()->focusWidget(); } QLineEdit::setFocus(); } // forked bits from QLineEdit::focusInEvent() void KexiSearchLineEdit::focusInEvent(QFocusEvent *e) { //qDebug() << "d->previouslyFocusedWidget:" << (QWidget*)d->previouslyFocusedWidget // << "window()->focusWidget():" << window()->focusWidget(); if (!d->previouslyFocusedWidget && window()->focusWidget() != this) { d->previouslyFocusedWidget = window()->focusWidget(); } QLineEdit::focusInEvent(e); d->completer->setWidget(this); connectCompleter(); update(); } // forked bits from QLineEdit::focusOutEvent() void KexiSearchLineEdit::focusOutEvent(QFocusEvent *e) { QLineEdit::focusOutEvent(e); disconnectCompleter(); update(); if (e->reason() == Qt::TabFocusReason || e->reason() == Qt::BacktabFocusReason) { // go back to previously focused widget if (d->previouslyFocusedWidget) { d->previouslyFocusedWidget->setFocus(); } e->accept(); } d->previouslyFocusedWidget = 0; d->removeHighlightingForSearchableObject(); } // forked bits from QLineControl::processKeyEvent() void KexiSearchLineEdit::keyPressEvent(QKeyEvent *event) { bool inlineCompletionAccepted = false; //qDebug() << event->key() << (QWidget*)d->previouslyFocusedWidget; KexiCompleter::CompletionMode completionMode = d->completer->completionMode(); if ((completionMode == KexiCompleter::PopupCompletion || completionMode == KexiCompleter::UnfilteredPopupCompletion) && d->completer->popup() && d->completer->popup()->isVisible()) { // The following keys are forwarded by the completer to the widget // Ignoring the events lets the completer provide suitable default behavior switch (event->key()) { case Qt::Key_Escape: event->ignore(); return; #ifdef QT_KEYPAD_NAVIGATION case Qt::Key_Select: if (!QApplication::keypadNavigationEnabled()) break; d->completer->popup()->hide(); // just hide. will end up propagating to parent #endif default: break; // normal key processing } } else if (completionMode == KexiCompleter::InlineCompletion) { switch (event->key()) { case Qt::Key_Enter: case Qt::Key_Return: case Qt::Key_F4: #ifdef QT_KEYPAD_NAVIGATION case Qt::Key_Select: if (!QApplication::keypadNavigationEnabled()) break; #endif if (!d->completer->currentCompletion().isEmpty() && hasSelectedText() && textAfterSelection().isEmpty()) { setText(d->completer->currentCompletion()); inlineCompletionAccepted = true; } default: break; // normal key processing } } if (d->completer->popup() && !d->completer->popup()->isVisible() && (event->key() == Qt::Key_F4 || event->key() == Qt::Key_Down)) { // go back to completing when popup is closed and F4/Down pressed d->completer->complete(); } else if (d->completer->popup() && d->completer->popup()->isVisible() && event->key() == Qt::Key_F4) { // hide popup if F4 pressed d->completer->popup()->hide(); } if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) { if (d->completer->popup() && !d->completer->popup()->isVisible()) { d->completer->setCompletionPrefix(text()); } if (d->completer->completionCount() == 1) { // single item on the completion list, select it automatically d->completer->setCurrentRow(0); slotCompletionActivated(d->completer->currentIndex()); event->accept(); if (d->completer->popup()) { d->completer->popup()->hide(); } return; } //qDebug() << "currentRow:" << d->completer->currentRow(); //qDebug() << "currentIndex:" << d->completer->currentIndex().isValid(); //qDebug() << "currentCompletion:" << d->completer->currentCompletion(); if (d->completer->popup() && d->completer->completionCount() > 1) { //qDebug() << "11111" << d->completer->completionPrefix() // << d->completer->completionCount(); // more than one item on completion list, find exact match, if found, accept for (int i = 0; i < d->completer->completionCount(); i++) { //qDebug() << d->completer->completionModel()->index(i, 0, QModelIndex()).data(Qt::EditRole).toString(); if (d->completer->completionPrefix() == d->completer->completionModel()->index(i, 0, QModelIndex()).data(Qt::EditRole).toString()) { d->completer->setCurrentRow(i); slotCompletionActivated(d->completer->currentIndex()); event->accept(); d->completer->popup()->hide(); return; } } // exactly matching item not found bool selectedItem = !d->completer->popup()->selectionModel()->selectedIndexes().isEmpty(); if (!selectedItem || !d->completer->popup()->isVisible()) { if (!d->completer->popup()->isVisible()) { // there is no matching text, go back to completing d->completer->complete(); } // do not hide event->accept(); return; } } // applying completion since there is item selected d->completer->popup()->hide(); connectCompleter(); QLineEdit::keyPressEvent(event); /* executes this: if (hasAcceptableInput() || fixup()) { emit returnPressed(); emit editingFinished(); } */ if (inlineCompletionAccepted) event->accept(); else event->ignore(); return; } if (event == QKeySequence::MoveToNextChar) { #if defined(Q_OS_WIN) if (hasSelectedText() && d->completer->completionMode() == KexiCompleter::InlineCompletion) { int selEnd = selectionEnd(); if (selEnd >= 0) { setCursorPosition(selEnd); } event->accept(); return; } #endif } else if (event == QKeySequence::MoveToPreviousChar) { #if defined(Q_OS_WIN) if (hasSelectedText() && d->completer->completionMode() == KexiCompleter::InlineCompletion) { int selStart = selectionStart(); if (selStart >= 0) { setCursorPosition(selStart); } event->accept(); return; } #endif } else { if (event->modifiers() & Qt::ControlModifier) { switch (event->key()) { case Qt::Key_Up: case Qt::Key_Down: complete(event->key()); return; default:; } } else { // ### check for *no* modifier switch (event->key()) { case Qt::Key_Backspace: if (!isReadOnly()) { backspace(); complete(Qt::Key_Backspace); return; } break; case Qt::Key_Delete: if (!isReadOnly()) { QLineEdit::keyPressEvent(event); complete(Qt::Key_Delete); return; } break; default:; } } } if (!isReadOnly()) { QString t = event->text(); if (!t.isEmpty() && t.at(0).isPrint()) { QLineEdit::keyPressEvent(event); complete(event->key()); return; } } QLineEdit::keyPressEvent(event); } void KexiSearchLineEdit::changeEvent(QEvent *event) { QLineEdit::changeEvent(event); if (event->type() == QEvent::StyleChange) { fixLeftMargin(this); } } // forked bits from QLineControl::advanceToEnabledItem() // iterating forward(dir=1)/backward(dir=-1) from the // current row based. dir=0 indicates a new completion prefix was set. bool KexiSearchLineEdit::advanceToEnabledItem(int dir) { int start = d->completer->currentRow(); if (start == -1) return false; int i = start + dir; if (dir == 0) dir = 1; do { if (!d->completer->setCurrentRow(i)) { if (!d->completer->wrapAround()) break; i = i > 0 ? 0 : d->completer->completionCount() - 1; } else { QModelIndex currentIndex = d->completer->currentIndex(); if (d->completer->completionModel()->flags(currentIndex) & Qt::ItemIsEnabled) return true; i += dir; } } while (i != start); d->completer->setCurrentRow(start); // restore return false; } QString KexiSearchLineEdit::textBeforeSelection() const { return hasSelectedText() ? text().left(selectionStart()) : QString(); } QString KexiSearchLineEdit::textAfterSelection() const { return hasSelectedText() ? text().mid(selectionEnd()) : QString(); } int KexiSearchLineEdit::selectionEnd() const { return hasSelectedText() ? (selectionStart() + selectedText().length()) : -1; } // forked bits from QLineControl::complete() void KexiSearchLineEdit::complete(int key) { if (isReadOnly() || echoMode() != QLineEdit::Normal) return; QString text = this->text(); if (d->completer->completionMode() == KexiCompleter::InlineCompletion) { if (key == Qt::Key_Backspace) return; int n = 0; if (key == Qt::Key_Up || key == Qt::Key_Down) { if (textAfterSelection().length()) return; QString prefix = hasSelectedText() ? textBeforeSelection() : text; if (text.compare(d->completer->currentCompletion(), d->completer->caseSensitivity()) != 0 || prefix.compare(d->completer->completionPrefix(), d->completer->caseSensitivity()) != 0) { d->completer->setCompletionPrefix(prefix); } else { n = (key == Qt::Key_Up) ? -1 : +1; } } else { d->completer->setCompletionPrefix(text); } if (!advanceToEnabledItem(n)) return; } else { #ifndef QT_KEYPAD_NAVIGATION if (text.isEmpty()) { d->completer->popup()->hide(); return; } #endif d->completer->setCompletionPrefix(text); } d->popupTreeView->resizeColumnToContents(0); d->completer->complete(); } bool KexiSearchLineEdit::highlightMatchingSubstrings() const { return d->delegate->highlightMatchingSubstrings; } void KexiSearchLineEdit::setHighlightMatchingSubstrings(bool highlight) { d->delegate->highlightMatchingSubstrings = highlight; } #include "KexiSearchLineEdit.moc" diff --git a/src/plugins/forms/widgets/kexidbimagebox.cpp b/src/plugins/forms/widgets/kexidbimagebox.cpp index aeeed24d2..d413eef85 100644 --- a/src/plugins/forms/widgets/kexidbimagebox.cpp +++ b/src/plugins/forms/widgets/kexidbimagebox.cpp @@ -1,895 +1,895 @@ /* This file is part of the KDE project Copyright (C) 2005 Cedric Pasteur Copyright (C) 2004-2011 JarosÅ‚aw Staniek 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 "kexidbimagebox.h" #include #include #include #include #include #include #include "kexidbutils.h" #include "kexiformpart.h" #include "kexiformmanager.h" #include #include #include #include #include #include #include #include #include #include -#include +#include #include #include #include #include #include #include #include #include #include //! @internal struct KexiDBImageBox_Static { KexiDBImageBox_Static() : pixmap(0), small(0) {} ~KexiDBImageBox_Static() { delete pixmap; delete small; } QPixmap *pixmap; QPixmap *small; }; Q_GLOBAL_STATIC(KexiDBImageBox_Static, KexiDBImageBox_static) KexiDBImageBox::KexiDBImageBox(bool designMode, QWidget *parent) : KexiFrame(parent) , KexiFormDataItemInterface() , m_alignment(Qt::AlignLeft | Qt::AlignTop) , m_readOnly(false) , m_scaledContents(false) , m_smoothTransformation(true) , m_keepAspectRatio(true) , m_insideSetData(false) , m_setFocusOnButtonAfterClosingPopup(false) , m_paintEventEnabled(true) , m_dropDownButtonVisible(true) , m_insideSetPalette(false) { setDesignMode(designMode); installEventFilter(this); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); QPalette pal(palette()); pal.setBrush(backgroundRole(), QBrush(Qt::transparent)); KexiFrame::setPalette(pal); m_contextMenu = new KexiImageContextMenu(this); m_contextMenu->installEventFilter(this); if (designMode) { m_chooser = 0; } else { m_chooser = new KexiDropDownButton(this); m_chooser->setFocusPolicy(Qt::StrongFocus); m_chooser->setMenu(m_contextMenu); setFocusProxy(m_chooser); m_chooser->installEventFilter(this); } setFrameShape(QFrame::Box); setFrameShadow(QFrame::Plain); setFrameColor(palette().color(QPalette::Foreground)); m_paletteBackgroundColorChanged = false; //set this here, not before connect(m_contextMenu, SIGNAL(updateActionsAvailabilityRequested(bool*,bool*)), this, SLOT(slotUpdateActionsAvailabilityRequested(bool*,bool*))); connect(m_contextMenu, SIGNAL(insertFromFileRequested(QUrl)), this, SLOT(handleInsertFromFileAction(QUrl))); connect(m_contextMenu, SIGNAL(saveAsRequested(QUrl)), this, SLOT(handleSaveAsAction(QUrl))); connect(m_contextMenu, SIGNAL(cutRequested()), this, SLOT(handleCutAction())); connect(m_contextMenu, SIGNAL(copyRequested()), this, SLOT(handleCopyAction())); connect(m_contextMenu, SIGNAL(pasteRequested()), this, SLOT(handlePasteAction())); connect(m_contextMenu, SIGNAL(clearRequested()), this, SLOT(clear())); connect(m_contextMenu, SIGNAL(showPropertiesRequested()), this, SLOT(handleShowPropertiesAction())); KexiFrame::setLineWidth(0); setDataSource(QString()); //to initialize popup menu and actions availability } KexiDBImageBox::~KexiDBImageBox() { } KexiImageContextMenu* KexiDBImageBox::contextMenu() const { return m_contextMenu; } QVariant KexiDBImageBox::value() { if (dataSource().isEmpty()) { //not db-aware return QVariant(); } //db-aware mode return m_value; //!< @todo } void KexiDBImageBox::setValueInternal(const QVariant& add, bool removeOld, bool loadPixmap) { if (isReadOnly()) return; m_contextMenu->hide(); if (removeOld) m_value = add.toByteArray(); else //do not add "m_origValue" to "add" as this is QByteArray m_value = KexiDataItemInterface::originalValue().toByteArray(); bool ok = !m_value.isEmpty(); if (ok) { if (loadPixmap) { ok = KexiUtils::loadPixmapFromData(&m_pixmap, m_value); m_currentScaledPixmap = QPixmap(); // clear cache } if (!ok) { //! @todo inform about error? } } if (!ok) { m_valueMimeType.clear(); m_pixmap = QPixmap(); m_currentScaledPixmap = QPixmap(); // clear cache } repaint(); } void KexiDBImageBox::setInvalidState(const QString& displayText) { Q_UNUSED(displayText); if (!dataSource().isEmpty()) { m_value = QByteArray(); } //! @todo m_pixmapLabel->setText( displayText ); if (m_chooser) m_chooser->hide(); setReadOnly(true); } bool KexiDBImageBox::valueIsNull() { return m_value.isEmpty(); } bool KexiDBImageBox::valueIsEmpty() { return false; } bool KexiDBImageBox::isReadOnly() const { return m_readOnly; } void KexiDBImageBox::setReadOnly(bool set) { m_readOnly = set; } QPixmap KexiDBImageBox::pixmap() const { if (dataSource().isEmpty()) { //not db-aware return m_data.pixmap(); } //db-aware mode return m_pixmap; } int KexiDBImageBox::pixmapId() const { if (dataSource().isEmpty()) { //not db-aware return m_data.id(); } return 0; } void KexiDBImageBox::setPixmapId(int id) { if (m_insideSetData) //avoid recursion return; setData(KexiBLOBBuffer::self()->objectForId(id, /*unstored*/false)); repaint(); } int KexiDBImageBox::storedPixmapId() const { if (dataSource().isEmpty() && m_data.stored()) { //not db-aware return m_data.id(); } return 0; } void KexiDBImageBox::setStoredPixmapId(int id) { setData(KexiBLOBBuffer::self()->objectForId(id, /*stored*/true)); repaint(); } bool KexiDBImageBox::hasScaledContents() const { return m_scaledContents; } void KexiDBImageBox::setScaledContents(bool set) { //! @todo m_pixmapLabel->setScaledContents(set); m_scaledContents = set; m_currentScaledPixmap = QPixmap(); repaint(); } bool KexiDBImageBox::smoothTransformation() const { return m_smoothTransformation; } Qt::Alignment KexiDBImageBox::alignment() const { return m_alignment; } bool KexiDBImageBox::keepAspectRatio() const { return m_keepAspectRatio; } void KexiDBImageBox::setSmoothTransformation(bool set) { m_smoothTransformation = set; m_currentScaledPixmap = QPixmap(); repaint(); } void KexiDBImageBox::setKeepAspectRatio(bool set) { m_keepAspectRatio = set; m_currentScaledPixmap = QPixmap(); if (m_scaledContents) { repaint(); } } QWidget* KexiDBImageBox::widget() { //! @todo return m_pixmapLabel; return this; } bool KexiDBImageBox::cursorAtStart() { return true; } bool KexiDBImageBox::cursorAtEnd() { return true; } QByteArray KexiDBImageBox::data() const { if (dataSource().isEmpty()) { //static mode return m_data.data(); } else { //db-aware mode return m_value; } } void KexiDBImageBox::insertFromFile() { m_contextMenu->insertFromFile(); } void KexiDBImageBox::handleInsertFromFileAction(const QUrl &url) { if (!dataSource().isEmpty() && isReadOnly()) return; if (dataSource().isEmpty()) { //static mode KexiBLOBBuffer::Handle h = KexiBLOBBuffer::self()->insertPixmap(url); if (!h) return; setData(h); repaint(); } else { //db-aware QString fileName(url.isLocalFile() ? url.toLocalFile() : url.toDisplayString()); //! @todo download the file if remote, then set fileName properly QFile f(fileName); if (!f.open(QIODevice::ReadOnly)) { //! @todo err msg return; } QByteArray ba = f.readAll(); if (f.error() != QFile::NoError) { //! @todo err msg f.close(); return; } QMimeDatabase db; m_valueMimeType = db.mimeTypeForFile(fileName, QMimeDatabase::MatchExtension).name(); setValueInternal(ba, true); } //! @todo emit signal for setting "dirty" flag within the design if (!dataSource().isEmpty()) { signalValueChanged(); } } void KexiDBImageBox::handleAboutToSaveAsAction( QString* origFilename, QString* mimeType, bool *dataIsEmpty) { Q_ASSERT(origFilename); Q_ASSERT(mimeType); Q_ASSERT(dataIsEmpty); if (data().isEmpty()) { qWarning() << "no pixmap!"; *dataIsEmpty = false; return; } if (dataSource().isEmpty()) { //for static images filename and mimetype can be available *origFilename = m_data.originalFileName(); if (!origFilename->isEmpty()) { *origFilename = QLatin1String("/") + *origFilename; } const QMimeDatabase db; const QMimeType mime(db.mimeTypeForName(m_data.mimeType())); if (!m_data.mimeType().isEmpty() && QImageReader::supportedMimeTypes().contains(mime.name().toLatin1())) { *mimeType = mime.name(); } } } bool KexiDBImageBox::handleSaveAsAction(const QUrl &url) { //! @todo handle remote URLs QFile f(url.toLocalFile()); if (!f.open(QIODevice::WriteOnly)) { //! @todo err msg return false; } f.write(data()); if (f.error() != QFile::NoError) { //! @todo err msg f.close(); return false; } f.close(); return true; } void KexiDBImageBox::handleCutAction() { if (!dataSource().isEmpty() && isReadOnly()) return; handleCopyAction(); clear(); } void KexiDBImageBox::handleCopyAction() { qApp->clipboard()->setPixmap(pixmap(), QClipboard::Clipboard); } void KexiDBImageBox::handlePasteAction() { if (isReadOnly() || (!designMode() && !hasFocus())) return; QPixmap pm(qApp->clipboard()->pixmap(QClipboard::Clipboard)); if (dataSource().isEmpty()) { //static mode KexiBLOBBuffer::Handle h = KexiBLOBBuffer::self()->insertPixmap(pm); if (!h) return; setData(h); } else { //db-aware mode m_pixmap = pm; QByteArray ba; QBuffer buffer(&ba); buffer.open(QIODevice::WriteOnly); if (m_pixmap.save(&buffer, "PNG")) { // write pixmap into ba in PNG format setValueInternal(ba, true, false/* !loadPixmap */); m_currentScaledPixmap = QPixmap(); // clear cache } else { setValueInternal(QByteArray(), true); } } repaint(); if (!dataSource().isEmpty()) { signalValueChanged(); } } void KexiDBImageBox::clear() { if (dataSource().isEmpty()) { //static mode setData(KexiBLOBBuffer::Handle()); } else { if (isReadOnly()) return; //db-aware mode setValueInternal(QByteArray(), true); } //! @todo emit signal for setting "dirty" flag within the design repaint(); if (!dataSource().isEmpty()) { signalValueChanged(); } } void KexiDBImageBox::handleShowPropertiesAction() { //! @todo } void KexiDBImageBox::slotUpdateActionsAvailabilityRequested(bool* valueIsNull, bool* valueIsReadOnly) { Q_ASSERT(valueIsNull); Q_ASSERT(valueIsReadOnly); *valueIsNull = !( (dataSource().isEmpty() && !pixmap().isNull()) /*static pixmap available*/ || (!dataSource().isEmpty() && !this->valueIsNull()) /*db-aware pixmap available*/ ); // read-only if static pixmap or db-aware pixmap for read-only widget: *valueIsReadOnly = (!designMode() && dataSource().isEmpty()) || (!dataSource().isEmpty() && isReadOnly()) || (designMode() && !dataSource().isEmpty()); } void KexiDBImageBox::contextMenuEvent(QContextMenuEvent * e) { if (popupMenuAvailable()) m_contextMenu->exec(e->globalPos()); } void KexiDBImageBox::updateActionStrings() { if (!m_contextMenu) return; if (designMode()) { } else { //update title in data view mode, based on the data source if (columnInfo()) { KexiImageContextMenu::updateTitle(m_contextMenu, columnInfo()->captionOrAliasOrName(), KexiFormManager::self()->library()->iconName(metaObject()->className())); } } if (m_chooser) { if (popupMenuAvailable() && dataSource().isEmpty()) { //this may work in the future (see @todo below) m_chooser->setToolTip(xi18n("Click to show actions for this image box")); } else { QString beautifiedImageBoxName; if (designMode()) { beautifiedImageBoxName = dataSource(); } else { beautifiedImageBoxName = columnInfo() ? columnInfo()->captionOrAliasOrName() : QString(); /*! @todo look at makeFirstCharacterUpperCaseInCaptions setting [bool] (see doc/dev/settings.txt) */ beautifiedImageBoxName = beautifiedImageBoxName[0].toUpper() + beautifiedImageBoxName.mid(1); } m_chooser->setToolTip( xi18n("Click to show actions for %1 image box", beautifiedImageBoxName)); } } } bool KexiDBImageBox::popupMenuAvailable() { /*! @todo add kexi-global setting which anyway, allows to show this button (read-only actions like copy/save as/print can be available) */ //chooser button can be only visible when data source is specified return !dataSource().isEmpty(); } void KexiDBImageBox::setDataSource(const QString &ds) { KexiFormDataItemInterface::setDataSource(ds); setData(KexiBLOBBuffer::Handle()); updateActionStrings(); KexiFrame::setFocusPolicy(focusPolicy()); //set modified policy if (m_chooser) { m_chooser->setEnabled(popupMenuAvailable()); if (m_dropDownButtonVisible && popupMenuAvailable()) { m_chooser->show(); } else { m_chooser->hide(); } } // update some properties not changed by user //! @todo get default line width from global style settings // if (!m_lineWidthChanged) { // KexiFrame::setLineWidth(ds.isEmpty() ? 0 : 1); // } if (!m_paletteBackgroundColorChanged && parentWidget()) { QPalette p = palette(); p.setColor(backgroundRole(), dataSource().isEmpty() ? parentWidget()->palette().color(parentWidget()->backgroundRole()) : palette().color(QPalette::Active, QPalette::Base) ); KexiFrame::setPalette(p); } } QSize KexiDBImageBox::sizeHint() const { if (pixmap().isNull()) return QSize(80, 80); return pixmap().size(); } int KexiDBImageBox::realLineWidth() const { switch (frameShape()) { case QFrame::NoFrame: // shadow, line, midline unused return 0; case QFrame::Box: switch (frameShadow()) { case QFrame::Plain: // midline unused return lineWidth(); default: // sunken, raised: return 2 * lineWidth() + midLineWidth(); } break; case QFrame::Panel: // shadow, midline unused return lineWidth(); case QFrame::WinPanel: // shadow, line, midline unused return 2; case QFrame::StyledPanel: { // shadow, line, midline unused - QStyleOptionFrameV3 option; + QStyleOptionFrame option; option.initFrom(this); return style()->pixelMetric(QStyle::PM_DefaultFrameWidth, &option, this); } default: return lineWidth(); } } static QPixmap *scaledImageBoxIcon(const QMargins& margins, const QSize& size) { const int realHeight = size.height() - margins.top() - margins.bottom(); const int realWidth = size.width() - margins.left() - margins.right(); if ( realHeight <= KexiDBImageBox_static->pixmap->height() || realWidth <= KexiDBImageBox_static->pixmap->width()) { if ( realHeight <= KexiDBImageBox_static->small->height() || realWidth <= KexiDBImageBox_static->small->width()) { return 0; } return KexiDBImageBox_static->small; } return KexiDBImageBox_static->pixmap; } void KexiDBImageBox::paintEvent(QPaintEvent *pe) { if (!m_paintEventEnabled) return; QPainter p(this); p.setClipRect(pe->rect()); QMargins margins(contentsMargins()); margins += realLineWidth(); if (designMode() && pixmap().isNull()) { QRect r( QPoint(margins.left(), margins.top()), size() - QSize(margins.left() + margins.right(), margins.top() + margins.bottom())); updatePixmap(); QPixmap *imagBoxPm = scaledImageBoxIcon(margins, size()); if (imagBoxPm) { p.drawPixmap(2, height() - margins.top() - margins.bottom() - imagBoxPm->height() - 2, *imagBoxPm); } QFont f(qApp->font()); p.setFont(f); const QFontMetrics fm(fontMetrics()); QString text; if (dataSource().isEmpty()) { if ((fm.height() * 2) > height()) { text = xi18nc("Unbound Image Box", "%1 (unbound)", objectName()); } else { text = xi18nc("Unbound Image Box", "%1\n(unbound)", objectName()); } } else { text = dataSource(); const QPixmap dataSourceTagIcon(KexiFormUtils::dataSourceTagIcon()); if (width() >= (dataSourceTagIcon.width() + 2 + fm.boundingRect(r, Qt::AlignCenter, text).width())) { r.setLeft( r.left() + dataSourceTagIcon.width() + 2 ); // make some room for the [>] icon QRect bounding = fm.boundingRect(r, Qt::AlignCenter, text); p.drawPixmap( bounding.left() - dataSourceTagIcon.width() - 2, bounding.top() + bounding.height() / 2 - dataSourceTagIcon.height() / 2, dataSourceTagIcon); } } p.drawText(r, Qt::AlignCenter, text); } else { QSize internalSize(size()); if (m_chooser && m_dropDownButtonVisible && !dataSource().isEmpty()) internalSize.setWidth(internalSize.width() - m_chooser->width()); const QRect internalRect(QPoint(0, 0), internalSize); if (m_currentScaledPixmap.isNull() || internalRect != m_currentRect) { m_currentRect = internalRect; m_currentPixmapPos = QPoint(0, 0); m_currentScaledPixmap = KexiUtils::scaledPixmap( margins, m_currentRect, pixmap(), &m_currentPixmapPos, m_alignment, m_scaledContents, m_keepAspectRatio, m_smoothTransformation ? Qt::SmoothTransformation : Qt::FastTransformation); } p.drawPixmap(m_currentPixmapPos, m_currentScaledPixmap); } KexiFrame::drawFrame(&p); if (designMode()) { const bool hasFrame = frameWidth() >= 1 && frameShape() != QFrame::NoFrame; if (!hasFrame) { KFormDesigner::paintWidgetFrame(p, rect()); } } else { // data mode // if the widget is focused, draw focus indicator rect _if_ there is no chooser button if ( !dataSource().isEmpty() && hasFocus() && (!m_chooser || !m_chooser->isVisible())) { QStyleOptionFocusRect option; option.initFrom(this); style()->drawPrimitive( QStyle::PE_FrameFocusRect, &option, &p, this); } } } void KexiDBImageBox::updatePixmap() { if (!(designMode() && pixmap().isNull())) return; if (!KexiDBImageBox_static->pixmap) { QPixmap pm( KIconLoader::global()->loadMimeTypeIcon( koIconNameCStr("image-x-generic"), KIconLoader::NoGroup, KIconLoader::SizeLarge, KIconLoader::DisabledState) ); if (!pm.isNull()) { KIconEffect::semiTransparent(pm); KIconEffect::semiTransparent(pm); } KexiDBImageBox_static->pixmap = new QPixmap(pm); KexiDBImageBox_static->small = new QPixmap( KexiDBImageBox_static->pixmap->scaled( KexiDBImageBox_static->pixmap->width() / 2, KexiDBImageBox_static->pixmap->height() / 2, Qt::KeepAspectRatio, Qt::SmoothTransformation) ); } } void KexiDBImageBox::setAlignment(Qt::Alignment alignment) { m_alignment = alignment; m_currentScaledPixmap = QPixmap(); // clear cache repaint(); } void KexiDBImageBox::setData(const KexiBLOBBuffer::Handle& handle) { if (m_insideSetData) //avoid recursion return; m_insideSetData = true; m_data = handle; m_currentScaledPixmap = QPixmap(); // clear cache emit idChanged(handle.id()); m_insideSetData = false; update(); } void KexiDBImageBox::resizeEvent(QResizeEvent * e) { KexiFrame::resizeEvent(e); if (m_chooser) { QSize s(m_chooser->sizeHint()); const int _realLineWidth = realLineWidth(); QSize margin(_realLineWidth, _realLineWidth); s.setHeight(height() - 2*margin.height()); s = s.boundedTo(size() - 2 * margin); m_chooser->resize(s); m_chooser->move(QRect(QPoint(0, 0), e->size() - m_chooser->size() - margin + QSize(1, 1)).bottomRight()); } } void KexiDBImageBox::setColumnInfo(KDbQueryColumnInfo* cinfo) { KexiFormDataItemInterface::setColumnInfo(cinfo); //updating strings and title is needed updateActionStrings(); } bool KexiDBImageBox::keyPressed(QKeyEvent *ke) { // Esc key should close the popup if (ke->modifiers() == Qt::NoModifier && ke->key() == Qt::Key_Escape) { if (m_contextMenu->isVisible()) { m_setFocusOnButtonAfterClosingPopup = true; return true; } } return false; } void KexiDBImageBox::setPalette(const QPalette &pal) { KexiFrame::setPalette(pal); if (m_insideSetPalette) return; m_insideSetPalette = true; setPaletteBackgroundColor(pal.color(QPalette::Active, QPalette::Base)); QPalette p(palette()); p.setColor(foregroundRole(), pal.color(foregroundRole())); setPalette(p); m_insideSetPalette = false; } void KexiDBImageBox::setPaletteBackgroundColor(const QColor & color) { m_paletteBackgroundColorChanged = true; QPalette pal(palette()); pal.setColor(backgroundRole(), color); setPalette(pal); if (m_chooser) m_chooser->setPalette(qApp->palette()); } bool KexiDBImageBox::dropDownButtonVisible() const { return m_dropDownButtonVisible; } int KexiDBImageBox::lineWidth() const { return KexiFrame::lineWidth(); } void KexiDBImageBox::setDropDownButtonVisible(bool set) { //! @todo use global default setting for this property if (m_dropDownButtonVisible == set) return; m_dropDownButtonVisible = set; if (m_chooser) { if (m_dropDownButtonVisible) m_chooser->show(); else m_chooser->hide(); } } bool KexiDBImageBox::subwidgetStretchRequired(KexiDBAutoField* autoField) const { Q_UNUSED(autoField); return true; } bool KexiDBImageBox::eventFilter(QObject * watched, QEvent * e) { if (watched == this || watched == m_chooser) { //we're watching chooser as well because it's a focus proxy even if invisible if (e->type() == QEvent::FocusIn || e->type() == QEvent::FocusOut || e->type() == QEvent::MouseButtonPress) { update(); //to repaint focus rect } } // hide popup menu as soon as it loses focus if (watched == m_contextMenu && e->type() == QEvent::FocusOut) { m_contextMenu->hide(); } return KexiFrame::eventFilter(watched, e); } void KexiDBImageBox::setValueInternal(const QVariant& add, bool removeOld) { setValueInternal(add, removeOld, true /*loadPixmap*/); } Qt::FocusPolicy KexiDBImageBox::focusPolicy() const { if (dataSource().isEmpty()) return Qt::NoFocus; return m_focusPolicyInternal; } Qt::FocusPolicy KexiDBImageBox::focusPolicyInternal() const { return m_focusPolicyInternal; } void KexiDBImageBox::setFocusPolicy(Qt::FocusPolicy policy) { m_focusPolicyInternal = policy; KexiFrame::setFocusPolicy(focusPolicy()); //set modified policy } void KexiDBImageBox::setFrameShape(QFrame::Shape s) { KexiFrame::setFrameShape(s); m_currentScaledPixmap = QPixmap(); // clear cache update(); } void KexiDBImageBox::setFrameShadow(QFrame::Shadow s) { KexiFrame::setFrameShadow(s); m_currentScaledPixmap = QPixmap(); // clear cache update(); } void KexiDBImageBox::setLineWidth(int w) { KexiFrame::setLineWidth(w); m_currentScaledPixmap = QPixmap(); // clear cache update(); } void KexiDBImageBox::setMidLineWidth(int w) { KexiFrame::setMidLineWidth(w); m_currentScaledPixmap = QPixmap(); // clear cache update(); } diff --git a/src/plugins/forms/widgets/kexidblineedit.cpp b/src/plugins/forms/widgets/kexidblineedit.cpp index 9080ac553..ec40ca7b1 100644 --- a/src/plugins/forms/widgets/kexidblineedit.cpp +++ b/src/plugins/forms/widgets/kexidblineedit.cpp @@ -1,464 +1,464 @@ /* This file is part of the KDE project Copyright (C) 2005 Cedric Pasteur Copyright (C) 2004-2014 JarosÅ‚aw Staniek 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 "kexidblineedit.h" #include "kexidbautofield.h" #include #include #include #include #include #include #include #include #include #include #include #include //! @internal A validator used for read only flag to disable editing class KexiDBLineEdit_ReadOnlyValidator : public QValidator { Q_OBJECT public: KexiDBLineEdit_ReadOnlyValidator(QObject * parent) : QValidator(parent) { } ~KexiDBLineEdit_ReadOnlyValidator() {} virtual State validate(QString &input, int &pos) const { input = qobject_cast(parent())->originalText(); pos = qobject_cast(parent())->originalCursorPosition(); return Intermediate; } }; //----- //! A style proxy overriding KexiDBLineEdit style class KexiDBLineEditStyle : public QProxyStyle { Q_OBJECT public: explicit KexiDBLineEditStyle(const QString &baseStyleName) : QProxyStyle(baseStyleName), indent(0) { } virtual ~KexiDBLineEditStyle() { } void setIndent(int indent) { this->indent = indent; } QRect subElementRect(SubElement element, const QStyleOption *option, const QWidget *widget = 0) const { const KFormDesigner::FormWidgetInterface *formWidget = dynamic_cast(widget); if (formWidget && formWidget->designMode()) { const KexiFormDataItemInterface *dataItemIface = dynamic_cast(widget); if (dataItemIface && !dataItemIface->dataSource().isEmpty() && !formWidget->editingMode()) { if (element == SE_LineEditContents) { QRect rect = QProxyStyle::subElementRect(SE_LineEditContents, option, widget); if (option->direction == Qt::LeftToRight) return rect.adjusted(indent, 0, 0, 0); else return rect.adjusted(0, 0, -indent, 0); } } } return QProxyStyle::subElementRect(element, option, widget); } int indent; }; //----- KexiDBLineEdit::KexiDBLineEdit(QWidget *parent) : QLineEdit(parent) , KexiDBTextWidgetInterface() , KexiFormDataItemInterface() , m_readWriteValidator(0) , m_menuExtender(this, this) , m_internalReadOnly(false) , m_slotTextChanged_enabled(true) , m_cursorPosition(0) , m_paletteChangeEvent_enabled(true) , m_inStyleChangeEvent(false) { QFont tmpFont; tmpFont.setPointSize(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont).pointSize()); setMinimumHeight(QFontMetrics(tmpFont).height() + 6); m_originalPalette = palette(); connect(this, SIGNAL(textChanged(QString)), this, SLOT(slotTextChanged(QString))); connect(this, SIGNAL(textEdited(QString)), this, SLOT(slotTextEdited(QString))); connect(this, SIGNAL(cursorPositionChanged(int,int)), this, SLOT(slotCursorPositionChanged(int,int))); m_internalStyle = new KexiDBLineEditStyle(style()->objectName()); m_internalStyle->setParent(this); m_internalStyle->setIndent(KexiFormUtils::dataSourceTagIcon().width()); m_inStyleChangeEvent = true; // do not allow QLineEdit::event() to touch the style setStyle(m_internalStyle); m_inStyleChangeEvent = false; KexiDataItemInterface::setLengthExceededEmittedAtPreviousChange(false); } KexiDBLineEdit::~KexiDBLineEdit() { } void KexiDBLineEdit::setInvalidState(const QString& displayText) { QLineEdit::setReadOnly(true); //! @todo move this to KexiDataItemInterface::setInvalidStateInternal() ? if (focusPolicy() & Qt::TabFocus) setFocusPolicy(Qt::ClickFocus); setValueInternal(displayText, true); } void KexiDBLineEdit::setValueInternal(const QVariant& add, bool removeOld) { m_slotTextChanged_enabled = false; bool lengthExceeded; m_originalText = m_textFormatter.toString( removeOld ? QVariant() : KexiDataItemInterface::originalValue(), add.toString(), &lengthExceeded); setText(m_originalText); setCursorPosition(0); //ok? emitLengthExceededIfNeeded(lengthExceeded); m_slotTextChanged_enabled = true; } QVariant KexiDBLineEdit::value() { return m_textFormatter.fromString(text()); } void KexiDBLineEdit::slotTextChanged(const QString&) { if (!m_slotTextChanged_enabled) return; signalValueChanged(); } void KexiDBLineEdit::slotTextEdited(const QString& text) { bool lengthExceeded = m_textFormatter.lengthExceeded(text); emitLengthExceededIfNeeded(lengthExceeded); } bool KexiDBLineEdit::fixup() { const QString t(text()); bool lengthExceeded = m_textFormatter.lengthExceeded(t); if (lengthExceeded) { m_slotTextChanged_enabled = false; setText(t.left(field()->maxLength())); m_slotTextChanged_enabled = true; } return true; } void KexiDBLineEdit::slotCursorPositionChanged(int oldPos, int newPos) { Q_UNUSED(oldPos); if (m_originalText == text()) { // when cursor was moved without altering the text, remember its position, // otherwise the change will be reverted by the validator m_cursorPosition = newPos; } } int KexiDBLineEdit::originalCursorPosition() const { return m_cursorPosition; } bool KexiDBLineEdit::valueIsNull() { return valueIsEmpty(); //ok??? text().isNull(); } bool KexiDBLineEdit::valueIsEmpty() { return m_textFormatter.valueIsEmpty( text() ); } bool KexiDBLineEdit::valueIsValid() { return m_textFormatter.valueIsValid( text() ); } bool KexiDBLineEdit::isReadOnly() const { return m_internalReadOnly; } void KexiDBLineEdit::updatePalette() { m_paletteChangeEvent_enabled = false; setPalette(m_internalReadOnly ? KexiUtils::paletteForReadOnly(m_originalPalette) : m_originalPalette); m_paletteChangeEvent_enabled = true; } void KexiDBLineEdit::changeEvent(QEvent *e) { if (e->type() == QEvent::PaletteChange && m_paletteChangeEvent_enabled) { m_originalPalette = palette(); updatePalette(); } QLineEdit::changeEvent(e); } void KexiDBLineEdit::setReadOnly(bool readOnly) { m_internalReadOnly = readOnly; updatePalette(); if (!designMode()) { if (m_internalReadOnly) { if (m_readWriteValidator) disconnect(m_readWriteValidator, SIGNAL(destroyed(QObject*)), this, SLOT(slotReadWriteValidatorDestroyed(QObject*))); m_readWriteValidator = validator(); if (m_readWriteValidator) connect(m_readWriteValidator, SIGNAL(destroyed(QObject*)), this, SLOT(slotReadWriteValidatorDestroyed(QObject*))); if (!m_readOnlyValidator) m_readOnlyValidator = new KexiDBLineEdit_ReadOnlyValidator(this); setValidator(m_readOnlyValidator); } else { //revert to r/w validator setValidator(m_readWriteValidator); } } } void KexiDBLineEdit::slotReadWriteValidatorDestroyed(QObject*) { m_readWriteValidator = 0; } void KexiDBLineEdit::contextMenuEvent(QContextMenuEvent *e) { QMenu *menu = createStandardContextMenu(); m_menuExtender.exec(menu, e->globalPos()); delete menu; } QWidget* KexiDBLineEdit::widget() { return this; } bool KexiDBLineEdit::cursorAtStart() { return cursorPosition() == 0; } bool KexiDBLineEdit::cursorAtEnd() { return KexiUtils::cursorAtEnd(this);; } void KexiDBLineEdit::clear() { if (!m_internalReadOnly) QLineEdit::clear(); } void KexiDBLineEdit::setColumnInfo(KDbQueryColumnInfo* cinfo) { KexiFormDataItemInterface::setColumnInfo(cinfo); m_textFormatter.setField(cinfo ? cinfo->field() : nullptr); KexiTextFormatter::OverrideDecimalPlaces overrideDecimalPlaces; overrideDecimalPlaces.enabled = true; overrideDecimalPlaces.value = -1; // all possible digits m_textFormatter.setOverrideDecimalPlaces(overrideDecimalPlaces); m_textFormatter.setGroupSeparatorsEnabled(false); // needed, otherwise text box contains separators (confusing) if (!cinfo) return; //! @todo handle input mask (via QLineEdit::setInputMask()) using a special KDbFieldInputMask class delete m_readWriteValidator; KDbFieldValidator* fieldValidator = new KDbFieldValidator(*cinfo->field(), this); if (m_internalReadOnly) { m_readWriteValidator = fieldValidator; } else { setValidator(fieldValidator); } const QString inputMask(m_textFormatter.inputMask()); if (!inputMask.isEmpty()) setInputMask(inputMask); KexiDBTextWidgetInterface::setColumnInfo(cinfo, this); } void KexiDBLineEdit::paintEvent(QPaintEvent *pe) { QLineEdit::paintEvent(pe); KFormDesigner::FormWidgetInterface *formWidget = dynamic_cast(this); if (formWidget && formWidget->designMode()) { KexiFormDataItemInterface *dataItemIface = dynamic_cast(this); if (dataItemIface && !dataItemIface->dataSource().isEmpty() && !formWidget->editingMode()) { // draw "data source tag" icon QPainter p(this); - QStyleOptionFrameV2 option; + QStyleOptionFrame option; initStyleOption(&option); int leftMargin, topMargin, rightMargin, bottomMargin; getContentsMargins(&leftMargin, &topMargin, &rightMargin, &bottomMargin); QRect r( style()->subElementRect(QStyle::SE_LineEditContents, &option, this) ); r.setX(r.x() + leftMargin); r.setY(r.y() + topMargin); r.setRight(r.right() - rightMargin); r.setBottom(r.bottom() - bottomMargin); QPixmap dataSourceTagIcon; int x; if (layoutDirection() == Qt::LeftToRight) { dataSourceTagIcon = KexiFormUtils::dataSourceTagIcon(); x = r.left() - dataSourceTagIcon.width() + 2; } else { dataSourceTagIcon = KexiFormUtils::dataSourceRTLTagIcon(); x = r.right() - 2; } p.drawPixmap( x, r.top() + (r.height() - dataSourceTagIcon.height()) / 2, dataSourceTagIcon ); } } } bool KexiDBLineEdit::event(QEvent * e) { if (e->type() == QEvent::StyleChange) { if (m_inStyleChangeEvent) { return true; } // let the QLineEdit set its KLineEditStyle if (!QLineEdit::event(e)) { return false; } // move the KLineEditStyle inside our internal style as parent m_internalStyle->setParent(style()); m_inStyleChangeEvent = true; // avoid recursion setStyle(m_internalStyle); m_inStyleChangeEvent = false; return true; } const bool ret = QLineEdit::event(e); KexiDBTextWidgetInterface::event(e, this, text().isEmpty()); if (e->type() == QEvent::FocusOut) { QFocusEvent *fe = static_cast(e); if (fe->reason() == Qt::TabFocusReason || fe->reason() == Qt::BacktabFocusReason) { //display aligned to left after loosing the focus (only if this is tab/backtab event) //! @todo add option to set cursor at the beginning setCursorPosition(0); //ok? } } return ret; } bool KexiDBLineEdit::appendStretchRequired(KexiDBAutoField* autoField) const { return KexiDBAutoField::Top == autoField->labelPosition(); } void KexiDBLineEdit::handleAction(const QString& actionName) { if (actionName == "edit_copy") { copy(); } else if (actionName == "edit_paste") { paste(); } else if (actionName == "edit_cut") { cut(); } //! @todo ? } void KexiDBLineEdit::setDisplayDefaultValue(QWidget *widget, bool displayDefaultValue) { KexiFormDataItemInterface::setDisplayDefaultValue(widget, displayDefaultValue); // initialize display parameters for default / entered value KexiDisplayUtils::DisplayParameters * const params = displayDefaultValue ? m_displayParametersForDefaultValue : m_displayParametersForEnteredValue; setFont(params->font); QPalette pal(palette()); pal.setColor(QPalette::Active, QPalette::Text, params->textColor); setPalette(pal); } void KexiDBLineEdit::undo() { cancelEditor(); } void KexiDBLineEdit::moveCursorToEnd() { QLineEdit::end(false/*!mark*/); } void KexiDBLineEdit::moveCursorToStart() { QLineEdit::home(false/*!mark*/); } void KexiDBLineEdit::selectAll() { QLineEdit::selectAll(); } bool KexiDBLineEdit::keyPressed(QKeyEvent *ke) { Q_UNUSED(ke); return false; } void KexiDBLineEdit::updateTextForDataSource() { if (!designMode()) return; setText(dataSource()); } void KexiDBLineEdit::setDataSource(const QString &ds) { KexiFormDataItemInterface::setDataSource(ds); updateTextForDataSource(); } void KexiDBLineEdit::setDataSourcePluginId(const QString &pluginId) { KexiFormDataItemInterface::setDataSourcePluginId(pluginId); updateTextForDataSource(); } #include "kexidblineedit.moc" diff --git a/src/widget/KexiListView.cpp b/src/widget/KexiListView.cpp index ecd5bcabe..f79a4bbbb 100644 --- a/src/widget/KexiListView.cpp +++ b/src/widget/KexiListView.cpp @@ -1,255 +1,255 @@ /* This file is part of the KDE project Copyright (C) 2016 JarosÅ‚aw Staniek Forked from kwidgetsaddons/src/kpageview_p.cpp: Copyright (C) 2006 Tobias Koenig (tokoe@kde.org) Copyright (C) 2007 Rafael Fernández López (ereslibre@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 "KexiListView.h" #include "KexiListView_p.h" #include #include #include const int KEXILISTVIEW_VERTICAL_MARGIN = 10; const int KEXILISTVIEW_HORIZONTAL_MARGIN = 12; KexiListView::KexiListView(QWidget *parent) : QListView(parent) { setViewMode(QListView::ListMode); setMovement(QListView::Static); setVerticalScrollMode(QListView::ScrollPerPixel); setItemDelegate(new KexiListViewDelegate(this)); } KexiListView::~KexiListView() { } void KexiListView::setModel(QAbstractItemModel *model) { /* KPageListViewProxy *proxy = new KPageListViewProxy( this ); proxy->setSourceModel( model ); proxy->rebuildMap(); connect( model, SIGNAL(layoutChanged()), proxy, SLOT(rebuildMap()) ); */ connect(model, SIGNAL(layoutChanged()), this, SLOT(updateWidth())); // QListView::setModel( proxy ); QListView::setModel(model); // Set our own selection model, which won't allow our current selection to be cleared setSelectionModel(new KexiListViewSelectionModel(model, this)); updateWidth(); } void KexiListView::updateWidth() { if (!model()) { return; } int rows = model()->rowCount(); int width = 0; for (int i = 0; i < rows; ++i) { width = qMax(width, sizeHintForIndex(model()->index(i, 0)).width()); } setFixedWidth(width + KEXILISTVIEW_HORIZONTAL_MARGIN * 2); } // ---- KexiListViewDelegate::KexiListViewDelegate(QObject *parent) : QAbstractItemDelegate(parent) { } static int layoutText(QTextLayout *layout, int maxWidth) { qreal height = 0; int textWidth = 0; layout->beginLayout(); while (true) { QTextLine line = layout->createLine(); if (!line.isValid()) { break; } line.setLineWidth(maxWidth); line.setPosition(QPointF(0, height)); height += line.height(); textWidth = qMax(textWidth, qRound(line.naturalTextWidth() + 0.5)); } layout->endLayout(); return textWidth; } void KexiListViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { if (!index.isValid()) { return; } - QStyleOptionViewItemV4 opt(option); + QStyleOptionViewItem opt(option); opt.showDecorationSelected = true; QStyle *style = opt.widget ? opt.widget->style() : QApplication::style(); int iconSize = style->pixelMetric(QStyle::PM_IconViewIconSize); const QString text = index.model()->data(index, Qt::DisplayRole).toString(); const QIcon icon = index.model()->data(index, Qt::DecorationRole).value(); const QPixmap pixmap = icon.pixmap(iconSize, iconSize, (option.state & QStyle::State_Selected) ? QIcon::Selected : QIcon::Normal); QFontMetrics fm = painter->fontMetrics(); int wp = pixmap.width() / pixmap.devicePixelRatio(); int hp = pixmap.height() / pixmap.devicePixelRatio(); QTextLayout iconTextLayout(text, option.font); QTextOption textOption(Qt::AlignHCenter); iconTextLayout.setTextOption(textOption); int maxWidth = qMax(3 * wp, 8 * fm.height()); layoutText(&iconTextLayout, maxWidth); QPen pen = painter->pen(); QPalette::ColorGroup cg = option.state & QStyle::State_Enabled ? QPalette::Normal : QPalette::Disabled; if (cg == QPalette::Normal && !(option.state & QStyle::State_Active)) { cg = QPalette::Inactive; } style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, opt.widget); if (option.state & QStyle::State_Selected) { painter->setPen(option.palette.color(cg, QPalette::HighlightedText)); } else { painter->setPen(option.palette.color(cg, QPalette::Text)); } painter->drawPixmap(option.rect.x() + (option.rect.width() / 2) - (wp / 2), option.rect.y() + KEXILISTVIEW_VERTICAL_MARGIN, pixmap); if (!text.isEmpty()) { iconTextLayout.draw(painter, QPoint(option.rect.x() + (option.rect.width() / 2) - (maxWidth / 2), option.rect.y() + hp + KEXILISTVIEW_VERTICAL_MARGIN + 2)); } painter->setPen(pen); drawFocus(painter, option, option.rect); } QSize KexiListViewDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { if (!index.isValid()) { return QSize(0, 0); } - QStyleOptionViewItemV4 opt(option); + QStyleOptionViewItem opt(option); opt.showDecorationSelected = true; QStyle *style = opt.widget ? opt.widget->style() : QApplication::style(); int iconSize = style->pixelMetric(QStyle::PM_IconViewIconSize); const QString text = index.model()->data(index, Qt::DisplayRole).toString(); const QIcon icon = index.model()->data(index, Qt::DecorationRole).value(); const QPixmap pixmap = icon.pixmap(iconSize, iconSize); QFontMetrics fm = option.fontMetrics; int gap = 0; //fm.height(); int wp = pixmap.width() / pixmap.devicePixelRatio(); int hp = pixmap.height() / pixmap.devicePixelRatio(); if (hp == 0) { /** * No pixmap loaded yet, we'll use the default icon size in this case. */ hp = iconSize; wp = iconSize; } QTextLayout iconTextLayout(text, option.font); int wt = layoutText(&iconTextLayout, qMax(3 * wp, 8 * fm.height())); int ht = iconTextLayout.boundingRect().height(); int width, height; if (text.isEmpty()) { height = hp; } else { height = hp + ht + 2 * KEXILISTVIEW_VERTICAL_MARGIN; } width = qMax(wt, wp) + gap; return QSize(width, height); } void KexiListViewDelegate::drawFocus(QPainter *painter, const QStyleOptionViewItem &option, const QRect &rect) const { if (option.state & QStyle::State_HasFocus) { QStyleOptionFocusRect o; o.QStyleOption::operator=(option); o.rect = rect; o.state |= QStyle::State_KeyboardFocusChange; QPalette::ColorGroup cg = (option.state & QStyle::State_Enabled) ? QPalette::Normal : QPalette::Disabled; o.backgroundColor = option.palette.color(cg, (option.state & QStyle::State_Selected) ? QPalette::Highlight : QPalette::Background); QApplication::style()->drawPrimitive(QStyle::PE_FrameFocusRect, &o, painter); } } // ---- KexiListViewSelectionModel::KexiListViewSelectionModel(QAbstractItemModel *model, QObject *parent) : QItemSelectionModel(model, parent) { } void KexiListViewSelectionModel::clear() { // Don't allow the current selection to be cleared } void KexiListViewSelectionModel::select(const QModelIndex &index, QItemSelectionModel::SelectionFlags command) { // Don't allow the current selection to be cleared if (!index.isValid() && (command & QItemSelectionModel::Clear)) { return; } QItemSelectionModel::select(index, command); } void KexiListViewSelectionModel::select(const QItemSelection &selection, QItemSelectionModel::SelectionFlags command) { // Don't allow the current selection to be cleared if (!selection.count() && (command & QItemSelectionModel::Clear)) { return; } QItemSelectionModel::select(selection, command); } diff --git a/src/widget/tableview/KexiTableScrollArea.cpp b/src/widget/tableview/KexiTableScrollArea.cpp index eb1a7a02f..3405b6528 100644 --- a/src/widget/tableview/KexiTableScrollArea.cpp +++ b/src/widget/tableview/KexiTableScrollArea.cpp @@ -1,2496 +1,2496 @@ /* This file is part of the KDE project Copyright (C) 2002 Till Busch Copyright (C) 2003 Lucijan Busch Copyright (C) 2003 Daniel Molkentin Copyright (C) 2003 Joseph Wenninger Copyright (C) 2003-2016 JarosÅ‚aw Staniek 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 Original Project: buX (www.bux.at) */ #include "KexiTableScrollArea.h" #include "KexiTableScrollArea_p.h" #include "KexiTableScrollAreaWidget.h" #include "KexiTableScrollAreaHeader.h" #include "KexiTableScrollAreaHeaderModel.h" #include "kexitableedit.h" #include #include #include "kexicelleditorfactory.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef KEXI_TABLE_PRINT_SUPPORT #include #endif //#define KEXITABLEVIEW_DEBUG //#define KEXITABLEVIEW_DEBUG_PAINT const int MINIMUM_ROW_HEIGHT = 17; KexiTableScrollArea::Appearance::Appearance(QWidget *widget) { //set defaults if (qApp) { baseColor = KColorScheme(QPalette::Active, KColorScheme::View).background().color()/*QPalette::Base*/; textColor = KColorScheme(QPalette::Active, KColorScheme::View).foreground().color()/*QPalette::Base*/; - QStyleOptionViewItemV4 option; + QStyleOptionViewItem option; option.initFrom(widget); const int gridHint = widget->style()->styleHint(QStyle::SH_Table_GridLineColor, &option, widget); gridColor = static_cast(gridHint); emptyAreaColor = KColorScheme(QPalette::Active, KColorScheme::View).background().color()/*QPalette::Base*/; alternateBaseColor = widget->palette().color(QPalette::AlternateBase); recordHighlightingColor = KexiUtils::blendedColors(QPalette::Highlight, baseColor, 34, 66); recordMouseOverHighlightingColor = KexiUtils::blendedColors(QPalette::Highlight, baseColor, 10, 90); recordMouseOverAlternateHighlightingColor = KexiUtils::blendedColors(QPalette::Highlight, alternateBaseColor, 10, 90); recordHighlightingTextColor = textColor; recordMouseOverHighlightingTextColor = textColor; } backgroundAltering = true; recordMouseOverHighlightingEnabled = true; recordHighlightingEnabled = true; persistentSelections = true; navigatorEnabled = true; fullRecordSelection = false; verticalGridEnabled = true; horizontalGridEnabled = !backgroundAltering || baseColor == alternateBaseColor; } //----------------------------------------- //! @todo KEXI3 KexiTableViewCellToolTip /* TODO KexiTableViewCellToolTip::KexiTableViewCellToolTip( KexiTableView * tableView ) : QToolTip() , m_tableView(tableView) { } KexiTableViewCellToolTip::~KexiTableViewCellToolTip() { } void KexiTableViewCellToolTip::maybeTip( const QPoint & p ) { const QPoint cp( m_tableView->viewportToContents( p ) ); const int row = m_tableView->recordAt( cp.y(), true ); 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->rowCount(); KDbRecordData *data = insertRowSelected ? m_tableView->m_insertItem : m_tableView->itemAt( row ); if (editor && record && (col < (int)record->count())) { int w = m_tableView->columnWidth( col ); int h = m_tableView->rowHeight(); int x = 0; int y_offset = 0; int align = SingleLine | AlignVCenter; QString txtValue; QVariant cellValue; KDbTableViewColumn *tvcol = m_tableView->column(col); if (!m_tableView->getVisibleLookupValue(cellValue, editor, record, tvcol)) cellValue = insertRowSelected ? editor->displayedField()->defaultValue() : record->at(col); //display default value if available const bool focused = m_tableView->selectedRecord() == record && col == m_tableView->currentColumn(); editor->setupContents( 0, focused, cellValue, txtValue, align, x, y_offset, w, h ); QRect realRect(m_tableView->columnPos(col)-m_tableView->horizontalScrollBar()->value(), m_tableView->recordPos(row)-m_tableView->verticalScrollBar()->value(), w, h); if (editor->showToolTipIfNeeded( txtValue.isEmpty() ? record->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 ); } } } } */ //TODO //----------------------------------------- KexiTableScrollArea::KexiTableScrollArea(KDbTableViewData* data, QWidget* parent) : QScrollArea(parent) , KexiRecordNavigatorHandler() , KexiSharedActionClient() , KexiDataAwareObjectInterface() , d(new Private(this)) { setAttribute(Qt::WA_StaticContents, true); setAttribute(Qt::WA_CustomWhatsThis, true); d->scrollAreaWidget = new KexiTableScrollAreaWidget(this); setWidget(d->scrollAreaWidget); m_data = new KDbTableViewData(); //to prevent crash because m_data==0 m_owner = true; //-this will be deleted if needed viewport()->setFocusPolicy(Qt::WheelFocus); setFocusPolicy(Qt::WheelFocus); //<--- !!!!! important (was NoFocus), // otherwise QApplication::setActiveWindow() won't activate // this widget when needed! viewport()->installEventFilter(this); d->scrollAreaWidget->installEventFilter(this); d->diagonalGrayPattern = QBrush(d->appearance.gridColor, Qt::BDiagPattern); setLineWidth(1); horizontalScrollBar()->installEventFilter(this); //context menu m_contextMenu = new QMenu(this); m_contextMenu->setObjectName("m_contextMenu"); //! \todo replace lineedit with table_field icon //setContextMenuTitle(KexiIcon("lineedit"), xi18n("Record")); // the default // cannot display anything here - most actions in the context menu // are related to a single cell (Cut, Copy..) and others to entire row (Delete Row): setContextMenuEnabled(false); d->pUpdateTimer = new QTimer(this); d->pUpdateTimer->setSingleShot(true); // Create headers d->headerModel = new KexiTableScrollAreaHeaderModel(this); d->horizontalHeader = new KexiTableScrollAreaHeader(Qt::Horizontal, this); d->horizontalHeader->setObjectName("horizontalHeader"); d->horizontalHeader->setSelectionBackgroundColor(palette().color(QPalette::Highlight)); d->verticalHeader = new KexiTableScrollAreaHeader(Qt::Vertical, this); d->verticalHeader->setObjectName("verticalHeader"); d->verticalHeader->setSelectionBackgroundColor(palette().color(QPalette::Highlight)); setupNavigator(); if (data) { setData(data); } setAcceptDrops(true); viewport()->setAcceptDrops(true); // Connect header, table and scrollbars connect(horizontalScrollBar(), SIGNAL(valueChanged(int)), d->horizontalHeader, SLOT(setOffset(int))); connect(verticalScrollBar(), SIGNAL(valueChanged(int)), d->verticalHeader, SLOT(setOffset(int))); connect(d->horizontalHeader, SIGNAL(sectionResized(int,int,int)), this, SLOT(slotColumnWidthChanged(int,int,int))); connect(d->horizontalHeader, SIGNAL(sectionHandleDoubleClicked(int)), this, SLOT(slotSectionHandleDoubleClicked(int))); connect(d->horizontalHeader, SIGNAL(sectionClicked(int)), this, SLOT(sortColumnInternal(int))); connect(d->pUpdateTimer, SIGNAL(timeout()), this, SLOT(slotUpdate())); setAppearance(d->appearance); //refresh d->setSpreadSheetMode(false); //! @todo KEXI3 d->cellToolTip = new KexiTableViewCellToolTip(this); } KexiTableScrollArea::~KexiTableScrollArea() { cancelRecordEditing(); KDbTableViewData *data = m_data; m_data = 0; if (m_owner) { if (data) data->deleteLater(); } delete d; } void KexiTableScrollArea::clearVariables() { KexiDataAwareObjectInterface::clearVariables(); d->clearVariables(); } void KexiTableScrollArea::setupNavigator() { m_navPanel = new KexiRecordNavigator(*this, this); navPanelWidget()->setObjectName("navPanel"); m_navPanel->setRecordHandler(this); } void KexiTableScrollArea::initDataContents() { updateWidgetContentsSize(); KexiDataAwareObjectInterface::initDataContents(); m_navPanel->showEditingIndicator(false); } void KexiTableScrollArea::updateWidgetContentsSize() { updateScrollAreaWidgetSize(); d->horizontalHeader->setFixedSize(d->horizontalHeader->sizeHint()); d->verticalHeader->setFixedSize(d->verticalHeader->sizeHint()); //qDebug() << d->horizontalHeader->sizeHint() << d->verticalHeader->sizeHint(); //qDebug() << d->horizontalHeader->geometry() << d->verticalHeader->geometry(); } void KexiTableScrollArea::updateScrollAreaWidgetSize() { QSize s(tableSize()); const int colOffset = d->columnOffset(); s.setWidth(qMax(s.width() + colOffset, viewport()->width())); s.setHeight(qMax(s.height() + colOffset, viewport()->height())); d->scrollAreaWidget->resize(s); } void KexiTableScrollArea::updateVerticalHeaderSection(int row) { d->verticalHeader->updateSection(row); } void KexiTableScrollArea::slotRecordsDeleted(const QList &records) { viewport()->repaint(); updateWidgetContentsSize(); setCursorPosition(qMax(0, (int)m_curRecord - (int)records.count()), -1, ForceSetCursorPosition); } void KexiTableScrollArea::setFont(const QFont &font) { QScrollArea::setFont(font); #ifdef Q_OS_WIN //! @todo KEXI3 WIN32 test this d->recordHeight = fontMetrics().lineSpacing() + 4; #else d->recordHeight = fontMetrics().lineSpacing() + 1; #endif if (d->appearance.fullRecordSelection) { d->recordHeight -= 1; } if (d->recordHeight < MINIMUM_ROW_HEIGHT) { d->recordHeight = MINIMUM_ROW_HEIGHT; } KexiDisplayUtils::initDisplayForAutonumberSign(&d->autonumberSignDisplayParameters, this); KexiDisplayUtils::initDisplayForDefaultValue(&d->defaultValueDisplayParameters, this); update(); } void KexiTableScrollArea::updateAllVisibleRecordsBelow(int record) { //get last visible row // int r = recordAt(viewport()->height() + verticalScrollBar()->value()); // if (r == -1) { // r = rowCount() + 1 + (isInsertingEnabled() ? 1 : 0); // } //update all visible rows below int leftcol = d->horizontalHeader->visualIndexAt(d->horizontalHeader->offset()); d->scrollAreaWidget->update(columnPos(leftcol), recordPos(record), viewport()->width(), viewport()->height() - (recordPos(record) - verticalScrollBar()->value())); } void KexiTableScrollArea::clearColumnsInternal(bool /*repaint*/) { } void KexiTableScrollArea::slotUpdate() { // qDebug() << m_navPanel; updateScrollAreaWidgetSize(); d->scrollAreaWidget->update(); updateWidgetContentsSize(); } KDbOrderByColumn::SortOrder KexiTableScrollArea::currentLocalSortOrder() const { return KDbOrderByColumn::fromQt(d->horizontalHeader->sortIndicatorOrder()); } void KexiTableScrollArea::setLocalSortOrder(int column, KDbOrderByColumn::SortOrder order) { d->horizontalHeader->setSortIndicator(column, KDbOrderByColumn::toQt(order)); } int KexiTableScrollArea::currentLocalSortColumn() const { return d->horizontalHeader->sortIndicatorSection(); } void KexiTableScrollArea::updateGUIAfterSorting(int previousRow) { int prevRowVisibleOffset = recordPos(previousRow) - verticalScrollBar()->value(); verticalScrollBar()->setValue(recordPos(m_curRecord) - prevRowVisibleOffset); d->scrollAreaWidget->update(); selectCellInternal(m_curRecord, m_curColumn); } QSizePolicy KexiTableScrollArea::sizePolicy() const { // this widget is expandable return QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); } QSize KexiTableScrollArea::sizeHint() const { QSize ts = tableSize(); int w = qMax(ts.width() + leftMargin() + verticalScrollBar()->sizeHint().width() + 2 * 2, (navPanelWidgetVisible() ? navPanelWidget()->width() : 0)); int h = qMax(ts.height() + topMargin() + horizontalScrollBar()->sizeHint().height(), minimumSizeHint().height()); w = qMin(w, qApp->desktop()->availableGeometry(this).width() * 3 / 4); //stretch h = qMin(h, qApp->desktop()->availableGeometry(this).height() * 3 / 4); //stretch #ifdef KEXITABLEVIEW_DEBUG qDebug() << w << h; #endif return QSize(w, h); } QSize KexiTableScrollArea::minimumSizeHint() const { return QSize( leftMargin() + ((columnCount() > 0) ? columnWidth(0) : KEXI_DEFAULT_DATA_COLUMN_WIDTH) + 2*2, d->recordHeight*5 / 2 + topMargin() + (navPanelWidgetVisible() ? navPanelWidget()->height() : 0) ); } QRect KexiTableScrollArea::viewportGeometry() const { return viewport()->geometry(); } //internal inline void KexiTableScrollArea::paintRow(KDbRecordData *data, QPainter *pb, int r, int rowp, int cx, int cy, int colfirst, int collast, int maxwc) { Q_UNUSED(cx); Q_UNUSED(cy); if (!data) return; //qDebug() << "r" << r << "rowp" << rowp << "cx" << cx << "cy" << cy // << "colfirst" << colfirst << "collast" << collast << "maxwc" << maxwc; // 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 = columnCount() - 1; int transly = rowp; if (d->appearance.recordHighlightingEnabled && r == m_curRecord && !d->appearance.fullRecordSelection) { pb->fillRect(0, transly, maxwc, d->recordHeight, d->appearance.recordHighlightingColor); } else if (d->appearance.recordMouseOverHighlightingEnabled && r == d->highlightedRecord) { if (d->appearance.backgroundAltering && (r % 2 != 0)) pb->fillRect(0, transly, maxwc, d->recordHeight, d->appearance.recordMouseOverAlternateHighlightingColor); else pb->fillRect(0, transly, maxwc, d->recordHeight, d->appearance.recordMouseOverHighlightingColor); } else { if (d->appearance.backgroundAltering && (r % 2 != 0)) pb->fillRect(0, transly, maxwc, d->recordHeight, d->appearance.alternateBaseColor); else pb->fillRect(0, transly, maxwc, d->recordHeight, 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); // qDebug() << "c:" << c << "colp:" << colp << "cx:" << cx << "contentsX():" << horizontalScrollBar()->value() << "colw:" << colw; //(js #2010-01-05) breaks rendering: int translx = colp - cx + horizontalScrollBar()->value(); int translx = colp; // Translate painter and draw the cell const QTransform oldTr( pb->worldTransform() ); pb->translate(translx, transly); paintCell(pb, data, r, c, QRect(colp, rowp, colw, d->recordHeight)); pb->setWorldTransform(oldTr); } if (m_dragIndicatorLine >= 0) { int y_line = -1; if (r == (recordCount() - 1) && m_dragIndicatorLine == recordCount()) { y_line = transly + d->recordHeight - 3; //draw at last line } if (m_dragIndicatorLine == r) { y_line = transly; } if (y_line >= 0) { if (!d->dragIndicatorRubberBand) { d->dragIndicatorRubberBand = new QRubberBand(QRubberBand::Line, viewport()); } d->dragIndicatorRubberBand->setGeometry(0, y_line, maxwc, 3); d->dragIndicatorRubberBand->show(); } else { if (d->dragIndicatorRubberBand) { d->dragIndicatorRubberBand->hide(); } } } else { if (d->dragIndicatorRubberBand) { d->dragIndicatorRubberBand->hide(); } } } void KexiTableScrollArea::drawContents(QPainter *p) { int cx = p->clipBoundingRect().x(); int cy = p->clipBoundingRect().y(); int cw = p->clipBoundingRect().width(); int ch = p->clipBoundingRect().height(); #ifdef KEXITABLEVIEW_DEBUG qDebug() << "disable" << d->disableDrawContents << "cx" << cx << "cy" << cy << "cw" << cw << "ch" << ch << "contentsRect" << contentsRect() << "geo" << geometry(); #endif if (d->disableDrawContents) return; bool paintOnlyInsertRow = false; bool inserting = isInsertingEnabled(); bool plus1row = false; //true if we should show 'inserting' row at the end int colfirst = columnNumberAt(cx); int rowfirst = recordNumberAt(cy); int collast = columnNumberAt(cx + cw - 1); int rowlast = recordNumberAt(cy + ch - 1); if (rowfirst == -1 && (cy / d->recordHeight) == recordCount()) { // make the insert row paint too when requested #ifdef KEXITABLEVIEW_DEBUG qDebug() << "rowfirst == -1 && (cy / d->rowHeight) == rowCount()"; #endif rowfirst = m_data->count(); rowlast = rowfirst; paintOnlyInsertRow = true; plus1row = inserting; } /* qDebug() << "cx" << cx << "cy" << cy << "cw" << cw << "ch" << ch << "colfirst" << colfirst << "rowfirst" << rowfirst << "collast" << collast << "rowlast" << rowlast;*/ if (rowlast == -1) { rowlast = recordCount() - 1; plus1row = inserting; if (rowfirst == -1) { if (recordNumberAt(cy - d->recordHeight) != -1) { //paintOnlyInsertRow = true; // qDebug() << "-- paintOnlyInsertRow --"; } } } // qDebug() << "rowfirst="<fillRect(cx, cy, cw, ch, d->appearance.baseColor); int rowp = 0; int r = 0; if (paintOnlyInsertRow) { r = recordCount(); rowp = recordPos(r); // 'insert' row's position } else { if (rowfirst >= 0) { QList::ConstIterator it(m_data->constBegin()); it += rowfirst;//move to 1st row rowp = recordPos(rowfirst); // row position for (r = rowfirst;r <= rowlast; r++, ++it, rowp += d->recordHeight) { // qDebug() << *it; paintRow(*it, p, r, rowp, cx, cy, colfirst, collast, maxwc); } } } if (plus1row && rowfirst >= 0) { //additional - 'insert' row paintRow(m_insertRecord, p, r, rowp, cx, cy, colfirst, collast, maxwc); } paintEmptyArea(p, cx, cy, cw, ch); } bool KexiTableScrollArea::isDefaultValueDisplayed(KDbRecordData *data, int col, QVariant* value) { const bool cursorAtInsertRowOrEditingNewRow = (data == m_insertRecord || (m_newRecordEditing && m_currentRecord == data)); KDbTableViewColumn *tvcol; if (cursorAtInsertRowOrEditingNewRow && (tvcol = m_data->column(col)) && hasDefaultValueAt(*tvcol) && !tvcol->field()->isAutoIncrement()) { if (value) *value = tvcol->field()->defaultValue(); return true; } return false; } void KexiTableScrollArea::paintCell(QPainter* p, KDbRecordData *data, int record, int column, const QRect &cr, bool print) { Q_UNUSED(print); //qDebug() << "col/row:" << col << row << "rect:" << cr; p->save(); int w = cr.width(); int h = cr.height(); int x2 = w - 1; int y2 = h - 1; // Draw our lines QPen pen(p->pen()); if (d->appearance.horizontalGridEnabled) { p->setPen(d->appearance.gridColor); p->drawLine(0, y2, x2, y2); // bottom } if (d->appearance.verticalGridEnabled) { p->setPen(d->appearance.gridColor); p->drawLine(x2, 0, x2, y2); // right } p->setPen(pen); if (m_editor && record == m_curRecord && column == m_curColumn //don't paint contents of edited cell && m_editor->hasFocusableWidget() //..if it's visible ) { p->restore(); return; } KexiTableEdit *edit = tableEditorWidget(column, /*ignoreMissingEditor=*/true); int x = edit ? edit->leftMargin() : 0; int y_offset = 0; int align = Qt::TextSingleLine | Qt::AlignVCenter; QString txt; //text to draw if (data == m_insertRecord) { //qDebug() << "we're at INSERT row..."; } KDbTableViewColumn *tvcol = m_data->column(column); QVariant cellValue; if (column < (int)data->count()) { if (m_currentRecord == data) { if (m_editor && record == m_curRecord && column == m_curColumn && !m_editor->hasFocusableWidget()) { //we're over editing cell and the editor has no widget // - we're displaying internal values, not buffered 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(record, column); } } else { cellValue = data->at(column); } } bool defaultValueDisplayed = isDefaultValueDisplayed(data, column); if (data == m_insertRecord && cellValue.isNull()) { if (!tvcol->field()->isAutoIncrement() && !tvcol->field()->defaultValue().isNull()) { //display default value in the "insert record", 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.recordHighlightingEnabled && !d->appearance.persistentSelections && m_curRecord >= 0 && record != m_curRecord; // setup default pen QPen defaultPen; const bool usesSelectedTextColor = edit && edit->usesSelectedTextColor(); if (defaultValueDisplayed){ if (column == m_curColumn && record == m_curRecord && usesSelectedTextColor) defaultPen = d->defaultValueDisplayParameters.selectedTextColor; else defaultPen = d->defaultValueDisplayParameters.textColor; } else if (d->appearance.fullRecordSelection && (record == d->highlightedRecord || (record == m_curRecord && d->highlightedRecord == -1)) && usesSelectedTextColor) { defaultPen = d->appearance.recordHighlightingTextColor; //special case: highlighted record } else if (d->appearance.fullRecordSelection && record == m_curRecord && usesSelectedTextColor) { defaultPen = d->appearance.textColor; //special case for full record selection } else if ( m_currentRecord == data && column == m_curColumn && !columnReadOnly && !dontPaintNonpersistentSelectionBecauseDifferentRowHasBeenHighlighted && usesSelectedTextColor) { defaultPen = palette().color(QPalette::HighlightedText); //selected text } else if ( d->appearance.recordHighlightingEnabled && record == m_curRecord && !dontPaintNonpersistentSelectionBecauseDifferentRowHasBeenHighlighted && usesSelectedTextColor) { defaultPen = d->appearance.recordHighlightingTextColor; } else if ( d->appearance.recordMouseOverHighlightingEnabled && record == d->highlightedRecord && !dontPaintNonpersistentSelectionBecauseDifferentRowHasBeenHighlighted && usesSelectedTextColor) { defaultPen = d->appearance.recordMouseOverHighlightingTextColor; } 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, data, tvcol); /*qDebug() << "edit->setupContents()" << (m_currentRecord == record && col == m_curColumn) << cellValue << txt << align << x << y_offset << w << h;*/ edit->setupContents(p, m_currentRecord == data && column == m_curColumn, cellValue, txt, align, x, y_offset, w, h); } if (!d->appearance.horizontalGridEnabled) y_offset++; //correction because we're not drawing cell borders if (d->appearance.fullRecordSelection) { } if (m_currentRecord == data && (column == m_curColumn || d->appearance.fullRecordSelection)) { if (edit && ( (d->appearance.recordHighlightingEnabled && !d->appearance.fullRecordSelection) || (record == m_curRecord && d->highlightedRecord == -1 && d->appearance.fullRecordSelection)) ) { edit->paintSelectionBackground(p, isEnabled(), txt, align, x, y_offset, w, h, isEnabled() ? palette().color(QPalette::Highlight) : QColor(200, 200, 200),//d->grayColor, p->fontMetrics(), columnReadOnly, d->appearance.fullRecordSelection); } } if (!edit) { p->fillRect(0, 0, x2, y2, d->diagonalGrayPattern); } // If we are in the focus cell, draw indication if ( m_currentRecord == data && column == m_curColumn //js: && !d->recordIndicator) && !d->appearance.fullRecordSelection) { // qDebug() << ">>> CURRENT CELL ("<field()->isAutoIncrement()) { // "autonumber" column KexiDisplayUtils::paintAutonumberSign(d->autonumberSignDisplayParameters, p, x, y_offset, w - x - x - ((align & Qt::AlignLeft) ? 2 : 0), h, (Qt::Alignment)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); } #ifdef KEXITABLEVIEW_DEBUG_PAINT p->setPen(QPen(QColor(255, 0, 0, 150), 1, Qt::DashLine)); p->drawRect(x, y_offset, w - 1, h - 1); qDebug() << cellValue << "x:" << x << "y:" << y_offset << "w:" << w << "h:" << h; #endif p->restore(); } QPoint KexiTableScrollArea::contentsToViewport2(const QPoint &p) { return QPoint(p.x() - horizontalScrollBar()->value(), p.y() - verticalScrollBar()->value()); } void KexiTableScrollArea::contentsToViewport2(int x, int y, int& vx, int& vy) { const QPoint v = contentsToViewport2(QPoint(x, y)); vx = v.x(); vy = v.y(); } QPoint KexiTableScrollArea::viewportToContents2(const QPoint& vp) { return QPoint(vp.x() + horizontalScrollBar()->value(), vp.y() + verticalScrollBar()->value()); } void KexiTableScrollArea::paintEmptyArea(QPainter *p, int cx, int cy, int cw, int ch) { //qDebug() << cx << cy << cw << ch; // Regions work with shorts, so avoid an overflow and adjust the // table size to the visible size QSize ts(tableSize()); //qDebug() << ts; /* qDebug() << QString(" (cx:%1 cy:%2 cw:%3 ch:%4)") .arg(cx).arg(cy).arg(cw).arg(ch); qDebug() << QString(" (w:%3 h:%4)") .arg(ts.width()).arg(ts.height());*/ // 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)); //qDebug() << "---cy-- " << verticalScrollBar()->value(); // Subtract the table from it reg = reg.subtracted(QRect(QPoint(0, 0), ts - QSize(0, + verticalScrollBar()->value()))); // And draw the rectangles (transformed inc contents coordinates as needed) const QVector rects(reg.rects()); foreach(const QRect& rect, rects) { QRect realRect(viewportToContents2(rect.topLeft()), rect.size()); // qDebug() << QString("- pEA: p->fillRect(x:%1 y:%2 w:%3 h:%4)") // .arg(rect.x()).arg(rect.y()) // .arg(rect.width()).arg(rect.height()) // << viewportGeometry(); p->fillRect(realRect, d->appearance.emptyAreaColor); } } void KexiTableScrollArea::contentsMouseDoubleClickEvent(QMouseEvent *e) { // qDebug(); m_contentsMousePressEvent_dblClick = true; contentsMousePressEvent(e); m_contentsMousePressEvent_dblClick = false; if (m_currentRecord) { if (d->editOnDoubleClick && columnEditable(m_curColumn) && columnType(m_curColumn) != KDbField::Boolean) { KexiTableEdit *edit = tableEditorWidget(m_curColumn, /*ignoreMissingEditor=*/true); if (edit && edit->handleDoubleClick()) { //nothing to do: editors like BLOB editor has custom handling of double clicking } else { startEditCurrentCell(); // createEditor(m_curRecord, m_curColumn, QString()); } } emit itemDblClicked(m_currentRecord, m_curRecord, m_curColumn); } } void KexiTableScrollArea::contentsMousePressEvent(QMouseEvent* e) { setFocus(); if (m_data->isEmpty() && !isInsertingEnabled()) { return; } //qDebug() << e->pos(); const int x = e->pos().x(); if (columnNumberAt(x) == -1) { //outside a column return; } if (!d->moveCursorOnMouseRelease) { if (!handleContentsMousePressOrRelease(e, false)) return; } // qDebug()<< "by now the current items should be set, if not -> error + crash"; if (e->button() == Qt::RightButton) { showContextMenu(e->globalPos()); } else if (e->button() == Qt::LeftButton) { if (columnType(m_curColumn) == KDbField::Boolean && columnEditable(m_curColumn)) { //only accept clicking on the [x] rect (copied from KexiBoolTableEdit::setupContents()) int s = qMax(d->recordHeight - 5, 12); s = qMin(d->recordHeight - 3, s); s = qMin(columnWidth(m_curColumn) - 3, s); //avoid too large box const QRect r( columnPos(m_curColumn) + qMax(columnWidth(m_curColumn) / 2 - s / 2, 0), recordPos(m_curRecord) + d->recordHeight / 2 - s / 2 /*- 1*/, s, s); //qDebug() << r; if (r.contains(e->pos())) { // qDebug() << "e->x:" << e->x() << " e->y:" << e->y() << " " << recordPos(m_curRecord) << // " " << columnPos(m_curColumn); boolToggled(); } } //! @todo #if 0 else if (columnType(m_curColumn) == QVariant::StringList && columnEditable(m_curColumn)) { createEditor(m_curRecord, m_curColumn); } #endif } } void KexiTableScrollArea::contentsMouseReleaseEvent(QMouseEvent* e) { if (m_data->count() == 0 && !isInsertingEnabled()) return; if (d->moveCursorOnMouseRelease) handleContentsMousePressOrRelease(e, true); int col = columnNumberAt(e->pos().x()); int row = recordNumberAt(e->pos().y()); if (!m_currentRecord || col == -1 || row == -1 || col != m_curColumn || row != m_curRecord)//outside a current cell return; emit itemMouseReleased(m_currentRecord, m_curRecord, m_curColumn); } bool KexiTableScrollArea::handleContentsMousePressOrRelease(QMouseEvent* e, bool release) { Q_UNUSED(release); //qDebug() << "oldRow=" << m_curRecord << " oldCol=" << m_curColumn; int newrow, newcol; //compute clicked row nr const int x = e->pos().x(); if (isInsertingEnabled()) { if (recordNumberAt(e->pos().y()) == -1) { newrow = recordNumberAt(e->pos().y() - d->recordHeight); if (newrow == -1 && m_data->count() > 0) { return false; } newrow++; qDebug() << "Clicked just on 'insert' record."; } else { // get new focus cell newrow = recordNumberAt(e->pos().y()); } } else { if (recordNumberAt(e->pos().y()) == -1 || columnNumberAt(x) == -1) { return false; //clicked outside a grid } // get new focus cell newrow = recordNumberAt(e->pos().y()); } newcol = columnNumberAt(x); if (e->button() != Qt::NoButton) { setCursorPosition(newrow, newcol); } return true; } void KexiTableScrollArea::showContextMenu(const QPoint& _pos) { if (!d->contextMenuEnabled || m_contextMenu->isEmpty()) return; QPoint pos(_pos); if (pos == QPoint(-1, -1)) { pos = viewport()->mapToGlobal(QPoint(columnPos(m_curColumn), recordPos(m_curRecord) + d->recordHeight)); } //show own context menu if configured selectRecord(m_curRecord); m_contextMenu->exec(pos); } void KexiTableScrollArea::contentsMouseMoveEvent(QMouseEvent *e) { int row; const int col = columnNumberAt(e->x()); if (col < 0) { row = -1; } else { row = recordNumberAt(e->y(), true /*ignoreEnd*/); if (row > (recordCount() - 1 + (isInsertingEnabled() ? 1 : 0))) row = -1; //no row to paint } // qDebug() << " row="<modifiers() == Qt::ShiftModifier; if (action_name == "edit_delete_row") return k == Qt::Key_Delete && e->modifiers() == Qt::ControlModifier; if (action_name == "edit_delete") return k == Qt::Key_Delete && e->modifiers() == Qt::NoModifier; if (action_name == "edit_edititem") return k == Qt::Key_F2 && e->modifiers() == Qt::NoModifier; if (action_name == "edit_insert_empty_row") return k == Qt::Key_Insert && e->modifiers() == (Qt::ShiftModifier | Qt::ControlModifier); return false; } void KexiTableScrollArea::contentsContextMenuEvent(QContextMenuEvent* e) { const bool nobtn = e->modifiers() == Qt::NoModifier; if (nobtn && e->reason() == QContextMenuEvent::Keyboard) { showContextMenu(); } } void KexiTableScrollArea::keyPressEvent(QKeyEvent* e) { #ifdef KEXITABLEVIEW_DEBUG qDebug() << e; #endif if (!hasData()) return; // qDebug() << "key=" <key() << " txt=" <text(); const int k = e->key(); const bool ro = isReadOnly(); QWidget *w = focusWidget(); if (!w || (w != viewport() && w != this && (!m_editor || !KDbUtils::hasParent(dynamic_cast(m_editor), w)))) { //don't process stranger's events e->ignore(); return; } if (d->skipKeyPress) { d->skipKeyPress = false; e->ignore(); return; } if (m_currentRecord == 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(); emit updateSaveCancelActions(); e->accept(); return; } else if (k == Qt::Key_Return || k == Qt::Key_Enter) { if (columnType(m_curColumn) == KDbField::Boolean) { boolToggled(); } else { acceptEditor(); } e->accept(); return; } } else if (recordEditing() >= 0) {// if a row is in edit mode, do some special stuff if (shortCutPressed(e, "data_save_row")) { qDebug() << "shortCutPressed!!!"; acceptRecordEditing(); return; } } if (k == Qt::Key_Return || k == Qt::Key_Enter) { emit itemReturnPressed(m_currentRecord, m_curRecord, m_curColumn); } int curRow = m_curRecord; int curCol = m_curColumn; const bool nobtn = e->modifiers() == Qt::NoModifier; bool printable = false; //check shared shortcuts if (!ro) { if (shortCutPressed(e, "edit_delete_row")) { deleteCurrentRecord(); e->accept(); return; } else if (shortCutPressed(e, "edit_delete")) { deleteAndStartEditCurrentCell(); e->accept(); return; } else if (shortCutPressed(e, "edit_insert_empty_row")) { insertEmptyRecord(); e->accept(); 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.fullRecordSelection)) { if (e->isAccepted()) return; } else if (k == Qt::Key_Backspace && nobtn) { if (!ro && columnType(curCol) != KDbField::Boolean && columnEditable(curCol)) { const CreateEditorFlags flags = DefaultCreateEditorFlags | ReplaceOldValue; createEditor(curRow, curCol, QString(), flags); } } else if (k == Qt::Key_Space) { if (nobtn && !ro && columnEditable(curCol)) { if (columnType(curCol) == KDbField::Boolean) { boolToggled(); } else printable = true; //just space key } } else if (k == Qt::Key_Escape) { if (nobtn && recordEditing() >= 0) { cancelRecordEditing(); 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 == (columnCount() - 1)) { if (curRow < (recordCount() - 1 + (isInsertingEnabled() ? 1 : 0))) {//skip to next row curRow++; curCol = 0; } } else curCol++; } } else if ((e->modifiers() == Qt::ShiftModifier && k == Qt::Key_Tab) || (nobtn && k == Qt::Key_Backtab) || (e->modifiers() == Qt::ShiftModifier && k == Qt::Key_Backtab) || 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 = columnCount() - 1; } } else curCol--; } } else { KexiTableEdit *edit = tableEditorWidget(m_curColumn); 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 { qDebug() << "default"; if (e->text().isEmpty() || !e->text()[0].isPrint()) { qDebug() << "NOT PRINTABLE: 0x0" << QString("%1").arg(k, 0, 16); // e->ignore(); QScrollArea::keyPressEvent(e); return; } printable = true; } } } //finally: we've printable char: if (printable && !ro) { KDbTableViewColumn *tvcol = m_data->column(curCol); if (tvcol->acceptsFirstChar(e->text()[0])) { qDebug() << "ev pressed: acceptsFirstChar()==true"; const CreateEditorFlags flags = DefaultCreateEditorFlags | ReplaceOldValue; createEditor(curRow, curCol, e->text(), flags); } else { //! @todo show message "key not allowed eg. on a statusbar" qDebug() << "ev pressed: acceptsFirstChar()==false"; } } m_verticalScrollBarValueChanged_enabled = false; // if focus cell changes, repaint setCursorPosition(curRow, curCol, DontEnsureCursorVisibleIfPositionUnchanged); m_verticalScrollBarValueChanged_enabled = true; e->accept(); } void KexiTableScrollArea::emitSelected() { if (m_currentRecord) emit itemSelected(m_currentRecord); } int KexiTableScrollArea::recordsPerPage() const { return viewport()->height() / d->recordHeight; } KexiDataItemInterface *KexiTableScrollArea::editor(int col, bool ignoreMissingEditor) { if (!m_data || col < 0 || col >= columnCount()) return 0; KDbTableViewColumn *tvcol = m_data->column(col); //find the editor for this column KexiTableEdit *editor = d->editors.value(tvcol); if (editor) return editor; //not found: create editor = KexiCellEditorFactory::createEditor(*tvcol, d->scrollAreaWidget); if (!editor) {//create error! if (!ignoreMissingEditor) { //! @todo show error??? cancelRecordEditing(); } 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), recordHeight()); editor->installEventFilter(this); if (editor->widget()) editor->widget()->installEventFilter(this); //store d->editors.insert(tvcol, editor); return editor; } KexiTableEdit* KexiTableScrollArea::tableEditorWidget(int col, bool ignoreMissingEditor) { return dynamic_cast(editor(col, ignoreMissingEditor)); } void KexiTableScrollArea::editorShowFocus(int row, int col) { Q_UNUSED(row); KexiDataItemInterface *edit = editor(col); if (edit) { //qDebug() << "IN"; QRect rect = cellGeometry(m_curRecord, m_curColumn); edit->showFocus(rect, isReadOnly() || m_data->column(col)->isReadOnly()); } } void KexiTableScrollArea::slotEditRequested() { createEditor(m_curRecord, m_curColumn); } void KexiTableScrollArea::reloadData() { KexiDataAwareObjectInterface::reloadData(); d->scrollAreaWidget->update(); } void KexiTableScrollArea::createEditor(int row, int col, const QString& addText, CreateEditorFlags flags) { //qDebug() << "addText:" << addText << "removeOld:" << removeOld; if (row < 0) { qWarning() << "ROW NOT SPECIFIED!" << row; return; } if (isReadOnly()) { qDebug() << "DATA IS READ ONLY!"; return; } if (m_data->column(col)->isReadOnly()) {//d->pColumnModes.at(d->numCols-1) & ColumnReadOnly) qDebug() << "COL IS READ ONLY!"; return; } if (recordEditing() >= 0 && row != recordEditing()) { if (!acceptRecordEditing()) { return; } } const bool startRecordEditing = recordEditing() == -1; //remember if we're starting row edit if (startRecordEditing) { //we're starting row editing session m_data->clearRecordEditBuffer(); setRecordEditing(row); //indicate on the vheader that we are editing: if (isInsertingEnabled() && row == recordCount()) { //we should know that we are in state "new record editing" m_newRecordEditing = true; KDbRecordData *insertItem = m_insertRecord; beginInsertItem(insertItem, row); //'insert' row editing: show another row after that: m_data->append(insertItem); //new empty 'inserting' item m_insertRecord = m_data->createItem(); endInsertItem(insertItem, row); updateWidgetContentsSize(); //refr. current and next row d->scrollAreaWidget->update(columnPos(col), recordPos(row), viewport()->width(), d->recordHeight*2); if (flags & EnsureCellVisible) { ensureVisible(columnPos(col), recordPos(row + 1) + d->recordHeight - 1, columnWidth(col), d->recordHeight); } d->verticalHeader->setOffset(verticalScrollBar()->value()); } d->verticalHeader->updateSection(row); } KexiTableEdit *editorWidget = tableEditorWidget(col); m_editor = editorWidget; if (!editorWidget) return; m_editor->setValue(*bufferedValueAt(row, col, !(flags & ReplaceOldValue)/*useDefaultValueIfPossible*/), addText, flags & ReplaceOldValue); if (m_editor->hasFocusableWidget()) { editorWidget->move(columnPos(col), recordPos(row)); editorWidget->resize(columnWidth(col), recordHeight()); editorWidget->show(); m_editor->setFocus(); } if (startRecordEditing) { m_navPanel->showEditingIndicator(true); //this will allow to enable 'next' btn //emit recordEditingStarted(row); } m_editor->installListener(this); } void KexiTableScrollArea::focusOutEvent(QFocusEvent* e) { KexiDataAwareObjectInterface::focusOutEvent(e); } bool KexiTableScrollArea::focusNextPrevChild(bool /*next*/) { return false; //special Tab/BackTab meaning } void KexiTableScrollArea::resizeEvent(QResizeEvent *e) { if (d->insideResizeEvent) return; d->insideResizeEvent = true; QScrollArea::resizeEvent(e); if ((viewport()->height() - e->size().height()) <= d->recordHeight) { slotUpdate(); triggerUpdate(); } d->insideResizeEvent = false; } void KexiTableScrollArea::showEvent(QShowEvent *e) { QScrollArea::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 updateScrollAreaWidgetSize(); } updateGeometries(); //now we can ensure cell's visibility ( if there was such a call before show() ) if (d->ensureCellVisibleOnShow != QPoint(-17, -17)) { // because (-1, -1) means "current cell" ensureCellVisible(d->ensureCellVisibleOnShow.y(), d->ensureCellVisibleOnShow.x()); d->ensureCellVisibleOnShow = QPoint(-17, -17); //reset the flag } if (d->firstShowEvent) { ensureVisible(0, 0, 0, 0); // needed because for small geometries contents were moved 1/2 of row height up d->firstShowEvent = false; } updateViewportMargins(); } void KexiTableScrollArea::dragMoveEvent(QDragMoveEvent *e) { if (!hasData()) return; if (m_dropsAtRecordEnabled) { QPoint p = e->pos(); int row = recordNumberAt(p.y()); if ((p.y() % d->recordHeight) > (d->recordHeight*2 / 3)) { row++; } KDbRecordData *data = m_data->at(row); emit dragOverRecord(data, row, e); if (e->isAccepted()) { if (m_dragIndicatorLine >= 0 && m_dragIndicatorLine != row) { //erase old indicator updateRecord(m_dragIndicatorLine); } if (m_dragIndicatorLine != row) { m_dragIndicatorLine = row; updateRecord(m_dragIndicatorLine); } } else { if (m_dragIndicatorLine >= 0) { //erase old indicator updateRecord(m_dragIndicatorLine); } m_dragIndicatorLine = -1; } } else { e->accept(); } } void KexiTableScrollArea::dropEvent(QDropEvent *e) { if (!hasData()) return; if (m_dropsAtRecordEnabled) { //we're no longer dragging over the table if (m_dragIndicatorLine >= 0) { int row2update = m_dragIndicatorLine; m_dragIndicatorLine = -1; updateRecord(row2update); } QPoint p = e->pos(); int row = recordNumberAt(p.y()); if ((p.y() % d->recordHeight) > (d->recordHeight*2 / 3)) { row++; } KDbRecordData *data = m_data->at(row); KDbRecordData *newData = 0; emit droppedAtRecord(data, row, e, newData ); if (newData ) { const int realRow = (row == m_curRecord ? -1 : row); insertItem(newData , realRow); setCursorPosition(row, 0); } } } void KexiTableScrollArea::dragLeaveEvent(QDragLeaveEvent *e) { Q_UNUSED(e); if (!hasData()) return; if (m_dropsAtRecordEnabled) { //we're no longer dragging over the table if (m_dragIndicatorLine >= 0) { int row2update = m_dragIndicatorLine; m_dragIndicatorLine = -1; updateRecord(row2update); } } } void KexiTableScrollArea::updateCell(int record, int column) { // qDebug() << record << column; d->scrollAreaWidget->update(cellGeometry(record, column)); } void KexiTableScrollArea::updateCurrentCell() { updateCell(m_curRecord, m_curColumn); } void KexiTableScrollArea::updateRecord(int record) { // qDebug()<value() << recordPos(row) << viewport()->width() << rowHeight(); if (record < 0 || record >= (recordCount() + 2/* sometimes we want to refresh the row after last*/)) return; //qDebug() << horizontalScrollBar()->value() << " " << verticalScrollBar()->value(); //qDebug() << QRect( columnPos( leftcol ), recordPos(row), viewport()->width(), rowHeight() ); d->scrollAreaWidget->update(horizontalScrollBar()->value(), recordPos(record), viewport()->width(), recordHeight()); } void KexiTableScrollArea::slotColumnWidthChanged(int column, int oldSize, int newSize) { Q_UNUSED(oldSize); Q_UNUSED(newSize); updateScrollAreaWidgetSize(); d->scrollAreaWidget->update(d->horizontalHeader->offset() + columnPos(column), d->verticalHeader->offset(), viewport()->width() - columnPos(column), viewport()->height()); //qDebug() << QRect(columnPos(column), 0, viewport()->width() - columnPos(column), viewport()->height()); QWidget *editorWidget = dynamic_cast(m_editor); if (editorWidget && editorWidget->isVisible()) { editorWidget->move(columnPos(m_curColumn), recordPos(m_curRecord)); editorWidget->resize(columnWidth(m_curColumn), recordHeight()); } updateGeometries(); editorShowFocus(m_curRecord, m_curColumn); if (editorWidget && editorWidget->isVisible()) { m_editor->setFocus(); } } void KexiTableScrollArea::slotSectionHandleDoubleClicked(int section) { adjustColumnWidthToContents(section); slotColumnWidthChanged(0, 0, 0); //to update contents and redraw ensureColumnVisible(section); } void KexiTableScrollArea::setSortingEnabled(bool set) { KexiDataAwareObjectInterface::setSortingEnabled(set); d->horizontalHeader->setSortingEnabled(set); } void KexiTableScrollArea::sortColumnInternal(int col, int order) { KexiDataAwareObjectInterface::sortColumnInternal(col, order); } int KexiTableScrollArea::leftMargin() const { return verticalHeaderVisible() ? d->verticalHeader->width() : 0; } int KexiTableScrollArea::topMargin() const { //qDebug() << d->horizontalHeader->height(); return horizontalHeaderVisible() ? d->horizontalHeader->height() : 0; } void KexiTableScrollArea::updateGeometries() { const QSize ts(tableSize()); if (d->horizontalHeader->offset() && ts.width() < (d->horizontalHeader->offset() + d->horizontalHeader->width())) { horizontalScrollBar()->setValue(ts.width() - d->horizontalHeader->width()); } const int colOffset = d->columnOffset(); const int frameLeftMargin = style()->pixelMetric(QStyle::PM_FocusFrameVMargin, 0, this) + colOffset; const int frameTopMargin = style()->pixelMetric(QStyle::PM_FocusFrameHMargin, 0, this) + colOffset; d->horizontalHeader->move(leftMargin() + frameLeftMargin, frameTopMargin); d->verticalHeader->move(frameLeftMargin, d->horizontalHeader->geometry().bottom() + 1); } int KexiTableScrollArea::columnWidth(int col) const { if (!hasData()) return 0; int vcID = m_data->visibleColumnIndex(col); //qDebug() << vcID << d->horizontalHeader->sectionSize(vcID); return (vcID == -1) ? 0 : d->horizontalHeader->sectionSize(vcID); } int KexiTableScrollArea::recordHeight() const { return d->recordHeight; } int KexiTableScrollArea::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->columnCount() - 1), vcID = 0; while (c >= 0 && (vcID = m_data->visibleColumnIndex(c)) == -1) c--; if (c < 0) return 0; if (c == col) return d->horizontalHeader->sectionPosition(vcID); return d->horizontalHeader->sectionPosition(vcID) + d->horizontalHeader->sectionSize(vcID); } int KexiTableScrollArea::recordPos(int record) const { return d->recordHeight*record; } int KexiTableScrollArea::columnNumberAt(int pos) const { if (!hasData()) return -1; const int realPos = pos - d->horizontalHeader->offset(); const int c = d->horizontalHeader->logicalIndexAt(realPos); if (c < 0) return c; return m_data->globalIndexOfVisibleColumn(c); } int KexiTableScrollArea::recordNumberAt(int pos, bool ignoreEnd) const { if (!hasData()) return -1; pos /= d->recordHeight; if (pos < 0) return 0; if ((pos >= (int)m_data->count()) && !ignoreEnd) return -1; return pos; } QRect KexiTableScrollArea::cellGeometry(int record, int column) const { return QRect(columnPos(column), recordPos(record), columnWidth(column), recordHeight()); } //#define KEXITABLEVIEW_COMBO_DEBUG QSize KexiTableScrollArea::tableSize() const { #ifdef KEXITABLEVIEW_COMBO_DEBUG if (objectName() == "KexiComboBoxPopup_tv") { qDebug() << "rowCount" << rowCount() << "\nisInsertingEnabled" << isInsertingEnabled() << "columnCount" << columnCount(); } #endif if ((recordCount() + (isInsertingEnabled() ? 1 : 0)) > 0 && columnCount() > 0) { /* qDebug() << columnPos( columnCount() - 1 ) + columnWidth( columnCount() - 1 ) << ", " << recordPos( rowCount()-1+(isInsertingEnabled()?1:0)) + d->rowHeight */ // qDebug() << m_navPanel->isVisible() <<" "<height()<<" " // << horizontalScrollBar()->sizeHint().height()<<" "<recordHeight + d->internal_bottomMargin ); #ifdef KEXITABLEVIEW_COMBO_DEBUG if (objectName() == "KexiComboBoxPopup_tv") { qDebug() << "size" << s << "\ncolumnPos(columnCount()-1)" << columnPos(columnCount() - 1) << "\ncolumnWidth(columnCount()-1)" << columnWidth(columnCount() - 1) << "\nrecordPos(rowCount()-1+(isInsertingEnabled()?1:0))" << recordPos(rowCount()-1+(isInsertingEnabled()?1:0)) << "\nd->rowHeight" << d->rowHeight << "\nd->internal_bottomMargin" << d->internal_bottomMargin; } #endif // qDebug() << rowCount()-1 <<" "<< (isInsertingEnabled()?1:0) <<" "<< rowEditing() << " " << s; #ifdef KEXITABLEVIEW_DEBUG qDebug() << s << "cw(last):" << columnWidth(columnCount() - 1); #endif return s; } return QSize(0, 0); } void KexiTableScrollArea::ensureCellVisible(int record, int column) { if (!isVisible()) { //the table is invisible: we can't ensure visibility now d->ensureCellVisibleOnShow = QPoint(record, column); return; } if (column == -1) { column = m_curColumn; } if (record == -1) { record = m_curRecord; } if (column < 0 || record < 0) { return; } //quite clever: ensure the cell is visible: QRect r(columnPos(column) - 1, recordPos(record) + (d->appearance.fullRecordSelection ? 1 : 0) - 1, columnWidth(column) + 2, recordHeight() + 2); if (navPanelWidgetVisible() && horizontalScrollBar()->isHidden()) { //a hack: for visible navigator: increase height of the visible rect 'r' r.setBottom(r.bottom() + navPanelWidget()->height()); } QSize tableSize(this->tableSize()); const int bottomBorder = r.bottom() + (isInsertingEnabled() ? recordHeight() : 0); if (!spreadSheetMode() && (tableSize.height() - bottomBorder) < recordHeight()) { // ensure the very bottom of scroll area is displayed to help the user see what's there r.moveTop(tableSize.height() - r.height() + 1); } QPoint pcenter = r.center(); #ifdef KEXITABLEVIEW_DEBUG qDebug() << pcenter.x() << pcenter.y() << (r.width() / 2) << (r.height() / 2); #endif ensureVisible(pcenter.x(), pcenter.y(), r.width() / 2, r.height() / 2); } void KexiTableScrollArea::ensureColumnVisible(int col) { if (!isVisible()) { return; } //quite clever: ensure the cell is visible: QRect r(columnPos(col == -1 ? m_curColumn : col) - 1, d->verticalHeader->offset(), columnWidth(col == -1 ? m_curColumn : col) + 2, 0); QPoint pcenter = r.center(); #ifdef KEXITABLEVIEW_DEBUG qDebug() << pcenter.x() << pcenter.y() << (r.width() / 2) << (r.height() / 2); #endif ensureVisible(pcenter.x(), pcenter.y(), r.width() / 2, r.height() / 2); } void KexiTableScrollArea::deleteCurrentRecord() { KexiDataAwareObjectInterface::deleteCurrentRecord(); ensureCellVisible(m_curRecord, -1); } KDbRecordData* KexiTableScrollArea::insertEmptyRecord(int pos) { const int previousRow = m_curRecord; KDbRecordData* data = KexiDataAwareObjectInterface::insertEmptyRecord(pos); // update header selection d->verticalHeader->setCurrentIndex( d->verticalHeader->selectionModel()->model()->index(m_curRecord, m_curColumn)); d->verticalHeader->updateSection(previousRow); d->verticalHeader->updateSection(m_curRecord); return data; } void KexiTableScrollArea::updateAfterCancelRecordEditing() { KexiDataAwareObjectInterface::updateAfterCancelRecordEditing(); m_navPanel->showEditingIndicator(false); } void KexiTableScrollArea::updateAfterAcceptRecordEditing() { KexiDataAwareObjectInterface::updateAfterAcceptRecordEditing(); m_navPanel->showEditingIndicator(false); } bool KexiTableScrollArea::getVisibleLookupValue(QVariant& cellValue, KexiTableEdit *edit, KDbRecordData *data, KDbTableViewColumn *tvcol) const { if (edit->columnInfo() && edit->columnInfo()->indexForVisibleLookupValue() != -1 && edit->columnInfo()->indexForVisibleLookupValue() < (int)data->count()) { const QVariant *visibleFieldValue = 0; if (m_currentRecord == data && m_data->recordEditBuffer()) { visibleFieldValue = m_data->recordEditBuffer()->at( tvcol->visibleLookupColumnInfo(), false/*!useDefaultValueIfPossible*/); } if (visibleFieldValue) //(use bufferedValueAt() - try to get buffered visible value for lookup field) cellValue = *visibleFieldValue; else cellValue /*txt*/ = data->at(edit->columnInfo()->indexForVisibleLookupValue()); return true; } return false; } //reimpl. void KexiTableScrollArea::removeEditor() { if (!m_editor) return; KexiDataAwareObjectInterface::removeEditor(); viewport()->setFocus(); } void KexiTableScrollArea::slotRecordRepaintRequested(KDbRecordData* data) { updateRecord(m_data->indexOf(data)); } void KexiTableScrollArea::verticalScrollBarValueChanged(int v) { KexiDataAwareObjectInterface::verticalScrollBarValueChanged(v); const QPoint posInViewport = viewport()->mapFromGlobal(QCursor::pos()) - QPoint(contentsMargins().left(), contentsMargins().top()); //qDebug() << posInViewport << contentsRect().size() - QSize(leftMargin(), topMargin()) // << QRect(QPoint(0, 0), contentsRect().size() - QSize(leftMargin(), topMargin())); const int record = recordNumberAt(posInViewport.y() + verticalScrollBar()->value()); if (record >= 0) { setHighlightedRecordNumber(record); } } #ifdef KEXI_TABLE_PRINT_SUPPORT void KexiTableScrollArea::print(QPrinter & /*printer*/ , QPrintDialog & /*printDialog*/) { 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; qDebug() << "bottom:" << bottomMargin; QPainter p(&printer); KDbRecordData *i; int width = leftMargin; for (int col = 0; col < columnCount(); 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, d->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()) { qDebug() << "row=" << row << "y=" << yOffset; int xOffset = leftMargin; for (int col = 0; col < columnCount(); col++) { qDebug() << "col=" << col << "x=" << xOffset; p.saveWorldMatrix(); p.translate(xOffset, yOffset); paintCell(&p, i, row, col, QRect(0, 0, columnWidth(col) + 1, d->rowHeight), true); p.restoreWorldMatrix(); 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.end(); } #endif KDbField* KexiTableScrollArea::field(int column) const { if (!m_data || !m_data->column(column)) return 0; return m_data->column(column)->field(); } void KexiTableScrollArea::adjustColumnWidthToContents(int column) { if (!hasData()) return; if (column == -1) { const int cols = columnCount(); for (int i = 0; i < cols; i++) adjustColumnWidthToContents(i); return; } int indexOfVisibleColumn = (m_data->column(column) && m_data->column(column)->columnInfo()) ? m_data->column(column)->columnInfo()->indexForVisibleLookupValue() : -1; if (-1 == indexOfVisibleColumn) indexOfVisibleColumn = column; if (indexOfVisibleColumn < 0) return; QList::ConstIterator it(m_data->constBegin()); if (it != m_data->constEnd() && (*it)->count() <= indexOfVisibleColumn) return; KexiCellEditorFactoryItem *item = KexiCellEditorFactory::item(columnType(indexOfVisibleColumn)); if (!item) return; int maxw = horizontalHeaderVisible() ? d->horizontalHeader->preferredSectionSize(column) : 0; if (maxw == 0 && m_data->isEmpty()) return; //nothing to adjust //! \todo js: this is NOT EFFECTIVE for big data sets!!!! KexiTableEdit *ed = tableEditorWidget(column/* not indexOfVisibleColumn*/); const QFontMetrics fm(fontMetrics()); if (ed) { for (it = m_data->constBegin(); it != m_data->constEnd(); ++it) { const int wfw = ed->widthForValue((*it)->at(indexOfVisibleColumn), fm); maxw = qMax(maxw, wfw); } const bool focused = currentColumn() == column; maxw += (fm.width(" ") + ed->leftMargin() + ed->rightMargin(focused) + 2); } if (maxw < KEXITV_MINIMUM_COLUMN_WIDTH) maxw = KEXITV_MINIMUM_COLUMN_WIDTH; //not too small //qDebug() << "setColumnWidth(column=" << column // << ", indexOfVisibleColumn=" << indexOfVisibleColumn << ", width=" << maxw << " )"; setColumnWidth(column/* not indexOfVisibleColumn*/, maxw); } void KexiTableScrollArea::setColumnWidth(int column, int width) { if (columnCount() <= column || column < 0) return; d->horizontalHeader->resizeSection(column, width); editorShowFocus(m_curRecord, m_curColumn); } void KexiTableScrollArea::maximizeColumnsWidth(const QList &columnList) { if (!isVisible()) { d->maximizeColumnsWidthOnShow += columnList; return; } if (width() <= d->horizontalHeader->headerWidth()) return; //sort the list and make it unique QList cl, sortedList(columnList); qSort(sortedList); int i = -999; QList::ConstIterator it(sortedList.constBegin()), end(sortedList.constEnd()); for (; it != end; ++it) { if (i != (*it)) { cl += (*it); i = (*it); } } //resize int sizeToAdd = (width() - d->horizontalHeader->headerWidth()) / cl.count() - d->verticalHeader->width(); if (sizeToAdd <= 0) return; end = cl.constEnd(); for (it = cl.constBegin(); it != end; ++it) { int w = d->horizontalHeader->sectionSize(*it); if (w > 0) { d->horizontalHeader->resizeSection(*it, w + sizeToAdd); } } d->scrollAreaWidget->update(); editorShowFocus(m_curRecord, m_curColumn); } void KexiTableScrollArea::setColumnResizeEnabled(int column, bool set) { if (column < 0 || column >= columnCount()) { return; } d->horizontalHeader->setSectionResizeMode(column, set ? QHeaderView::Interactive : QHeaderView::Fixed); } void KexiTableScrollArea::setColumnsResizeEnabled(bool set) { d->horizontalHeader->setSectionResizeMode(set ? QHeaderView::Interactive : QHeaderView::Fixed); } bool KexiTableScrollArea::stretchLastColumn() const { return d->horizontalHeader->stretchLastSection(); } void KexiTableScrollArea::setStretchLastColumn(bool set) { if (columnCount() > 0) { setColumnResizeEnabled(columnCount() - 1, !set); } d->horizontalHeader->setStretchLastSection(set); } void KexiTableScrollArea::setEditableOnDoubleClick(bool set) { d->editOnDoubleClick = set; } bool KexiTableScrollArea::editableOnDoubleClick() const { return d->editOnDoubleClick; } bool KexiTableScrollArea::verticalHeaderVisible() const { return d->verticalHeader->isVisible(); } void KexiTableScrollArea::setVerticalHeaderVisible(bool set) { d->verticalHeader->setVisible(set); updateViewportMargins(); } void KexiTableScrollArea::updateViewportMargins() { d->viewportMargins = QMargins( leftMargin() + 1, topMargin() + 1, 0, // right 0 // bottom ); setViewportMargins(d->viewportMargins); //qDebug() << d->viewportMargins; } bool KexiTableScrollArea::horizontalHeaderVisible() const { return d->horizontalHeaderVisible; } void KexiTableScrollArea::setHorizontalHeaderVisible(bool set) { d->horizontalHeaderVisible = set; //needed because isVisible() is not always accurate d->horizontalHeader->setVisible(set); updateViewportMargins(); } void KexiTableScrollArea::triggerUpdate() { // qDebug(); d->pUpdateTimer->start(20); } void KexiTableScrollArea::setHBarGeometry(QScrollBar & hbar, int x, int y, int w, int h) { #ifdef KEXITABLEVIEW_DEBUG /*todo*/ qDebug(); #endif if (d->appearance.navigatorEnabled) { m_navPanel->setHBarGeometry(hbar, x, y, w, h); } else { hbar.setGeometry(x , y, w, h); } } void KexiTableScrollArea::setSpreadSheetMode(bool set) { KexiDataAwareObjectInterface::setSpreadSheetMode(set); d->setSpreadSheetMode(set); } int KexiTableScrollArea::validRowNumber(const QString& text) { bool ok = true; int r = text.toInt(&ok); if (!ok || r < 1) r = 1; else if (r > (recordCount() + (isInsertingEnabled() ? 1 : 0))) r = recordCount() + (isInsertingEnabled() ? 1 : 0); return r -1; } void KexiTableScrollArea::moveToRecordRequested(int record) { setFocus(); selectRecord(record); } void KexiTableScrollArea::moveToLastRecordRequested() { setFocus(); selectLastRecord(); } void KexiTableScrollArea::moveToPreviousRecordRequested() { setFocus(); selectPreviousRecord(); } void KexiTableScrollArea::moveToNextRecordRequested() { setFocus(); selectNextRecord(); } void KexiTableScrollArea::moveToFirstRecordRequested() { setFocus(); selectFirstRecord(); } void KexiTableScrollArea::copySelection() { if (m_currentRecord && m_curColumn != -1) { KexiTableEdit *edit = tableEditorWidget(m_curColumn); QVariant defaultValue; const bool defaultValueDisplayed = isDefaultValueDisplayed(m_currentRecord, m_curColumn, &defaultValue); if (edit) { QVariant visibleValue; getVisibleLookupValue(visibleValue, edit, m_currentRecord, m_data->column(m_curColumn)); edit->handleCopyAction( defaultValueDisplayed ? defaultValue : m_currentRecord->at(m_curColumn), visibleValue); } } } void KexiTableScrollArea::cutSelection() { //try to handle @ editor's level KexiTableEdit *edit = tableEditorWidget(m_curColumn); if (edit) edit->handleAction("edit_cut"); } void KexiTableScrollArea::paste() { //try to handle @ editor's level KexiTableEdit *edit = tableEditorWidget(m_curColumn); if (edit) edit->handleAction("edit_paste"); } bool KexiTableScrollArea::eventFilter(QObject *o, QEvent *e) { //don't allow to stole key my events by others: // qDebug() << "spontaneous " << e->spontaneous() << " type=" << e->type(); #ifdef KEXITABLEVIEW_DEBUG if (e->type() != QEvent::Paint && e->type() != QEvent::Leave && e->type() != QEvent::MouseMove && e->type() != QEvent::HoverMove && e->type() != QEvent::HoverEnter && e->type() != QEvent::HoverLeave) { qDebug() << e << o; } if (e->type() == QEvent::Paint) { qDebug() << "PAINT!" << static_cast(e) << static_cast(e)->rect(); } #endif if (e->type() == QEvent::KeyPress) { if (e->spontaneous()) { QKeyEvent *ke = static_cast(e); const int k = ke->key(); int mods = ke->modifiers(); //cell editor's events: //try to handle the event @ editor's level KexiTableEdit *edit = tableEditorWidget(m_curColumn); if (edit && edit->handleKeyPress(ke, m_editor == edit)) { ke->accept(); return true; } else if (m_editor && (o == dynamic_cast(m_editor) || o == m_editor->widget())) { if ((k == Qt::Key_Tab && (mods == Qt::NoModifier || mods == Qt::ShiftModifier)) || (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::Leave) { if ( o == d->scrollAreaWidget && d->appearance.recordMouseOverHighlightingEnabled && d->appearance.persistentSelections) { if (d->highlightedRecord != -1) { int oldRow = d->highlightedRecord; d->highlightedRecord = -1; updateRecord(oldRow); d->verticalHeader->updateSection(oldRow); const bool dontPaintNonpersistentSelectionBecauseDifferentRowHasBeenHighlighted = d->appearance.recordHighlightingEnabled && !d->appearance.persistentSelections; if (oldRow != m_curRecord && m_curRecord >= 0) { if (!dontPaintNonpersistentSelectionBecauseDifferentRowHasBeenHighlighted) { //no highlight for now: show selection again updateRecord(m_curRecord); } } } } d->recentCellWithToolTip = QPoint(-1, -1); } else if (o == viewport() && e->type() == QEvent::DragEnter) { e->accept(); } return QScrollArea::eventFilter(o, e); } void KexiTableScrollArea::setBottomMarginInternal(int pixels) { d->internal_bottomMargin = pixels; updateWidgetContentsSize(); } void KexiTableScrollArea::changeEvent(QEvent *e) { switch (e->type()) { case QEvent::PaletteChange: { d->verticalHeader->setSelectionBackgroundColor(palette().color(QPalette::Highlight)); d->horizontalHeader->setSelectionBackgroundColor(palette().color(QPalette::Highlight)); break; } default:; } QScrollArea::changeEvent(e); } const KexiTableScrollArea::Appearance& KexiTableScrollArea::appearance() const { return d->appearance; } void KexiTableScrollArea::setAppearance(const Appearance& a) { setFont(font()); //this also updates contents if (a.fullRecordSelection) { d->recordHeight -= 1; } else { d->recordHeight += 1; } if (d->verticalHeader) { d->verticalHeader->setDefaultSectionSize(d->recordHeight); } if (a.recordHighlightingEnabled) { m_updateEntireRecordWhenMovingToOtherRecord = true; } navPanelWidget()->setVisible(a.navigatorEnabled); setHorizontalScrollBarPolicy(a.navigatorEnabled ? Qt::ScrollBarAlwaysOn : Qt::ScrollBarAsNeeded); d->highlightedRecord = -1; //! @todo is setMouseTracking useful for other purposes? viewport()->setMouseTracking(a.recordMouseOverHighlightingEnabled); d->appearance = a; updateViewportMargins(); } int KexiTableScrollArea::highlightedRecordNumber() const { return d->highlightedRecord; } void KexiTableScrollArea::setHighlightedRecordNumber(int record) { if (record != -1) { record = qMin(recordCount() - 1 + (isInsertingEnabled() ? 1 : 0), record); record = qMax(0, record); } const int previouslyHighlightedRow = d->highlightedRecord; if (previouslyHighlightedRow == record) { if (previouslyHighlightedRow != -1) updateRecord(previouslyHighlightedRow); return; } d->highlightedRecord = record; if (d->highlightedRecord != -1) updateRecord(d->highlightedRecord); if (previouslyHighlightedRow != -1) updateRecord(previouslyHighlightedRow); if (m_curRecord >= 0 && (previouslyHighlightedRow == -1 || previouslyHighlightedRow == m_curRecord) && d->highlightedRecord != m_curRecord && !d->appearance.persistentSelections) { //currently selected row needs to be repainted updateRecord(m_curRecord); } } KDbRecordData *KexiTableScrollArea::highlightedRecord() const { return d->highlightedRecord == -1 ? 0 : m_data->at(d->highlightedRecord); } QScrollBar* KexiTableScrollArea::verticalScrollBar() const { return QScrollArea::verticalScrollBar(); } int KexiTableScrollArea::lastVisibleRecord() const { return recordNumberAt(verticalScrollBar()->value()); } void KexiTableScrollArea::valueChanged(KexiDataItemInterface* item) { #ifdef KEXITABLEVIEW_DEBUG qDebug() << item->field()->name() << item->value(); #else Q_UNUSED(item); #endif // force reload editing-related actions emit updateSaveCancelActions(); } bool KexiTableScrollArea::cursorAtNewRecord() const { return m_newRecordEditing; } void KexiTableScrollArea::lengthExceeded(KexiDataItemInterface *item, bool lengthExceeded) { showLengthExceededMessage(item, lengthExceeded); } void KexiTableScrollArea::updateLengthExceededMessage(KexiDataItemInterface *item) { showUpdateForLengthExceededMessage(item); } QHeaderView* KexiTableScrollArea::horizontalHeader() const { return d->horizontalHeader; } QHeaderView* KexiTableScrollArea::verticalHeader() const { return d->verticalHeader; } int KexiTableScrollArea::horizontalHeaderHeight() const { return d->horizontalHeader->height(); } QWidget* KexiTableScrollArea::navPanelWidget() const { return dynamic_cast(m_navPanel); } bool KexiTableScrollArea::navPanelWidgetVisible() const { return navPanelWidget() && d->appearance.navigatorEnabled; } bool KexiTableScrollArea::event(QEvent *e) { switch (e->type()) { case QEvent::QueryWhatsThis: case QEvent::WhatsThis: { QHelpEvent *he = static_cast(e); QString text = whatsThisText(he->pos()); if (!text.isEmpty()) { if (e->type() == QEvent::WhatsThis) { QWhatsThis::showText(mapToGlobal(he->pos()), text, this); } return true; } return false; } default: break; } return QScrollArea::event(e); } QString KexiTableScrollArea::whatsThisText(const QPoint &pos) const { const int leftMargin = verticalHeaderVisible() ? d->verticalHeader->width() : 0; if (KDbUtils::hasParent(d->verticalHeader, childAt(pos))) { return xi18nc("@info:whatsthis", "Contains a pointer to the currently selected record."); } else if (KDbUtils::hasParent(navPanelWidget(), childAt(pos))) { return xi18nc("@info:whatsthis", "Record navigator."); } const int col = columnNumberAt(pos.x() - leftMargin); KDbField *f = col == -1 ? 0 : field(col); if (!f) { return QString(); } return xi18nc("@info:whatsthis", "Column %1.", f->description().isEmpty() ? f->captionOrName() : f->description()); } void KexiTableScrollArea::selectCellInternal(int previousRow, int previousColumn) { // let the current style draw selection d->horizontalHeader->setCurrentIndex( d->horizontalHeader->selectionModel()->model()->index(m_curRecord, m_curColumn)); d->verticalHeader->setCurrentIndex( d->verticalHeader->selectionModel()->model()->index(m_curRecord, m_curColumn)); if (previousColumn != m_curColumn) { d->horizontalHeader->updateSection(previousColumn); } d->horizontalHeader->updateSection(m_curColumn); if (previousRow != m_curRecord) { d->verticalHeader->updateSection(previousRow); } d->verticalHeader->updateSection(m_curRecord); } QAbstractItemModel* KexiTableScrollArea::headerModel() const { return d->headerModel; } void KexiTableScrollArea::beginInsertItem(KDbRecordData *data, int pos) { Q_UNUSED(data); KexiTableScrollAreaHeaderModel* headerModel = static_cast(d->headerModel); headerModel->beginInsertRows(headerModel->index(pos, 0).parent(), pos, pos); } void KexiTableScrollArea::endInsertItem(KDbRecordData *data, int pos) { Q_UNUSED(data); Q_UNUSED(pos); KexiTableScrollAreaHeaderModel* headerModel = static_cast(d->headerModel); headerModel->endInsertRows(); } void KexiTableScrollArea::beginRemoveItem(KDbRecordData *data, int pos) { Q_UNUSED(data); KexiTableScrollAreaHeaderModel* headerModel = static_cast(d->headerModel); headerModel->beginRemoveRows(headerModel->index(pos, 0).parent(), pos, pos); } void KexiTableScrollArea::endRemoveItem(int pos) { Q_UNUSED(pos); KexiTableScrollAreaHeaderModel* headerModel = static_cast(d->headerModel); headerModel->endRemoveRows(); updateWidgetContentsSize(); } int KexiTableScrollArea::recordCount() const { return KexiDataAwareObjectInterface::recordCount(); } int KexiTableScrollArea::currentRecord() const { return KexiDataAwareObjectInterface::currentRecord(); } diff --git a/src/widget/utils/kexirecordnavigator.cpp b/src/widget/utils/kexirecordnavigator.cpp index 09af4095a..00c4f6f73 100644 --- a/src/widget/utils/kexirecordnavigator.cpp +++ b/src/widget/utils/kexirecordnavigator.cpp @@ -1,626 +1,626 @@ /* This file is part of the KDE project Copyright (C) 2004 Lucijan Busch Copyright (C) 2003-2015 JarosÅ‚aw Staniek 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 #include #include #include #include #include #include #include #include #include #include -#include +#include #include #include #include #include "kexirecordnavigator.h" #include #include #include //! @internal /*! @warning not reentrant! */ struct KexiRecordNavigatorStatic { KexiRecordNavigatorStatic() : pen(":/kexi-tableview-pen") , plus(":/kexi-tableview-plus") , pointer(":/kexi-tableview-pointer") { } static QPixmap replaceColors(const QPixmap &pixmap, const QPalette &palette) { QColor fc(palette.color(QPalette::Foreground)); QPixmap p(pixmap); KexiUtils::replaceColors(&p, fc); return p; } QPixmap pen, plus, pointer; }; Q_GLOBAL_STATIC(KexiRecordNavigatorStatic, KexiRecordNavigator_static) // ---- //! @internal class Q_DECL_HIDDEN KexiRecordNavigator::Private { public: Private() : handler(0) , view(0) , editingIndicatorLabel(0) , editingIndicatorEnabled(false) , editingIndicatorVisible(false) , isInsertingEnabled(true) { } KexiRecordNavigatorHandler *handler; QHBoxLayout *lyr; QLabel *textLabel; QToolButton *navBtnFirst; QToolButton *navBtnPrev; QToolButton *navBtnNext; QToolButton *navBtnLast; QToolButton *navBtnNew; QLineEdit *navRecordNumber; QIntValidator *navRecordNumberValidator; QLineEdit *navRecordCount; //!< readonly counter int nav1DigitWidth; QAbstractScrollArea *view; QLabel *editingIndicatorLabel; bool editingIndicatorEnabled; bool editingIndicatorVisible; bool isInsertingEnabled; }; //-------------------------------------------------- #include KexiRecordNavigator::KexiRecordNavigator(QAbstractScrollArea &parentView, QWidget *parent) : QWidget(parent) , d(new Private) { d->view = &parentView; setFocusPolicy(Qt::NoFocus); setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred); d->view->addScrollBarWidget(this, Qt::AlignLeft); d->view->horizontalScrollBar()->installEventFilter(this); d->lyr = new QHBoxLayout(this); d->lyr->setContentsMargins(0, /*winStyle ? 1 :*/ 0, 0, 0); d->lyr->setSpacing(2); d->textLabel = new QLabel(this); d->lyr->addWidget(d->textLabel); setLabelText(xi18n("Record:")); setFont(KexiUtils::smallestReadableFont()); QFontMetrics fm(font()); d->nav1DigitWidth = fm.width("8"); d->navBtnFirst = createAction(KexiRecordNavigator::Actions::moveToFirstRecord()); d->navBtnPrev = createAction(KexiRecordNavigator::Actions::moveToPreviousRecord()); d->navBtnPrev->setAutoRepeat(true); d->lyr->addSpacing(2); d->navRecordNumber = new QLineEdit(this); d->lyr->addWidget(d->navRecordNumber, 0, Qt::AlignVCenter); d->navRecordNumber->setContentsMargins(QMargins()); d->navRecordNumber->setFrame(false); d->navRecordNumber->setFixedHeight(fm.height() + 2); d->navRecordNumber->setAlignment(Qt::AlignRight | (/*winStyle ? Qt::AlignBottom :*/ Qt::AlignVCenter)); d->navRecordNumber->setFocusPolicy(Qt::ClickFocus); d->navRecordNumberValidator = new QIntValidator(1, INT_MAX, this); d->navRecordNumber->setValidator(d->navRecordNumberValidator); d->navRecordNumber->installEventFilter(this); d->navRecordNumber->setToolTip(xi18n("Current record number")); QLabel *lbl_of = new QLabel(xi18nc("\"of\" in record number information: N of M", "of"), this); lbl_of->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); lbl_of->setFixedWidth(fm.width(lbl_of->text()) + d->nav1DigitWidth); lbl_of->setAlignment(Qt::AlignCenter); d->lyr->addWidget(lbl_of, 0, Qt::AlignVCenter); d->navRecordCount = new QLineEdit(this); d->lyr->addWidget(d->navRecordCount, 0, Qt::AlignVCenter); d->navRecordCount->setFrame(false); d->navRecordCount->setReadOnly(true); QPalette navRecordCountPalette(d->navRecordCount->palette()); navRecordCountPalette.setBrush( QPalette::Base, QBrush(Qt::transparent) ); d->navRecordCount->setPalette(navRecordCountPalette); d->navRecordCount->setFixedHeight(d->navRecordNumber->maximumHeight()); d->navRecordCount->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); d->navRecordCount->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); d->navRecordCount->setFocusPolicy(Qt::NoFocus); d->navRecordCount->setToolTip(xi18n("Number of records")); d->navBtnNext = createAction(KexiRecordNavigator::Actions::moveToNextRecord()); d->navBtnNext->setAutoRepeat(true); d->navBtnLast = createAction(KexiRecordNavigator::Actions::moveToLastRecord()); d->lyr->addSpacing(2); d->navBtnNew = createAction(KexiRecordNavigator::Actions::moveToNewRecord()); d->navBtnNew->setEnabled(isInsertingEnabled()); d->lyr->addSpacing(2); connect(d->navBtnPrev, SIGNAL(clicked()), this, SLOT(slotPrevButtonClicked())); connect(d->navBtnNext, SIGNAL(clicked()), this, SLOT(slotNextButtonClicked())); connect(d->navBtnLast, SIGNAL(clicked()), this, SLOT(slotLastButtonClicked())); connect(d->navBtnFirst, SIGNAL(clicked()), this, SLOT(slotFirstButtonClicked())); connect(d->navBtnNew, SIGNAL(clicked()), this, SLOT(slotNewButtonClicked())); setRecordCount(0); setCurrentRecordNumber(0); } KexiRecordNavigator::~KexiRecordNavigator() { delete d; } QToolButton* KexiRecordNavigator::createAction(const KGuiItem& item) { // add the button outside of the layout: needed because oxygen, and especially qtcurve draw strange margins QWidget *par = new QWidget(this); d->lyr->addWidget(par, 0, Qt::AlignVCenter); QToolButton *toolButton = new KexiSmallToolButton(item.icon(), par); toolButton->setMinimumWidth(toolButton->sizeHint().width() + 2*3); par->setMinimumWidth(toolButton->minimumWidth()); toolButton->setFocusPolicy(Qt::NoFocus); toolButton->setToolTip(item.toolTip()); toolButton->setWhatsThis(item.whatsThis()); toolButton->installEventFilter(this); return toolButton; } void KexiRecordNavigator::setInsertingEnabled(bool set) { if (d->isInsertingEnabled == set) return; d->isInsertingEnabled = set; d->navBtnNew->setEnabled(d->isInsertingEnabled); } void KexiRecordNavigator::setEnabled(bool set) { QWidget::setEnabled(set); if (set && !d->isInsertingEnabled) d->navBtnNew->setEnabled(false); } bool KexiRecordNavigator::eventFilter(QObject *o, QEvent *e) { const QEvent::Type t = e->type(); const QScrollBar *bar; if (t == QEvent::Wheel) { wheelEvent(static_cast(e)); return true; } else if (o == d->navRecordNumber) { bool recordEntered = false; bool ret; if (t == QEvent::KeyPress) { QKeyEvent *ke = static_cast(e); switch (ke->key()) { case Qt::Key_Escape: { ke->accept(); d->navRecordNumber->undo(); d->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 (t == QEvent::FocusOut) { if (static_cast(e)->reason() != Qt::TabFocusReason && static_cast(e)->reason() != Qt::BacktabFocusReason && static_cast(e)->reason() != Qt::OtherFocusReason) { recordEntered = true; } ret = false; } if (recordEntered) { bool ok = true; uint r = d->navRecordNumber->text().toUInt(&ok); if (!ok || r < 1) r = (recordCount() > 0) ? 1 : 0; if (hasFocus() || t == QEvent::KeyPress) { d->view->setFocus(); } setCurrentRecordNumber(r); emit recordNumberEntered(r); if (d->handler) d->handler->moveToRecordRequested(r - 1); return ret; } } else if ((bar = d->view->horizontalScrollBar()) == o) { // Visually hide and deactivate the scrollbar if it's unusable (there are no content to scroll) if (bar->value() == 0 && bar->minimum() == 0 && bar->maximum() == 0) { QWidget *w = qobject_cast(o); if (t == QEvent::Paint && w->style()->objectName() == QLatin1String("gtk+")) { // workaround for GTK+ style: fill the background QPainter p(w); p.fillRect(w->rect(), w->palette().color(QPalette::Window)); } return true; } } return false; } void KexiRecordNavigator::wheelEvent(QWheelEvent* wheelEvent) { const int delta = wheelEvent->delta(); // trigger the respective button slots if (delta > 0) { if (d->navBtnPrev->isEnabled()) { slotPrevButtonClicked(); } } else if (delta < 0) { if (d->navBtnNext->isEnabled()) { slotNextButtonClicked(); } } // scroll wheel events also cancel the editing, // so move focus out of the navRecordNumber if (d->navRecordNumber->hasFocus()) { if (d->view) { d->view->setFocus(); } } } void KexiRecordNavigator::resizeEvent(QResizeEvent *e) { QWidget::resizeEvent(e); // update height of buttons: they are not in the layout foreach (KexiSmallToolButton *btn, findChildren()) { const int h = height(); btn->setFixedHeight(h + 2); btn->parentWidget()->setFixedHeight(h); btn->move(0, (btn->parentWidget()->height() - btn->height()) / 2); } } void KexiRecordNavigator::setCurrentRecordNumber(int r) { int recCnt = recordCount(); if (r > (recCnt + (d->isInsertingEnabled ? 1 : 0))) r = recCnt + (d->isInsertingEnabled ? 1 : 0); QString n; if (r > 0) n = QString::number(r); else n = " "; d->navRecordNumber->setText(n); updateButtons(recCnt); } void KexiRecordNavigator::updateButtons(int recCnt) { const int r = currentRecordNumber(); if (isEnabled()) { d->navBtnPrev->setEnabled(r > 1); d->navBtnFirst->setEnabled(r > 1); d->navBtnNext->setEnabled(r > 0 && r < (recCnt + (d->isInsertingEnabled ? (1 + (d->editingIndicatorVisible ? 1 : 0)/*if we're editing, next btn is avail.*/) : 0))); d->navBtnLast->setEnabled(r != (recCnt + (d->editingIndicatorVisible ? 1 : 0)) && (d->editingIndicatorVisible || recCnt > 0)); } } void KexiRecordNavigator::setRecordCount(int count) { const QString & n = QString::number(count); if (d->isInsertingEnabled && currentRecordNumber() == 0) { setCurrentRecordNumber(1); } if (d->navRecordCount->text().length() != n.length()) {//resize d->navRecordCount->setFixedWidth(d->nav1DigitWidth * (n.length() + 1)); if (d->view->horizontalScrollBar()->isVisible()) { //+width of the delta //resize(width() + (n.length() - d->navRecordCount->text().length())*d->nav1DigitWidth, height()); } } //update record number widget's width const int w = d->nav1DigitWidth * qMax(qMax(n.length(), 2) + 1, d->navRecordNumber->text().length() + 1) + 2; if (d->navRecordNumber->width() != w) //resize d->navRecordNumber->setFixedWidth(w); d->navRecordCount->setText(n); updateButtons(recordCount()); } int KexiRecordNavigator::currentRecordNumber() const { bool ok = true; int r = d->navRecordNumber->text().toInt(&ok); if (!ok || r < 1) r = 0; return r; } int KexiRecordNavigator::recordCount() const { bool ok = true; int r = d->navRecordCount->text().toInt(&ok); if (!ok || r < 1) r = 0; return r; } 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) { d->textLabel->setText(text.isEmpty() ? QString() : (QString::fromLatin1(" ") + text + " ")); } void KexiRecordNavigator::setButtonToolTipText(KexiRecordNavigator::Button btn, const QString& txt) { switch (btn) { case KexiRecordNavigator::ButtonFirst: d->navBtnFirst->setToolTip(txt); break; case KexiRecordNavigator::ButtonPrevious: d->navBtnPrev->setToolTip(txt); break; case KexiRecordNavigator::ButtonNext: d->navBtnNext->setToolTip(txt); break; case KexiRecordNavigator::ButtonLast: d->navBtnLast->setToolTip(txt); break; case KexiRecordNavigator::ButtonNew: d->navBtnNew->setToolTip(txt); break; } } void KexiRecordNavigator::setButtonWhatsThisText(KexiRecordNavigator::Button btn, const QString& txt) { switch (btn) { case KexiRecordNavigator::ButtonFirst: d->navBtnFirst->setWhatsThis(txt); break; case KexiRecordNavigator::ButtonPrevious: d->navBtnPrev->setWhatsThis(txt); break; case KexiRecordNavigator::ButtonNext: d->navBtnNext->setWhatsThis(txt); break; case KexiRecordNavigator::ButtonLast: d->navBtnLast->setWhatsThis(txt); break; case KexiRecordNavigator::ButtonNew: d->navBtnNew->setWhatsThis(txt); break; } } void KexiRecordNavigator::setNumberFieldToolTips(const QString& numberTooltip, const QString& countTooltip) { d->navRecordNumber->setToolTip(numberTooltip); d->navRecordCount->setToolTip(countTooltip); } void KexiRecordNavigator::setInsertingButtonVisible(bool set) { d->navBtnNew->parentWidget()->setVisible(set); } 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::isInsertingEnabled() const { return d->isInsertingEnabled; } 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); d->editingIndicatorLabel->setFixedWidth(KexiRecordNavigator::penPixmap(palette()).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) { d->editingIndicatorLabel->setPixmap(KexiRecordNavigator::penPixmap(palette())); d->editingIndicatorLabel->setToolTip(xi18n("Editing indicator")); } else { d->editingIndicatorLabel->setPixmap(QPixmap()); d->editingIndicatorLabel->setToolTip(QString()); } } void KexiRecordNavigator::paintEvent(QPaintEvent* pe) { QWidget::paintEvent(pe); QPainter p(this); // add frame on top - QStyleOptionFrameV2 option; + QStyleOptionFrame option; option.initFrom(this); - option.features = QStyleOptionFrameV2::Flat; + option.features = QStyleOptionFrame::Flat; option.rect = QRect(option.rect.left() - 5, option.rect.top(), option.rect.width() + 10, option.rect.height() + 5); // to avoid rounding style()->drawPrimitive(QStyle::PE_Frame, &option, &p, this); } //------------------------------------------------ //! @internal class KexiRecordNavigatorActionsInternal { public: KexiRecordNavigatorActionsInternal() : moveToFirstRecord(xi18n("First record"), "go-first-view", xi18n("Go to first record")) , moveToPreviousRecord(xi18n("Previous record"), "go-previous-view", xi18n("Go to previous record")) , moveToNextRecord(xi18n("Next record"), "go-next-view", xi18n("Go to next record")) , moveToLastRecord(xi18n("Last record"), "go-last-view", xi18n("Go to last record")) , moveToNewRecord(xi18n("New record"), "list-add", xi18n("Go to new record")) { moveToFirstRecord.setWhatsThis(xi18n("Moves cursor to first record.")); moveToPreviousRecord.setWhatsThis(xi18n("Moves cursor to previous record.")); moveToNextRecord.setWhatsThis(xi18n("Moves cursor to next record.")); moveToLastRecord.setWhatsThis(xi18n("Moves cursor to last record.")); moveToNewRecord.setWhatsThis(xi18n("Moves cursor to new record and allows inserting.")); } KGuiItem moveToFirstRecord; KGuiItem moveToPreviousRecord; KGuiItem moveToNextRecord; KGuiItem moveToLastRecord; KGuiItem moveToNewRecord; }; Q_GLOBAL_STATIC(KexiRecordNavigatorActionsInternal, KexiRecordNavigatorActions_internal) const KGuiItem& KexiRecordNavigator::Actions::moveToFirstRecord() { return KexiRecordNavigatorActions_internal->moveToFirstRecord; } const KGuiItem& KexiRecordNavigator::Actions::moveToPreviousRecord() { return KexiRecordNavigatorActions_internal->moveToPreviousRecord; } const KGuiItem& KexiRecordNavigator::Actions::moveToNextRecord() { return KexiRecordNavigatorActions_internal->moveToNextRecord; } const KGuiItem& KexiRecordNavigator::Actions::moveToLastRecord() { return KexiRecordNavigatorActions_internal->moveToLastRecord; } const KGuiItem& KexiRecordNavigator::Actions::moveToNewRecord() { return KexiRecordNavigatorActions_internal->moveToNewRecord; } //static QPixmap KexiRecordNavigator::penPixmap(const QPalette &palette) { return KexiRecordNavigatorStatic::replaceColors( KexiRecordNavigator_static->pen, palette); } //static QPixmap KexiRecordNavigator::plusPixmap(const QPalette &palette) { return KexiRecordNavigatorStatic::replaceColors( KexiRecordNavigator_static->plus, palette); } //static QPixmap KexiRecordNavigator::pointerPixmap(const QPalette &palette) { return KexiRecordNavigatorStatic::replaceColors( KexiRecordNavigator_static->pointer, palette); }