diff --git a/CMakeLists.txt b/CMakeLists.txt index d02f3deab..97c58fa10 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,282 +1,282 @@ cmake_minimum_required(VERSION 3.0 FATAL_ERROR) find_package(ECM 1.8.0 REQUIRED NOMODULE) set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/modules ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) include(SetKexiCMakePolicies NO_POLICY_SCOPE) include(SetKexiVersionInfo) project(KEXI VERSION ${PROJECT_VERSION}) include(KexiAddTests) kexi_add_tests(OFF) # ECM include(ECMAddAppIcon) include(ECMAddTests) include(ECMGenerateHeaders) include(ECMInstallIcons) include(ECMMarkAsTest) include(ECMMarkNonGuiExecutable) include(ECMPoQmTools) include(ECMSetupVersion) include(KDEInstallDirs) include(KDECMakeSettings NO_POLICY_SCOPE) include(KDECompilerSettings NO_POLICY_SCOPE) # Own include(KexiMacros) include(KexiAddIconsRccFile) include(KexiGenerateDependencyGraph) ensure_out_of_source_build("Please refer to the build instruction https://community.kde.org/Kexi/Building") get_git_revision_and_branch() detect_release_build() ####################### ######################## ## Productset setting ## ######################## ####################### # For predefined productsets see the definitions in KexiProducts.cmake and # in the files in the folder cmake/productsets. # Finding out the products & features to build is done in 5 steps: # 1. have the user define the products/features wanted, by giving a productset # 2. estimate all additional required products/features # 3. estimate which of the products/features can be build by external deps # 4. find which products/features have been temporarily disabled due to problems # 5. estimate which of the products/features can be build by internal deps # get the special macros include(CalligraProductSetMacros) set(PRODUCTSET_DEFAULT "DESKTOP") if(NOT PRODUCTSET OR PRODUCTSET STREQUAL "ALL") # Force the default set also when it is "ALL" because we can't build both desktop and mobile set(PRODUCTSET ${PRODUCTSET_DEFAULT} CACHE STRING "Set of products/features to build" FORCE) endif() # get the definitions of products, features and product sets include(KexiProducts.cmake) if (RELEASE_BUILD) set(KEXI_SHOULD_BUILD_STAGING FALSE) else () set(KEXI_SHOULD_BUILD_STAGING TRUE) endif () # finally choose products/features to build calligra_set_productset(${PRODUCTSET}) ########################## ########################### ## Look for Qt, KF5 ## ########################### ########################## set(REQUIRED_KF5_VERSION 5.16.0) set(REQUIRED_KF5_COMPONENTS Archive Codecs Config ConfigWidgets CoreAddons GuiAddons I18n IconThemes ItemViews WidgetsAddons TextWidgets XmlGui ) if(SHOULD_BUILD_KEXI_DESKTOP_APP) list(APPEND REQUIRED_KF5_COMPONENTS Completion KIO TextEditor TextWidgets ) endif() find_package(KF5 ${REQUIRED_KF5_VERSION} REQUIRED COMPONENTS ${REQUIRED_KF5_COMPONENTS}) find_package(KF5 ${REQUIRED_KF5_VERSION} COMPONENTS Crash) macro_bool_to_01(KF5Crash_FOUND HAVE_KCRASH) set_package_properties(KF5Crash PROPERTIES TYPE OPTIONAL PURPOSE "Used to provide crash reporting on Linux") set(REQUIRED_QT_VERSION 5.4.0) find_package(Qt5 ${REQUIRED_QT_VERSION} REQUIRED COMPONENTS Core Gui Widgets Xml Network PrintSupport Svg Test) find_package(Qt5 ${REQUIRED_QT_VERSION} COMPONENTS UiTools WebKit WebKitWidgets) # use sane compile flags add_definitions( -DQT_NO_CAST_TO_ASCII -DQT_NO_SIGNALS_SLOTS_KEYWORDS -DQT_NO_URL_CAST_FROM_STRING -DQT_STRICT_ITERATORS -DQT_USE_QSTRINGBUILDER ) # overcome some platform incompatibilities if(WIN32) find_package(KDEWin REQUIRED) endif() # set custom KEXI plugin installdir set(KEXI_PLUGIN_INSTALL_DIR ${PLUGIN_INSTALL_DIR}/${KEXI_BASE_PATH}) simple_option(BUILD_EXAMPLES "Build and install examples" ON) macro_bool_to_01(BUILD_EXAMPLES COMPILING_EXAMPLES) # set custom KEXI examples installdir set(KEXI_EXAMPLES_INSTALL_DIR ${SHARE_INSTALL_PREFIX}/${KEXI_BASE_PATH}/examples) # TEMPORARY: for initial Qt5/KF5 build porting phase deprecation warnings are only annoying noise # remove once code porting phase starts, perhaps first locally in product subdirs #if (CMAKE_COMPILER_IS_GNUCXX OR CMAKE_COMPILER_IS_GNUC) # add_definitions(-Wno-deprecated -Wno-deprecated-declarations) #endif () ########################### ############################ ## Required dependencies ## ############################ ########################### set(KEXI_FRAMEWORKS_MIN_VERSION ${PROJECT_VERSION}) ## ## Test for KDb ## simple_option(KEXI_DEBUG_GUI "Debugging GUI for KEXI (requires KDB_DEBUG_GUI to be set too)" OFF) if(KEXI_DEBUG_GUI) set(KDB_REQUIRED_COMPONENTS DEBUG_GUI) endif() find_package(KDb ${KEXI_FRAMEWORKS_MIN_VERSION} REQUIRED COMPONENTS ${KDB_REQUIRED_COMPONENTS}) set_package_properties(KDb PROPERTIES TYPE REQUIRED PURPOSE "Required by KEXI for data handling") ## ## Test for KReport ## find_package(KReport ${KEXI_FRAMEWORKS_MIN_VERSION}) set_package_properties(KReport PROPERTIES TYPE REQUIRED PURPOSE "Required by KEXI for report handling") if (KReport_FOUND) if(NOT KREPORT_SCRIPTING) message(FATAL_ERROR "KEXI requires KReport package with scripting support enabled (KREPORT_SCRIPTING)") endif() endif() ## ## Test for KPropertyWidgets ## if(SHOULD_BUILD_KEXI_DESKTOP_APP) find_package(KPropertyWidgets ${KEXI_FRAMEWORKS_MIN_VERSION} REQUIRED COMPONENTS KF) set_package_properties(KPropertyWidgets PROPERTIES TYPE REQUIRED PURPOSE "Required by KEXI for handling properties") else() # only KPropertyCore find_package(KPropertyCore ${KEXI_FRAMEWORKS_MIN_VERSION} REQUIRED COMPONENTS KF) set_package_properties(KPropertyCore PROPERTIES TYPE REQUIRED PURPOSE "Required by KEXI for handling properties") endif() include(CheckIfQtGuiCanBeExecuted) if(SHOULD_BUILD_KEXI_DESKTOP_APP) include(CheckGlobalBreezeIcons) endif() ########################### ############################ ## Optional dependencies ## ############################ ########################### ## ## Test for marble ## set(MARBLE_MIN_VERSION "0.19.2") find_package(KexiMarble ${MARBLE_MIN_VERSION}) set_package_properties(KexiMarble PROPERTIES DESCRIPTION "KDE World Globe Widget library" URL "https://marble.kde.org" TYPE RECOMMENDED PURPOSE "Required by KEXI form map widget" ) if(NOT MARBLE_FOUND) set(MARBLE_INCLUDE_DIR "") else() set(HAVE_MARBLE TRUE) endif() set_package_properties(GLIB2 PROPERTIES TYPE RECOMMENDED PURPOSE "${_REQUIRED_BY_MDB}") ## ## Test for Qt WebKitWidgets ## #TODO switch to Qt WebEngine macro_bool_to_01(Qt5WebKitWidgets_FOUND HAVE_QTWEBKITWIDGETS) set_package_properties(Qt5WebKit PROPERTIES DESCRIPTION "Webkit for Qt, the HTML engine." - URL "http://qt.io" + URL "https://qt.io" TYPE RECOMMENDED PURPOSE "Required by KEXI web form widget" ) set_package_properties(Qt5WebKitWidgets PROPERTIES DESCRIPTION "QWidgets module for Webkit, the HTML engine." - URL "http://qt.io" + URL "https://qt.io" TYPE RECOMMENDED PURPOSE "Required by KEXI web form widget" ) ################## ################### ## Helper macros ## ################### ################## include(MacroKexiAddBenchmark) include(MacroKexiAddTest) ############################################# #### Temporarily broken products #### ############################################# # If a product does not build due to some temporary brokeness disable it here, # by calling calligra_disable_product with the product id and the reason, # e.g.: # calligra_disable_product(APP_KEXI "isn't buildable at the moment") ############################################# #### Calculate buildable products #### ############################################# calligra_drop_unbuildable_products() ############################################# #### Setup product-depending vars #### ############################################# ################### #################### ## Subdirectories ## #################### ################### add_subdirectory(src) if(SHOULD_BUILD_DOC) find_package(KF5 ${KF5_DEP_VERSION} REQUIRED COMPONENTS DocTools) add_subdirectory(doc) endif() # non-app directories are moved here because they can depend on SHOULD_BUILD_{appname} variables set above add_subdirectory(cmake) if (IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/po") ki18n_install(po) endif() calligra_product_deps_report("product_deps") calligra_log_should_build() feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/cmake/modules/FindGLIB2.cmake b/cmake/modules/FindGLIB2.cmake index cddb7b79d..1c78e9f61 100644 --- a/cmake/modules/FindGLIB2.cmake +++ b/cmake/modules/FindGLIB2.cmake @@ -1,56 +1,56 @@ # - Try to find the GLIB2 libraries # Once done this will define # # GLIB2_FOUND - system has glib2 # GLIB2_INCLUDE_DIR - the glib2 include directory # GLIB2_LIBRARIES - glib2 library # Copyright (c) 2008 Laurent Montel, # # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. include(FeatureSummary) set_package_properties(GLIB2 PROPERTIES DESCRIPTION "Common C routines used by GTK+ and other libs" URL "http://www.gtk.org") if(GLIB2_INCLUDE_DIR AND GLIB2_LIBRARIES) # Already in cache, be silent set(GLIB2_FIND_QUIETLY TRUE) endif(GLIB2_INCLUDE_DIR AND GLIB2_LIBRARIES) find_package(PkgConfig) pkg_check_modules(PC_LibGLIB2 QUIET glib-2.0) find_path(GLIB2_MAIN_INCLUDE_DIR NAMES glib.h HINTS ${PC_LibGLIB2_INCLUDEDIR} PATH_SUFFIXES glib-2.0) -find_library(GLIB2_LIBRARY - NAMES glib-2.0 +find_library(GLIB2_LIBRARY + NAMES glib-2.0 HINTS ${PC_LibGLIB2_LIBDIR} ) set(GLIB2_LIBRARIES ${GLIB2_LIBRARY}) # search the glibconfig.h include dir under the same root where the library is found get_filename_component(glib2LibDir "${GLIB2_LIBRARIES}" PATH) find_path(GLIB2_INTERNAL_INCLUDE_DIR glibconfig.h PATH_SUFFIXES glib-2.0/include HINTS ${PC_LibGLIB2_INCLUDEDIR} "${glib2LibDir}" ${CMAKE_SYSTEM_LIBRARY_PATH}) set(GLIB2_INCLUDE_DIR "${GLIB2_MAIN_INCLUDE_DIR}") # not sure if this include dir is optional or required # for now it is optional if(GLIB2_INTERNAL_INCLUDE_DIR) set(GLIB2_INCLUDE_DIR ${GLIB2_INCLUDE_DIR} "${GLIB2_INTERNAL_INCLUDE_DIR}") endif(GLIB2_INTERNAL_INCLUDE_DIR) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(GLIB2 DEFAULT_MSG GLIB2_LIBRARIES GLIB2_MAIN_INCLUDE_DIR) mark_as_advanced(GLIB2_INCLUDE_DIR GLIB2_LIBRARIES) diff --git a/kundo2_aware_xgettext.sh b/kundo2_aware_xgettext.sh index 315a459b4..3a5475a79 100644 --- a/kundo2_aware_xgettext.sh +++ b/kundo2_aware_xgettext.sh @@ -1,113 +1,113 @@ # # Helper function for extracting translatable messages from KEXI source code. # Usage: kundo2_aware_xgettext # If there are no messages or the is empty, the pot file is deleted. # # Example usage that creates $podir/myapp.pot file: # kundo2_aware_xgettext myapp.pot `find . -name \*.cpp -o -name \*.h` # function kundo2_aware_xgettext() { POTFILE="$podir/$1" shift if test -n "$*"; then # we rely on last line being a 'msgstr' signaling that strings has been extracted (a header is always present) # normally it ends with 'msgstr ""' but if plural it can end with eg 'msgstr[1] ""' kundo2_aware_xgettext_internal $* | tee "${POTFILE}" | tail -n1 | grep "^msgstr" > /dev/null \ || rm -f "${POTFILE}" 2> /dev/null fi } # How to unit test: # export podir=. # cp init-sample.pot sample.pot # source krita_xgettext.sh # add_ctxt_qtundo sample.pot # # Then check that all messages in sample.pot have "(qtundo-format)" in msgctxt. function add_ctxt_qtundo() { POT_PART_QUNDOFORMAT="$1" POT_PART_QUNDOFORMAT2="`mktemp $podir/_qundoformat2_XXXXXXXX.pot`" # Prepend "(qtundo-format)" to existing msgctxt properties of messages sed -i -e 's/^msgctxt "/msgctxt "(qtundo-format) /' "${POT_PART_QUNDOFORMAT}" # Add msgctxt "(qtundo-format)" to messages not having msgctxt yet # # lastLine != "#, fuzzy" is the check for the .pot header. # If lastLine starts with '"' the msgctxt has been split on several lines and is treated by sed above, so skip it mv "${POT_PART_QUNDOFORMAT}" "${POT_PART_QUNDOFORMAT2}" cat "${POT_PART_QUNDOFORMAT2}" | awk ' /^msgid "/ { if (lastLine !~ /^\"/ && lastLine !~ /^msgctxt/ && lastLine != "#, fuzzy") { print "msgctxt \"(qtundo-format)\"" } } { print ; lastLine = $0 }' > "${POT_PART_QUNDOFORMAT}" rm -f "${POT_PART_QUNDOFORMAT2}" } function kundo2_aware_xgettext_internal() { SRC_FILES="$*" POT_PART_NORMAL="`mktemp $podir/_normal_XXXXXXXX.pot`" POT_PART_QUNDOFORMAT="`mktemp $podir/_qundoformat_XXXXXXXX.pot`" POT_MERGED="`mktemp $podir/_merged_XXXXXXXX.pot`" $XGETTEXT ${CXG_EXTRA_ARGS} ${SRC_FILES} -o "${POT_PART_NORMAL}" --force-po XGETTEXT_FLAGS_KUNDO2="\ --copyright-holder=This_file_is_part_of_KDE \ ---msgid-bugs-address=http://bugs.kde.org \ +--msgid-bugs-address=https://bugs.kde.org \ --from-code=UTF-8 -C -k --kde \ -kkundo2_i18n:1 -kkundo2_i18np:1,2 -kkundo2_i18nc:1c,2 -kkundo2_i18ncp:1c,2,3 \ " $XGETTEXT_PROGRAM ${XGETTEXT_FLAGS_KUNDO2} ${CXG_EXTRA_ARGS} ${SRC_FILES} -o "${POT_PART_QUNDOFORMAT}" if [ $(cat ${POT_PART_NORMAL} ${POT_PART_QUNDOFORMAT} | grep -c \(qtundo-format\)) != 0 ]; then echo "ERROR: Context '(qtundo-format)' should not be added manually. Use kundo2_i18n*() calls instead." 1>&2 exit 17 fi if [ -s "${POT_PART_QUNDOFORMAT}" ]; then add_ctxt_qtundo "${POT_PART_QUNDOFORMAT}" fi if [ -s "${POT_PART_NORMAL}" -a -s "${POT_PART_QUNDOFORMAT}" ]; then # ensure an empty line or else KDE_HEADER search will fail # in case POT_PART_NORMAL only contains header echo "" >>${POT_PART_NORMAL} - + ${MSGCAT} -F "${POT_PART_NORMAL}" "${POT_PART_QUNDOFORMAT}" > ${POT_MERGED} MERGED_HEADER_LINE_COUNT=$(cat ${POT_MERGED} | grep "^$" -B 100000 --max-count=1 | wc -l) KDE_HEADER="$(cat ${POT_PART_NORMAL} | grep "^$" -B 100000 --max-count=1)" MERGED_TAIL="$(cat ${POT_MERGED} | tail -n +$MERGED_HEADER_LINE_COUNT)" # Print out the resulting .pot echo "$KDE_HEADER" echo "$MERGED_TAIL" elif [ -s "${POT_PART_NORMAL}" ]; then echo "# POT_PART_NORMAL only" cat "${POT_PART_NORMAL}" elif [ -s "${POT_PART_QUNDOFORMAT}" ]; then echo "# POT_PART_QUNDOFORMAT only" cat "${POT_PART_QUNDOFORMAT}" fi rm -f "${POT_PART_NORMAL}" "${POT_PART_QUNDOFORMAT}" "${POT_MERGED}" } # Sets EXCLUDE variable to excludes compatible with the find(1) command, e.g. '-path a -o -path b'. # To unconditionally exclude dir (with subdirs) just put an empty file .i18n in it. # To disable excluding for given file, e.g. foo.pot, add "foo.pot" line to the .i18n file. function find_exclude() { EXCLUDE="" for f in `find . -name .i18n | sed 's/\/\.i18n$//g' | sort`; do if ! grep -q "^${1}$" "$f/.i18n" ; then if [ -n "$EXCLUDE" ] ; then EXCLUDE="$EXCLUDE -o " ; fi EXCLUDE="$EXCLUDE -path $f" fi done if [ -z "$EXCLUDE" ] ; then EXCLUDE="-path __dummy__" ; fi # needed because -prune in find needs args } diff --git a/src/doc/dev/kexi_i18n_guidelines.txt b/src/doc/dev/kexi_i18n_guidelines.txt index f443818cf..ee8033761 100644 --- a/src/doc/dev/kexi_i18n_guidelines.txt +++ b/src/doc/dev/kexi_i18n_guidelines.txt @@ -1,39 +1,39 @@ Kexi Translation Issues ----------------------- -Many things can be found here: http://i18n.kde.org/translation-howto/index.html +Many things can be found here: https://i18n.kde.org/translation-howto/index.html Additional guidelines: #1. Let's not use "Do you really want" question. Use "Do you want" question instead. #2. Try do not overuse html formatting tags like in translated strings is you do not need to. Eg. rather use "" + i18n("Connection error: %") + "" instead of i18n("Connection error: %"); #3. Use " instead of ' , ie.: i18n("Object \"%1\" not found.") --GOOD instead of : i18n("Object '%1' not found.") --BAD Also, do not use tags around "" ie. i18n("Object \"%1\" not found.") --BAD -- staniek@kde.org, july 2004 diff --git a/src/kexiutils/KexiFadeWidgetEffect.cpp b/src/kexiutils/KexiFadeWidgetEffect.cpp index 1d1fc0799..9609586e8 100644 --- a/src/kexiutils/KexiFadeWidgetEffect.cpp +++ b/src/kexiutils/KexiFadeWidgetEffect.cpp @@ -1,159 +1,159 @@ /* This file is part of the KDE project Copyright (C) 2008 Matthias Kretz Copyright (C) 2008 Rafael Fernández López 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) version 3. 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 "KexiFadeWidgetEffect.h" #include "KexiFadeWidgetEffect_p.h" #include #include #include #include KexiFadeWidgetEffectPrivate::KexiFadeWidgetEffectPrivate(QWidget *_destWidget) : destWidget(_destWidget), disabled(false) { } // Code from KFileItemDelegate (Author: Frederik Höglund) // Fast transitions. Read: -// http://techbase.kde.org/Development/Tutorials/Graphics/Performance +// https://techbase.kde.org/Development/Tutorials/Graphics/Performance // for further information on why not use setOpacity. QPixmap KexiFadeWidgetEffectPrivate::transition(const QPixmap &from, const QPixmap &to, qreal amount) const { const int value = int(0xff * amount); if (value == 0) { return from; } if (value == 1) { return to; } QColor color; color.setAlphaF(amount); // If the native paint engine supports Porter/Duff compositing and CompositionMode_Plus if (from.paintEngine()->hasFeature(QPaintEngine::PorterDuff) && from.paintEngine()->hasFeature(QPaintEngine::BlendModes)) { QPixmap under = from; QPixmap over = to; QPainter p; p.begin(&over); p.setCompositionMode(QPainter::CompositionMode_DestinationIn); p.fillRect(over.rect(), color); p.end(); p.begin(&under); p.setCompositionMode(QPainter::CompositionMode_DestinationOut); p.fillRect(under.rect(), color); p.setCompositionMode(QPainter::CompositionMode_Plus); p.drawPixmap(0, 0, over); p.end(); return under; } else { // Fall back to using QRasterPaintEngine to do the transition. QImage under = from.toImage(); QImage over = to.toImage(); QPainter p; p.begin(&over); p.setCompositionMode(QPainter::CompositionMode_DestinationIn); p.fillRect(over.rect(), color); p.end(); p.begin(&under); p.setCompositionMode(QPainter::CompositionMode_DestinationOut); p.fillRect(under.rect(), color); p.setCompositionMode(QPainter::CompositionMode_Plus); p.drawImage(0, 0, over); p.end(); return QPixmap::fromImage(under); } } KexiFadeWidgetEffect::KexiFadeWidgetEffect(QWidget *destWidget, int defaultDuration) : QWidget(destWidget), d(new KexiFadeWidgetEffectPrivate(destWidget)) { d->defaultDuration = defaultDuration; Q_ASSERT(destWidget && destWidget->parentWidget()); if (!destWidget || !destWidget->parentWidget() || !destWidget->isVisible() || !style()->styleHint(QStyle::SH_Widget_Animate, 0, this)) { d->disabled = true; hide(); return; } destWidget->updateGeometry(); destWidget->repaint(); setGeometry(QRect(destWidget->mapTo(parentWidget(), QPoint(0, 0)), destWidget->size())); d->oldPixmap = destWidget->grab(); d->timeLine.setFrameRange(0, 255); d->timeLine.setCurveShape(QTimeLine::EaseOutCurve); connect(&d->timeLine, SIGNAL(finished()), SLOT(finished())); connect(&d->timeLine, SIGNAL(frameChanged(int)), SLOT(repaint())); show(); } KexiFadeWidgetEffect::~KexiFadeWidgetEffect() { delete d; } void KexiFadeWidgetEffect::finished() { d->destWidget->setUpdatesEnabled(false); hide(); deleteLater(); d->destWidget->setUpdatesEnabled(true); } void KexiFadeWidgetEffect::start(int duration) { if (d->disabled) { deleteLater(); return; } d->destWidget->updateGeometry(); d->destWidget->update(); qApp->processEvents(); hide(); d->newPixmap = d->destWidget->grab(); d->timeLine.setDuration(duration); show(); raise(); d->timeLine.start(); } void KexiFadeWidgetEffect::start() { start(d->defaultDuration); } void KexiFadeWidgetEffect::paintEvent(QPaintEvent *) { QPainter p(this); p.drawPixmap(rect(), d->transition(d->oldPixmap, d->newPixmap, d->timeLine.currentValue())); p.end(); } diff --git a/src/kexiutils/KexiJsonTrader.h b/src/kexiutils/KexiJsonTrader.h index 2ba1c0d28..dd10c5312 100644 --- a/src/kexiutils/KexiJsonTrader.h +++ b/src/kexiutils/KexiJsonTrader.h @@ -1,90 +1,90 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright (C) 2007 David Faure Copyright (C) 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. */ #ifndef KEXI_JSONTRADER_H #define KEXI_JSONTRADER_H #include "kexiutils_export.h" #include #include class QPluginLoader; class QJsonObject; /** * Support class to fetch a list of relevant plugins */ class KEXIUTILS_EXPORT KexiJsonTrader { public: //! Creates instance of the trader. //! @a subDir is a name of subdirectory in which plugin files of given type are stored, e.g. "kexi". explicit KexiJsonTrader(const QString& subDir); ~KexiJsonTrader(); /** * The main function in the KexiJsonTrader class. * * It will return a list of QPluginLoader objects that match your * specifications. The only required parameter is the @a servicetypes. * The @a mimetype parameter is used to limit the possible choices * returned based on the constraints you give it. * * The keys used in the query (Type, ServiceType, Exec) are all * fields found in the .desktop files. * * @param servicetypes A list of service types like 'KMyApp/Plugin' or 'KFilePlugin'. * At least one has to be found in a plugin. * @param mimetype A mimetype constraint to limit the choices returned, QString() to * get all services of the given @p servicetypes. * * @return A list of QPluginLoader that satisfy the query - * @see http://techbase.kde.org/Development/Tutorials/Services/Traders#The_KTrader_Query_Language + * @see https://techbase.kde.org/Development/Tutorials/Services/Traders#The_KTrader_Query_Language * * @note Ownership of the QPluginLoader objects is transferred to the caller. */ QList query(const QStringList &servicetypes, const QString &mimetype = QString()); /** * @overload QList query(const QStringList &, const QString &); */ QList query(const QString &servicetype, const QString &mimetype = QString()); /** * @return very top-level metadata JSON object for @a pluginLoader */ static QJsonObject metaDataObjectForPluginLoader(const QPluginLoader &pluginLoader); /** * @return root JSON object for @a pluginLoader, a "KPlugin" child item of object * returned by metaDataObjectForPluginLoader() */ static QJsonObject rootObjectForPluginLoader(const QPluginLoader &pluginLoader); private: Q_DISABLE_COPY(KexiJsonTrader) class Private; Private * const d; }; #endif diff --git a/src/kexiutils/completer/KexiCompleter.cpp b/src/kexiutils/completer/KexiCompleter.cpp index b8c3d12ad..0b464cd73 100644 --- a/src/kexiutils/completer/KexiCompleter.cpp +++ b/src/kexiutils/completer/KexiCompleter.cpp @@ -1,1914 +1,1914 @@ /**************************************************************************** ** ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of the QtGui module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** GNU Lesser General Public License Usage ** This file may be used under the terms of the GNU Lesser General Public ** License version 2.1 as published by the Free Software Foundation and ** appearing in the file LICENSE.LGPL included in the packaging of this ** file. Please review the following information to ensure the GNU Lesser ** General Public License version 2.1 requirements will be met: -** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU General ** Public License version 3.0 as published by the Free Software Foundation ** and appearing in the file LICENSE.GPL included in the packaging of this ** file. Please review the following information to ensure the GNU General ** Public License version 3.0 requirements will be met: -** http://www.gnu.org/copyleft/gpl.html. +** https://www.gnu.org/copyleft/gpl.html. ** ** Other Usage ** Alternatively, this file may be used in accordance with the terms and ** conditions contained in a signed written agreement between you and Nokia. ** ** ** ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ /*! \class KexiCompleter \brief The KexiCompleter class provides completions based on an item model. You can use KexiCompleter to provide auto completions in any Qt widget, such as QLineEdit and QComboBox. When the user starts typing a word, KexiCompleter suggests possible ways of completing the word, based on a word list. The word list is provided as a QAbstractItemModel. (For simple applications, where the word list is static, you can pass a QStringList to KexiCompleter's constructor.) \tableofcontents \section section1 Basic Usage A KexiCompleter is used typically with a QLineEdit or QComboBox. For example, here's how to provide auto completions from a simple word list in a QLineEdit: \snippet doc/src/snippets/code/src_gui_util_qcompleter.cpp 0 A QFileSystemModel can be used to provide auto completion of file names. For example: \snippet doc/src/snippets/code/src_gui_util_qcompleter.cpp 1 To set the model on which KexiCompleter should operate, call setModel(). By default, KexiCompleter will attempt to match the \l {completionPrefix}{completion prefix} (i.e., the word that the user has started typing) against the Qt::EditRole data stored in column 0 in the model case sensitively. This can be changed using setCompletionRole(), setCompletionColumn(), and setCaseSensitivity(). If the model is sorted on the column and role that are used for completion, you can call setModelSorting() with either KexiCompleter::CaseSensitivelySortedModel or KexiCompleter::CaseInsensitivelySortedModel as the argument. On large models, this can lead to significant performance improvements, because KexiCompleter can then use binary search instead of linear search. The model can be a \l{QAbstractListModel}{list model}, a \l{QAbstractTableModel}{table model}, or a \l{QAbstractItemModel}{tree model}. Completion on tree models is slightly more involved and is covered in the \l{Handling Tree Models} section below. The completionMode() determines the mode used to provide completions to the user. \section section2 Iterating Through Completions To retrieve a single candidate string, call setCompletionPrefix() with the text that needs to be completed and call currentCompletion(). You can iterate through the list of completions as below: \snippet doc/src/snippets/code/src_gui_util_qcompleter.cpp 2 completionCount() returns the total number of completions for the current prefix. completionCount() should be avoided when possible, since it requires a scan of the entire model. \section section3 The Completion Model completionModel() return a list model that contains all possible completions for the current completion prefix, in the order in which they appear in the model. This model can be used to display the current completions in a custom view. Calling setCompletionPrefix() automatically refreshes the completion model. \section section4 Handling Tree Models KexiCompleter can look for completions in tree models, assuming that any item (or sub-item or sub-sub-item) can be unambiguously represented as a string by specifying the path to the item. The completion is then performed one level at a time. Let's take the example of a user typing in a file system path. The model is a (hierarchical) QFileSystemModel. The completion occurs for every element in the path. For example, if the current text is \c C:\Wind, KexiCompleter might suggest \c Windows to complete the current path element. Similarly, if the current text is \c C:\Windows\Sy, KexiCompleter might suggest \c System. For this kind of completion to work, KexiCompleter needs to be able to split the path into a list of strings that are matched at each level. For \c C:\Windows\Sy, it needs to be split as "C:", "Windows" and "Sy". The default implementation of splitPath(), splits the completionPrefix using QDir::separator() if the model is a QFileSystemModel. To provide completions, KexiCompleter needs to know the path from an index. This is provided by pathFromIndex(). The default implementation of pathFromIndex(), returns the data for the \l{Qt::EditRole}{edit role} for list models and the absolute file path if the mode is a QFileSystemModel. \sa QAbstractItemModel, QLineEdit, QComboBox, {Completer Example} */ #include "KexiCompleter_p.h" #ifndef QT_NO_COMPLETER #include #include #include #include #include #include #include #include #include #include #include class KexiEmptyItemModel : public QAbstractItemModel { Q_OBJECT public: explicit KexiEmptyItemModel(QObject *parent = 0) : QAbstractItemModel(parent) {} QModelIndex index(int, int, const QModelIndex &) const override { return QModelIndex(); } QModelIndex parent(const QModelIndex &) const override { return QModelIndex(); } int rowCount(const QModelIndex &) const override { return 0; } int columnCount(const QModelIndex &) const override { return 0; } bool hasChildren(const QModelIndex &) const override { return false; } QVariant data(const QModelIndex &, int) const override { return QVariant(); } }; Q_GLOBAL_STATIC(KexiEmptyItemModel, kexiEmptyModel) QAbstractItemModel *KexiAbstractItemModelPrivate::staticEmptyModel() { return kexiEmptyModel(); } namespace { struct DefaultRoleNames : public QHash { DefaultRoleNames() { (*this)[Qt::DisplayRole] = "display"; (*this)[Qt::DecorationRole] = "decoration"; (*this)[Qt::EditRole] = "edit"; (*this)[Qt::ToolTipRole] = "toolTip"; (*this)[Qt::StatusTipRole] = "statusTip"; (*this)[Qt::WhatsThisRole] = "whatsThis"; } }; } Q_GLOBAL_STATIC(DefaultRoleNames, qDefaultRoleNames) const QHash &KexiAbstractItemModelPrivate::defaultRoleNames() { return *qDefaultRoleNames(); } KexiCompletionModel::KexiCompletionModel(KexiCompleterPrivate *c, QObject *parent) : QAbstractProxyModel(parent), c(c), showAll(false), d(new KexiCompletionModelPrivate(this)) { QAbstractProxyModel::setSourceModel(KexiAbstractItemModelPrivate::staticEmptyModel()); createEngine(); } KexiCompletionModel::~KexiCompletionModel() { delete d; } int KexiCompletionModel::columnCount(const QModelIndex &) const { return sourceModel()->columnCount(); } void KexiCompletionModel::setSourceModel(QAbstractItemModel *source) { bool hadModel = (sourceModel() != 0); if (hadModel) QObject::disconnect(sourceModel(), 0, this, 0); QAbstractProxyModel::setSourceModel(source ? source : KexiAbstractItemModelPrivate::staticEmptyModel()); if (source) { // TODO: Optimize updates in the source model connect(source, SIGNAL(modelReset()), this, SLOT(invalidate())); connect(source, SIGNAL(destroyed()), this, SLOT(modelDestroyed())); connect(source, SIGNAL(layoutChanged()), this, SLOT(invalidate())); connect(source, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(rowsInserted())); connect(source, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(invalidate())); connect(source, SIGNAL(columnsInserted(QModelIndex,int,int)), this, SLOT(invalidate())); connect(source, SIGNAL(columnsRemoved(QModelIndex,int,int)), this, SLOT(invalidate())); connect(source, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(invalidate())); } invalidate(); } void KexiCompletionModel::createEngine() { bool sortedEngine = false; switch (c->sorting) { case KexiCompleter::UnsortedModel: sortedEngine = false; break; case KexiCompleter::CaseSensitivelySortedModel: sortedEngine = c->cs == Qt::CaseSensitive; break; case KexiCompleter::CaseInsensitivelySortedModel: sortedEngine = c->cs == Qt::CaseInsensitive; break; } if (sortedEngine) engine.reset(new QSortedModelEngine(c)); else engine.reset(new QUnsortedModelEngine(c)); } QModelIndex KexiCompletionModel::mapToSource(const QModelIndex& index) const { if (!index.isValid()) return engine->curParent; int row; QModelIndex parent = engine->curParent; if (!showAll) { if (!engine->matchCount()) return QModelIndex(); Q_ASSERT(index.row() < engine->matchCount()); KexiIndexMapper& rootIndices = engine->historyMatch.indices; if (index.row() < rootIndices.count()) { row = rootIndices[index.row()]; parent = QModelIndex(); } else { row = engine->curMatch.indices[index.row() - rootIndices.count()]; } } else { row = index.row(); } return sourceModel()->index(row, index.column(), parent); } QModelIndex KexiCompletionModel::mapFromSource(const QModelIndex& idx) const { if (!idx.isValid()) return QModelIndex(); int row = -1; if (!showAll) { if (!engine->matchCount()) return QModelIndex(); KexiIndexMapper& rootIndices = engine->historyMatch.indices; if (idx.parent().isValid()) { if (idx.parent() != engine->curParent) return QModelIndex(); } else { row = rootIndices.indexOf(idx.row()); if (row == -1 && engine->curParent.isValid()) return QModelIndex(); // source parent and our parent don't match } if (row == -1) { KexiIndexMapper& indices = engine->curMatch.indices; engine->filterOnDemand(idx.row() - indices.last()); row = indices.indexOf(idx.row()) + rootIndices.count(); } if (row == -1) return QModelIndex(); } else { if (idx.parent() != engine->curParent) return QModelIndex(); row = idx.row(); } return createIndex(row, idx.column()); } bool KexiCompletionModel::setCurrentRow(int row) { if (row < 0 || !engine->matchCount()) return false; if (row >= engine->matchCount()) engine->filterOnDemand(row + 1 - engine->matchCount()); if (row >= engine->matchCount()) // invalid row return false; engine->curRow = row; return true; } QModelIndex KexiCompletionModel::currentIndex(bool sourceIndex) const { if (!engine->matchCount()) return QModelIndex(); int row = engine->curRow; if (showAll) row = engine->curMatch.indices[engine->curRow]; QModelIndex idx = createIndex(row, c->column); if (!sourceIndex) return idx; return mapToSource(idx); } QModelIndex KexiCompletionModel::index(int row, int column, const QModelIndex& parent) const { if (row < 0 || column < 0 || column >= columnCount(parent) || parent.isValid()) return QModelIndex(); if (!showAll) { if (!engine->matchCount()) return QModelIndex(); if (row >= engine->historyMatch.indices.count()) { int want = row + 1 - engine->matchCount(); if (want > 0) engine->filterOnDemand(want); if (row >= engine->matchCount()) return QModelIndex(); } } else { if (row >= sourceModel()->rowCount(engine->curParent)) return QModelIndex(); } return createIndex(row, column); } int KexiCompletionModel::completionCount() const { if (!engine->matchCount()) return 0; engine->filterOnDemand(INT_MAX); return engine->matchCount(); } int KexiCompletionModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) return 0; if (showAll) { // Show all items below current parent, even if we have no valid matches if (engine->curParts.count() != 1 && !engine->matchCount() && !engine->curParent.isValid()) return 0; return sourceModel()->rowCount(engine->curParent); } return completionCount(); } void KexiCompletionModel::setFiltered(bool filtered) { if (showAll == !filtered) return; showAll = !filtered; resetModel(); } bool KexiCompletionModel::hasChildren(const QModelIndex &parent) const { if (parent.isValid()) return false; if (showAll) return sourceModel()->hasChildren(mapToSource(parent)); if (!engine->matchCount()) return false; return true; } QVariant KexiCompletionModel::data(const QModelIndex& index, int role) const { return sourceModel()->data(mapToSource(index), role); } void KexiCompletionModel::modelDestroyed() { QAbstractProxyModel::setSourceModel(0); // switch to static empty model invalidate(); } void KexiCompletionModel::rowsInserted() { invalidate(); emit rowsAdded(); } void KexiCompletionModel::invalidate() { engine->cache.clear(); filter(engine->curParts); } void KexiCompletionModel::filter(const QStringList& parts) { engine->filter(parts); resetModel(); if (sourceModel()->canFetchMore(engine->curParent)) sourceModel()->fetchMore(engine->curParent); } void KexiCompletionModel::resetModel() { if (rowCount() == 0) { beginResetModel(); endResetModel(); return; } emit layoutAboutToBeChanged(); QModelIndexList piList = persistentIndexList(); QModelIndexList empty; for (int i = 0; i < piList.size(); i++) empty.append(QModelIndex()); changePersistentIndexList(piList, empty); emit layoutChanged(); } ////////////////////////////////////////////////////////////////////////////// void KexiCompletionEngine::filter(const QStringList& parts) { const QAbstractItemModel *model = c->proxy->sourceModel(); curParts = parts; if (curParts.isEmpty()) curParts.append(QString()); curRow = -1; curParent = QModelIndex(); curMatch = KexiMatchData(); historyMatch = filterHistory(); if (!model) return; QModelIndex parent; for (int i = 0; i < curParts.count() - 1; i++) { QString part = curParts[i]; int emi = filter(part, parent, -1).exactMatchIndex; if (emi == -1) return; parent = model->index(emi, c->column, parent); } // Note that we set the curParent to a valid parent, even if we have no matches // When filtering is disabled, we show all the items under this parent curParent = parent; if (curParts.last().isEmpty()) curMatch = KexiMatchData(KexiIndexMapper(0, model->rowCount(curParent) - 1), -1, false); else curMatch = filter(curParts.last(), curParent, 1); // build at least one curRow = curMatch.isValid() ? 0 : -1; } inline bool matchPrefix(const QString& s1, const QString& s2, Qt::CaseSensitivity cs) { return s1.startsWith(s2, cs); } inline bool matchSubstring(const QString& s1, const QString& s2, Qt::CaseSensitivity cs) { return s1.contains(s2, cs); } typedef bool (*MatchFunction)(const QString&, const QString&, Qt::CaseSensitivity); KexiMatchData KexiCompletionEngine::filterHistory() { QAbstractItemModel *source = c->proxy->sourceModel(); if (curParts.count() <= 1 || c->proxy->showAll || !source) return KexiMatchData(); #ifdef QT_NO_DIRMODEL bool isDirModel = false; #else bool isDirModel = qobject_cast(source) != 0; #endif #ifdef QT_NO_FILESYSTEMMODEL bool isFsModel = false; #else bool isFsModel = qobject_cast(source) != 0; #endif QVector v; KexiIndexMapper im(v); KexiMatchData m(im, -1, true); MatchFunction matchFunction = c->substringCompletion ? &matchSubstring : &matchPrefix; for (int i = 0; i < source->rowCount(); i++) { QString str = source->index(i, c->column).data().toString(); if (matchFunction(str, c->prefix, c->cs) #if (!defined(Q_OS_WIN) || defined(Q_OS_WINCE)) && !defined(Q_OS_SYMBIAN) && ((!isFsModel && !isDirModel) || QDir::toNativeSeparators(str) != QDir::separator()) #endif ) m.indices.append(i); } return m; } // Returns a match hint from the cache by chopping the search string bool KexiCompletionEngine::matchHint(QString part, const QModelIndex& parent, KexiMatchData *hint) { if (c->cs == Qt::CaseInsensitive) part = part.toLower(); const CacheItem& map = cache[parent]; QString key = part; while (!key.isEmpty()) { key.chop(1); if (map.contains(key)) { *hint = map[key]; return true; } } return false; } bool KexiCompletionEngine::lookupCache(QString part, const QModelIndex& parent, KexiMatchData *m) { if (c->cs == Qt::CaseInsensitive) part = part.toLower(); const CacheItem& map = cache[parent]; if (!map.contains(part)) return false; *m = map[part]; return true; } // When the cache size exceeds 1MB, it clears out about 1/2 of the cache. void KexiCompletionEngine::saveInCache(QString part, const QModelIndex& parent, const KexiMatchData& m) { KexiMatchData old = cache[parent].take(part); cost = cost + m.indices.cost() - old.indices.cost(); if (cost * sizeof(int) > 1024 * 1024) { QMap::iterator it1 = cache.begin(); while (it1 != cache.end()) { CacheItem& ci = it1.value(); int sz = ci.count()/2; QMap::iterator it2 = ci.begin(); int i = 0; while (it2 != ci.end() && i < sz) { cost -= it2.value().indices.cost(); it2 = ci.erase(it2); i++; } if (ci.count() == 0) { it1 = cache.erase(it1); } else { ++it1; } } } if (c->cs == Qt::CaseInsensitive) part = part.toLower(); cache[parent][part] = m; } /////////////////////////////////////////////////////////////////////////////////// KexiIndexMapper QSortedModelEngine::indexHint(QString part, const QModelIndex& parent, Qt::SortOrder order) { const QAbstractItemModel *model = c->proxy->sourceModel(); if (c->cs == Qt::CaseInsensitive) part = part.toLower(); const CacheItem& map = cache[parent]; // Try to find a lower and upper bound for the search from previous results int to = model->rowCount(parent) - 1; int from = 0; const CacheItem::const_iterator it = map.lowerBound(part); // look backward for first valid hint for(CacheItem::const_iterator it1 = it; it1-- != map.constBegin();) { const KexiMatchData& value = it1.value(); if (value.isValid()) { if (order == Qt::AscendingOrder) { from = value.indices.last() + 1; } else { to = value.indices.first() - 1; } break; } } // look forward for first valid hint for(CacheItem::const_iterator it2 = it; it2 != map.constEnd(); ++it2) { const KexiMatchData& value = it2.value(); if (value.isValid() && !it2.key().startsWith(part)) { if (order == Qt::AscendingOrder) { to = value.indices.first() - 1; } else { from = value.indices.first() + 1; } break; } } return KexiIndexMapper(from, to); } Qt::SortOrder QSortedModelEngine::sortOrder(const QModelIndex &parent) const { const QAbstractItemModel *model = c->proxy->sourceModel(); int rowCount = model->rowCount(parent); if (rowCount < 2) return Qt::AscendingOrder; QString first = model->data(model->index(0, c->column, parent), c->role).toString(); QString last = model->data(model->index(rowCount - 1, c->column, parent), c->role).toString(); return QString::compare(first, last, c->cs) <= 0 ? Qt::AscendingOrder : Qt::DescendingOrder; } KexiMatchData QSortedModelEngine::filter(const QString& part, const QModelIndex& parent, int) { const QAbstractItemModel *model = c->proxy->sourceModel(); KexiMatchData hint; if (lookupCache(part, parent, &hint)) return hint; KexiIndexMapper indices; Qt::SortOrder order = sortOrder(parent); if (matchHint(part, parent, &hint)) { if (!hint.isValid()) return KexiMatchData(); indices = hint.indices; } else { indices = indexHint(part, parent, order); } // binary search the model within 'indices' for 'part' under 'parent' int high = indices.to() + 1; int low = indices.from() - 1; int probe; QModelIndex probeIndex; QString probeData; while (high - low > 1) { probe = (high + low) / 2; probeIndex = model->index(probe, c->column, parent); probeData = model->data(probeIndex, c->role).toString(); const int cmp = QString::compare(probeData, part, c->cs); if ((order == Qt::AscendingOrder && cmp >= 0) || (order == Qt::DescendingOrder && cmp < 0)) { high = probe; } else { low = probe; } } if ((order == Qt::AscendingOrder && low == indices.to()) || (order == Qt::DescendingOrder && high == indices.from())) { // not found saveInCache(part, parent, KexiMatchData()); return KexiMatchData(); } probeIndex = model->index(order == Qt::AscendingOrder ? low+1 : high-1, c->column, parent); probeData = model->data(probeIndex, c->role).toString(); if (!probeData.startsWith(part, c->cs)) { saveInCache(part, parent, KexiMatchData()); return KexiMatchData(); } const bool exactMatch = QString::compare(probeData, part, c->cs) == 0; int emi = exactMatch ? (order == Qt::AscendingOrder ? low+1 : high-1) : -1; int from = 0; int to = 0; if (order == Qt::AscendingOrder) { from = low + 1; high = indices.to() + 1; low = from; } else { to = high - 1; low = indices.from() - 1; high = to; } while (high - low > 1) { probe = (high + low) / 2; probeIndex = model->index(probe, c->column, parent); probeData = model->data(probeIndex, c->role).toString(); const bool startsWith = probeData.startsWith(part, c->cs); if ((order == Qt::AscendingOrder && startsWith) || (order == Qt::DescendingOrder && !startsWith)) { low = probe; } else { high = probe; } } KexiMatchData m(order == Qt::AscendingOrder ? KexiIndexMapper(from, high - 1) : KexiIndexMapper(low+1, to), emi, false); saveInCache(part, parent, m); return m; } //////////////////////////////////////////////////////////////////////////////////////// int QUnsortedModelEngine::buildIndices(const QString& str, const QModelIndex& parent, int n, const KexiIndexMapper& indices, KexiMatchData* m) { Q_ASSERT(m->partial); Q_ASSERT(n != -1 || m->exactMatchIndex == -1); const QAbstractItemModel *model = c->proxy->sourceModel(); int i, count = 0; MatchFunction matchFunction = c->substringCompletion ? &matchSubstring : &matchPrefix; for (i = 0; i < indices.count() && count != n; ++i) { QModelIndex idx = model->index(indices[i], c->column, parent); QString data = model->data(idx, c->role).toString(); if (!matchFunction(data, str, c->cs) || !(model->flags(idx) & Qt::ItemIsSelectable)) continue; m->indices.append(indices[i]); ++count; if (m->exactMatchIndex == -1 && QString::compare(data, str, c->cs) == 0) { m->exactMatchIndex = indices[i]; if (n == -1) return indices[i]; } } return indices[i-1]; } void QUnsortedModelEngine::filterOnDemand(int n) { Q_ASSERT(matchCount()); if (!curMatch.partial) return; Q_ASSERT(n >= -1); const QAbstractItemModel *model = c->proxy->sourceModel(); int lastRow = model->rowCount(curParent) - 1; KexiIndexMapper im(curMatch.indices.last() + 1, lastRow); int lastIndex = buildIndices(curParts.last(), curParent, n, im, &curMatch); curMatch.partial = (lastRow != lastIndex); saveInCache(curParts.last(), curParent, curMatch); } KexiMatchData QUnsortedModelEngine::filter(const QString& part, const QModelIndex& parent, int n) { KexiMatchData hint; QVector v; KexiIndexMapper im(v); KexiMatchData m(im, -1, true); const QAbstractItemModel *model = c->proxy->sourceModel(); bool foundInCache = lookupCache(part, parent, &m); if (!foundInCache) { if (matchHint(part, parent, &hint) && !hint.isValid()) return KexiMatchData(); } if (!foundInCache && !hint.isValid()) { const int lastRow = model->rowCount(parent) - 1; KexiIndexMapper all(0, lastRow); int lastIndex = buildIndices(part, parent, n, all, &m); m.partial = (lastIndex != lastRow); } else { if (!foundInCache) { // build from hint as much as we can buildIndices(part, parent, INT_MAX, hint.indices, &m); m.partial = hint.partial; } if (m.partial && ((n == -1 && m.exactMatchIndex == -1) || (m.indices.count() < n))) { // need more and have more const int lastRow = model->rowCount(parent) - 1; KexiIndexMapper rest(hint.indices.last() + 1, lastRow); int want = n == -1 ? -1 : n - m.indices.count(); int lastIndex = buildIndices(part, parent, want, rest, &m); m.partial = (lastRow != lastIndex); } } saveInCache(part, parent, m); return m; } /////////////////////////////////////////////////////////////////////////////// KexiCompleterPrivate::KexiCompleterPrivate(KexiCompleter *qq) : widget(0), proxy(0), popup(0), cs(Qt::CaseSensitive), substringCompletion(false), role(Qt::EditRole), column(0), maxVisibleItems(7), sorting(KexiCompleter::UnsortedModel), wrap(true), eatFocusOut(true), hiddenBecauseNoMatch(false), q(qq) { } void KexiCompleterPrivate::init(QAbstractItemModel *m) { proxy = new KexiCompletionModel(this, q); QObject::connect(proxy, SIGNAL(rowsAdded()), q, SLOT(_q_autoResizePopup())); q->setModel(m); #ifdef QT_NO_LISTVIEW q->setCompletionMode(KexiCompleter::InlineCompletion); #else q->setCompletionMode(KexiCompleter::PopupCompletion); #endif // QT_NO_LISTVIEW } void KexiCompleterPrivate::setCurrentIndex(QModelIndex index, bool select) { if (!q->popup()) return; if (!select) { popup->selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate); } else { if (!index.isValid()) popup->selectionModel()->clear(); else popup->selectionModel()->setCurrentIndex(index, QItemSelectionModel::Select | QItemSelectionModel::Rows); } index = popup->selectionModel()->currentIndex(); if (!index.isValid()) popup->scrollToTop(); else popup->scrollTo(index, QAbstractItemView::PositionAtTop); } void KexiCompleterPrivate::_q_completionSelected(const QItemSelection& selection) { QModelIndex index; if (!selection.indexes().isEmpty()) index = selection.indexes().first(); _q_complete(index, true); } void KexiCompleterPrivate::_q_complete(QModelIndex index, bool highlighted) { QString completion; if (!index.isValid() || (!proxy->showAll && (index.row() >= proxy->engine->matchCount()))) { completion = prefix; } else { if (!(index.flags() & Qt::ItemIsEnabled)) return; QModelIndex si = proxy->mapToSource(index); si = si.sibling(si.row(), column); // for clicked() completion = q->pathFromIndex(si); #ifndef QT_NO_DIRMODEL // add a trailing separator in inline if (mode == KexiCompleter::InlineCompletion) { if (qobject_cast(proxy->sourceModel()) && QFileInfo(completion).isDir()) completion += QDir::separator(); } #endif #ifndef QT_NO_FILESYSTEMMODEL // add a trailing separator in inline if (mode == KexiCompleter::InlineCompletion) { if (qobject_cast(proxy->sourceModel()) && QFileInfo(completion).isDir()) completion += QDir::separator(); } #endif } if (highlighted) { emit q->highlighted(index); emit q->highlighted(completion); } else { emit q->activated(index); emit q->activated(completion); } } void KexiCompleterPrivate::_q_autoResizePopup() { if (!popup || !popup->isVisible()) return; showPopup(popupRect); } static void adjustPopupGeometry(QWidget *popupWidget, QWidget *widget, int widthHint, int heightHint, const QRect ¤tRect) { const QRect screen = QApplication::desktop()->availableGeometry(widget); const Qt::LayoutDirection dir = widget->layoutDirection(); QPoint pos; int rh, w; int h = heightHint; if (currentRect.isValid()) { rh = currentRect.height(); w = currentRect.width(); pos = widget->mapToGlobal(dir == Qt::RightToLeft ? currentRect.bottomRight() : currentRect.bottomLeft()); } else { rh = widget->height(); pos = widget->mapToGlobal(QPoint(0, widget->height() - 2)); w = widget->width(); } if (widthHint > w) { w = widthHint; } if (w > screen.width()) w = screen.width(); if ((pos.x() + w) > (screen.x() + screen.width())) pos.setX(screen.x() + screen.width() - w); if (pos.x() < screen.x()) pos.setX(screen.x()); int top = pos.y() - rh - screen.top() + 2; int bottom = screen.bottom() - pos.y(); h = qMax(h, popupWidget->minimumHeight()); if (h > bottom) { h = qMin(qMax(top, bottom), h); if (top > bottom) pos.setY(pos.y() - h - rh + 2); } popupWidget->setGeometry(pos.x(), pos.y(), w, h); } void KexiCompleterPrivate::showPopup(const QRect& rect) { int widthHint = popup->sizeHintForColumn(0); QScrollBar *vsb = popup->verticalScrollBar(); if (vsb) { widthHint += vsb->sizeHint().width() + 3 + 3; } int heightHint = (popup->sizeHintForRow(0) * qMin(maxVisibleItems, popup->model()->rowCount()) + 3) + 3; QScrollBar *hsb = popup->horizontalScrollBar(); if (hsb && hsb->isVisible()) { heightHint += hsb->sizeHint().height(); } adjustPopupGeometry(popup, widget, widthHint, heightHint, rect); if (!popup->isVisible()) popup->show(); } void KexiCompleterPrivate::_q_fileSystemModelDirectoryLoaded(const QString &path) { // Slot called when QFileSystemModel has finished loading. // If we hide the popup because there was no match because the model was not loaded yet, // we re-start the completion when we get the results if (hiddenBecauseNoMatch && prefix.startsWith(path) && prefix != (path + QLatin1Char('/')) && widget) { q->complete(); } } /*! Constructs a completer object with the given \a parent. */ KexiCompleter::KexiCompleter(QObject *parent) : QObject(parent), d(new KexiCompleterPrivate(this)) { d->init(); } /*! Constructs a completer object with the given \a parent that provides completions from the specified \a model. */ KexiCompleter::KexiCompleter(QAbstractItemModel *model, QObject *parent) : QObject(parent), d(new KexiCompleterPrivate(this)) { d->init(model); } #ifndef QT_NO_STRINGLISTMODEL /*! Constructs a KexiCompleter object with the given \a parent that uses the specified \a list as a source of possible completions. */ KexiCompleter::KexiCompleter(const QStringList& list, QObject *parent) : QObject(parent), d(new KexiCompleterPrivate(this)) { d->init(new QStringListModel(list, this)); } #endif // QT_NO_STRINGLISTMODEL /*! Destroys the completer object. */ KexiCompleter::~KexiCompleter() { delete d; } /*! Sets the widget for which completion are provided for to \a widget. This function is automatically called when a KexiCompleter is set on a QLineEdit using QLineEdit::setCompleter() or on a QComboBox using QComboBox::setCompleter(). The widget needs to be set explicitly when providing completions for custom widgets. \sa widget(), setModel(), setPopup() */ void KexiCompleter::setWidget(QWidget *widget) { if (widget && d->widget == widget) return; if (d->widget) d->widget->removeEventFilter(this); d->widget = widget; if (d->widget) d->widget->installEventFilter(this); if (d->popup) { d->popup->hide(); d->popup->setFocusProxy(d->widget); } } /*! Returns the widget for which the completer object is providing completions. \sa setWidget() */ QWidget *KexiCompleter::widget() const { return d->widget; } /*! Sets the model which provides completions to \a model. The \a model can be list model or a tree model. If a model has been already previously set and it has the KexiCompleter as its parent, it is deleted. For convenience, if \a model is a QFileSystemModel, KexiCompleter switches its caseSensitivity to Qt::CaseInsensitive on Windows and Qt::CaseSensitive on other platforms. \sa completionModel(), modelSorting, {Handling Tree Models} */ void KexiCompleter::setModel(QAbstractItemModel *model) { QAbstractItemModel *oldModel = d->proxy->sourceModel(); d->proxy->setSourceModel(model); if (d->popup) setPopup(d->popup); // set the model and make new connections if (oldModel && oldModel->QObject::parent() == this) delete oldModel; #ifndef QT_NO_DIRMODEL if (qobject_cast(model)) { #if (defined(Q_OS_WIN) && !defined(Q_OS_WINCE)) || defined(Q_OS_SYMBIAN) setCaseSensitivity(Qt::CaseInsensitive); #else setCaseSensitivity(Qt::CaseSensitive); #endif } #endif // QT_NO_DIRMODEL #ifndef QT_NO_FILESYSTEMMODEL QFileSystemModel *fsModel = qobject_cast(model); if (fsModel) { #if (defined(Q_OS_WIN) && !defined(Q_OS_WINCE)) || defined(Q_OS_SYMBIAN) setCaseSensitivity(Qt::CaseInsensitive); #else setCaseSensitivity(Qt::CaseSensitive); #endif setCompletionRole(QFileSystemModel::FileNameRole); connect(fsModel, SIGNAL(directoryLoaded(QString)), this, SLOT(_q_fileSystemModelDirectoryLoaded(QString))); } #endif // QT_NO_FILESYSTEMMODEL } /*! Returns the model that provides completion strings. \sa completionModel() */ QAbstractItemModel *KexiCompleter::model() const { return d->proxy->sourceModel(); } /*! \enum KexiCompleter::CompletionMode This enum specifies how completions are provided to the user. \value PopupCompletion Current completions are displayed in a popup window. \value InlineCompletion Completions appear inline (as selected text). \value UnfilteredPopupCompletion All possible completions are displayed in a popup window with the most likely suggestion indicated as current. \sa setCompletionMode() */ /*! \property KexiCompleter::completionMode \brief how the completions are provided to the user The default value is KexiCompleter::PopupCompletion. */ void KexiCompleter::setCompletionMode(KexiCompleter::CompletionMode mode) { d->mode = mode; d->proxy->setFiltered(mode != KexiCompleter::UnfilteredPopupCompletion); if (mode == KexiCompleter::InlineCompletion) { if (d->widget) d->widget->removeEventFilter(this); if (d->popup) { d->popup->deleteLater(); d->popup = 0; } } else { if (d->widget) d->widget->installEventFilter(this); } } KexiCompleter::CompletionMode KexiCompleter::completionMode() const { return d->mode; } /*! Sets the popup used to display completions to \a popup. KexiCompleter takes ownership of the view. A QListView is automatically created when the completionMode() is set to KexiCompleter::PopupCompletion or KexiCompleter::UnfilteredPopupCompletion. The default popup displays the completionColumn(). Ensure that this function is called before the view settings are modified. This is required since view's properties may require that a model has been set on the view (for example, hiding columns in the view requires a model to be set on the view). \sa popup() */ void KexiCompleter::setPopup(QAbstractItemView *popup) { Q_ASSERT(popup != 0); if (d->popup) { QObject::disconnect(d->popup->selectionModel(), 0, this, 0); QObject::disconnect(d->popup, 0, this, 0); } if (d->popup != popup) delete d->popup; if (popup->model() != d->proxy) popup->setModel(d->proxy); #if defined(Q_OS_MACOS) && !defined(QT_MAC_USE_COCOA) popup->show(); #else popup->hide(); #endif Qt::FocusPolicy origPolicy = Qt::NoFocus; if (d->widget) origPolicy = d->widget->focusPolicy(); popup->setParent(0, Qt::Popup); popup->setFocusPolicy(Qt::NoFocus); if (d->widget) d->widget->setFocusPolicy(origPolicy); popup->setFocusProxy(d->widget); popup->installEventFilter(this); popup->setItemDelegate(new KexiCompleterItemDelegate(popup)); #ifndef QT_NO_LISTVIEW if (QListView *listView = qobject_cast(popup)) { listView->setModelColumn(d->column); } #endif QObject::connect(popup, SIGNAL(clicked(QModelIndex)), this, SLOT(_q_complete(QModelIndex))); QObject::connect(this, SIGNAL(activated(QModelIndex)), popup, SLOT(hide())); QObject::connect(popup->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(_q_completionSelected(QItemSelection))); d->popup = popup; } /*! Returns the popup used to display completions. \sa setPopup() */ QAbstractItemView *KexiCompleter::popup() const { #ifndef QT_NO_LISTVIEW if (!d->popup && completionMode() != KexiCompleter::InlineCompletion) { QListView *listView = new QListView; listView->setEditTriggers(QAbstractItemView::NoEditTriggers); listView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); listView->setSelectionBehavior(QAbstractItemView::SelectRows); listView->setSelectionMode(QAbstractItemView::SingleSelection); listView->setModelColumn(d->column); KexiCompleter *that = const_cast(this); that->setPopup(listView); } #endif // QT_NO_LISTVIEW return d->popup; } /*! \reimp */ bool KexiCompleter::event(QEvent *ev) { return QObject::event(ev); } /*! \reimp */ bool KexiCompleter::eventFilter(QObject *o, QEvent *e) { if (d->eatFocusOut && o == d->widget && e->type() == QEvent::FocusOut) { d->hiddenBecauseNoMatch = false; if (d->popup && d->popup->isVisible()) return true; } if (o != d->popup) return QObject::eventFilter(o, e); switch (e->type()) { case QEvent::KeyPress: { QKeyEvent *ke = static_cast(e); QModelIndex curIndex = d->popup->currentIndex(); QModelIndexList selList = d->popup->selectionModel()->selectedIndexes(); const int key = ke->key(); // In UnFilteredPopup mode, select the current item if ((key == Qt::Key_Up || key == Qt::Key_Down) && selList.isEmpty() && curIndex.isValid() && d->mode == KexiCompleter::UnfilteredPopupCompletion) { d->setCurrentIndex(curIndex); return true; } // Handle popup navigation keys. These are hardcoded because up/down might make the // widget do something else (lineedit cursor moves to home/end on mac, for instance) switch (key) { case Qt::Key_End: case Qt::Key_Home: if (ke->modifiers() & Qt::ControlModifier) return false; break; case Qt::Key_Up: if (!curIndex.isValid()) { int rowCount = d->proxy->rowCount(); QModelIndex lastIndex = d->proxy->index(rowCount - 1, d->column); d->setCurrentIndex(lastIndex); return true; } else if (curIndex.row() == 0) { if (d->wrap) d->setCurrentIndex(QModelIndex()); return true; } return false; case Qt::Key_Down: if (!curIndex.isValid()) { QModelIndex firstIndex = d->proxy->index(0, d->column); d->setCurrentIndex(firstIndex); return true; } else if (curIndex.row() == d->proxy->rowCount() - 1) { if (d->wrap) d->setCurrentIndex(QModelIndex()); return true; } return false; case Qt::Key_PageUp: case Qt::Key_PageDown: return false; } // Send the event to the widget. If the widget accepted the event, do nothing // If the widget did not accept the event, provide a default implementation d->eatFocusOut = false; (static_cast(d->widget))->event(ke); d->eatFocusOut = true; if (!d->widget || e->isAccepted() || !d->popup->isVisible()) { // widget lost focus, hide the popup if (d->widget && (!d->widget->hasFocus() #ifdef QT_KEYPAD_NAVIGATION || (QApplication::keypadNavigationEnabled() && !d->widget->hasEditFocus()) #endif )) d->popup->hide(); if (e->isAccepted()) return true; } // default implementation for keys not handled by the widget when popup is open switch (key) { #ifdef QT_KEYPAD_NAVIGATION case Qt::Key_Select: if (!QApplication::keypadNavigationEnabled()) break; #endif case Qt::Key_Return: case Qt::Key_Enter: case Qt::Key_Tab: d->popup->hide(); if (curIndex.isValid()) d->_q_complete(curIndex); break; case Qt::Key_F4: if (ke->modifiers() & Qt::AltModifier) d->popup->hide(); break; case Qt::Key_Backtab: case Qt::Key_Escape: d->popup->hide(); break; default: break; } return true; } #ifdef QT_KEYPAD_NAVIGATION case QEvent::KeyRelease: { QKeyEvent *ke = static_cast(e); if (QApplication::keypadNavigationEnabled() && ke->key() == Qt::Key_Back) { // Send the event to the 'widget'. This is what we did for KeyPress, so we need // to do the same for KeyRelease, in case the widget's KeyPress event set // up something (such as a timer) that is relying on also receiving the // key release. I see this as a bug in Qt, and should really set it up for all // the affected keys. However, it is difficult to tell how this will affect // existing code, and I can't test for every combination! d->eatFocusOut = false; static_cast(d->widget)->event(ke); d->eatFocusOut = true; } break; } #endif case QEvent::MouseButtonPress: { #ifdef QT_KEYPAD_NAVIGATION if (QApplication::keypadNavigationEnabled()) { // if we've clicked in the widget (or its descendant), let it handle the click QWidget *source = qobject_cast(o); if (source) { QPoint pos = source->mapToGlobal((static_cast(e))->pos()); QWidget *target = QApplication::widgetAt(pos); if (target && (d->widget->isAncestorOf(target) || target == d->widget)) { d->eatFocusOut = false; static_cast(target)->event(e); d->eatFocusOut = true; return true; } } } #endif if (!d->popup->underMouse()) { d->popup->hide(); return true; } } return false; case QEvent::InputMethod: case QEvent::ShortcutOverride: QApplication::sendEvent(d->widget, e); break; default: return false; } return false; } /*! For KexiCompleter::PopupCompletion and KexiCompleter::UnfilteredPopupCompletion modes, calling this function displays the popup displaying the current completions. By default, if \a rect is not specified, the popup is displayed on the bottom of the widget(). If \a rect is specified the popup is displayed on the left edge of the rectangle. For KexiCompleter::InlineCompletion mode, the highlighted() signal is fired with the current completion. */ void KexiCompleter::complete(const QRect& rect) { QModelIndex idx = d->proxy->currentIndex(false); d->hiddenBecauseNoMatch = false; if (d->mode == KexiCompleter::InlineCompletion) { if (idx.isValid()) d->_q_complete(idx, true); return; } Q_ASSERT(d->widget != 0); if ((d->mode == KexiCompleter::PopupCompletion && !idx.isValid()) || (d->mode == KexiCompleter::UnfilteredPopupCompletion && d->proxy->rowCount() == 0)) { if (d->popup) d->popup->hide(); // no suggestion, hide d->hiddenBecauseNoMatch = true; return; } popup(); if (d->mode == KexiCompleter::UnfilteredPopupCompletion) d->setCurrentIndex(idx, false); d->showPopup(rect); d->popupRect = rect; } /*! Sets the current row to the \a row specified. Returns true if successful; otherwise returns false. This function may be used along with currentCompletion() to iterate through all the possible completions. \sa currentCompletion(), completionCount() */ bool KexiCompleter::setCurrentRow(int row) { return d->proxy->setCurrentRow(row); } /*! Returns the current row. \sa setCurrentRow() */ int KexiCompleter::currentRow() const { return d->proxy->currentRow(); } /*! Returns the number of completions for the current prefix. For an unsorted model with a large number of items this can be expensive. Use setCurrentRow() and currentCompletion() to iterate through all the completions. */ int KexiCompleter::completionCount() const { return d->proxy->completionCount(); } /*! \enum KexiCompleter::ModelSorting This enum specifies how the items in the model are sorted. \value UnsortedModel The model is unsorted. \value CaseSensitivelySortedModel The model is sorted case sensitively. \value CaseInsensitivelySortedModel The model is sorted case insensitively. \sa setModelSorting() */ /*! \property KexiCompleter::modelSorting \brief the way the model is sorted By default, no assumptions are made about the order of the items in the model that provides the completions. If the model's data for the completionColumn() and completionRole() is sorted in ascending order, you can set this property to \l CaseSensitivelySortedModel or \l CaseInsensitivelySortedModel. On large models, this can lead to significant performance improvements because the completer object can then use a binary search algorithm instead of linear search algorithm. The sort order (i.e ascending or descending order) of the model is determined dynamically by inspecting the contents of the model. \b Note: The performance improvements described above cannot take place when the completer's \l caseSensitivity is different to the case sensitivity used by the model's when sorting. \sa setCaseSensitivity(), KexiCompleter::ModelSorting */ void KexiCompleter::setModelSorting(KexiCompleter::ModelSorting sorting) { if (d->sorting == sorting) return; d->sorting = sorting; d->proxy->createEngine(); d->proxy->invalidate(); } KexiCompleter::ModelSorting KexiCompleter::modelSorting() const { return d->sorting; } /*! \property KexiCompleter::completionColumn \brief the column in the model in which completions are searched for. If the popup() is a QListView, it is automatically setup to display this column. By default, the match column is 0. \sa completionRole, caseSensitivity */ void KexiCompleter::setCompletionColumn(int column) { if (d->column == column) return; #ifndef QT_NO_LISTVIEW if (QListView *listView = qobject_cast(d->popup)) listView->setModelColumn(column); #endif d->column = column; d->proxy->invalidate(); } int KexiCompleter::completionColumn() const { return d->column; } /*! \property KexiCompleter::completionRole \brief the item role to be used to query the contents of items for matching. The default role is Qt::EditRole. \sa completionColumn, caseSensitivity */ void KexiCompleter::setCompletionRole(int role) { if (d->role == role) return; d->role = role; d->proxy->invalidate(); } int KexiCompleter::completionRole() const { return d->role; } /*! \brief the completions wrap around when navigating through items The default is true. */ void KexiCompleter::setWrapAround(bool wrap) { if (d->wrap == wrap) return; d->wrap = wrap; } bool KexiCompleter::wrapAround() const { return d->wrap; } /*! \property KexiCompleter::maxVisibleItems \brief the maximum allowed size on screen of the completer, measured in items By default, this property has a value of 7. */ int KexiCompleter::maxVisibleItems() const { return d->maxVisibleItems; } void KexiCompleter::setMaxVisibleItems(int maxItems) { if (maxItems < 0) { qWarning("KexiCompleter::setMaxVisibleItems: " "Invalid max visible items (%d) must be >= 0", maxItems); return; } d->maxVisibleItems = maxItems; } /*! \property KexiCompleter::caseSensitivity \brief the case sensitivity of the matching The default is Qt::CaseSensitive. \sa substringCompletion, completionColumn, completionRole, modelSorting */ void KexiCompleter::setCaseSensitivity(Qt::CaseSensitivity cs) { if (d->cs == cs) return; d->cs = cs; d->proxy->createEngine(); d->proxy->invalidate(); } Qt::CaseSensitivity KexiCompleter::caseSensitivity() const { return d->cs; } /*! \property KexiCompleter::substringCompletion \brief the completion uses any substring matching If true the completion uses any substring matching. If false prefix matching is used. The default is false. \sa caseSensitivity */ void KexiCompleter::setSubstringCompletion(bool substringCompletion) { if (d->substringCompletion == substringCompletion) return; d->substringCompletion = substringCompletion; d->proxy->invalidate(); } bool KexiCompleter::substringCompletion() const { return d->substringCompletion; } /*! \property KexiCompleter::completionPrefix \brief the completion prefix used to provide completions. The completionModel() is updated to reflect the list of possible matches for \a prefix. */ void KexiCompleter::setCompletionPrefix(const QString &prefix) { d->prefix = prefix; d->proxy->filter(splitPath(prefix)); } QString KexiCompleter::completionPrefix() const { return d->prefix; } /*! Returns the model index of the current completion in the completionModel(). \sa setCurrentRow(), currentCompletion(), model() */ QModelIndex KexiCompleter::currentIndex() const { return d->proxy->currentIndex(false); } /*! Returns the current completion string. This includes the \l completionPrefix. When used alongside setCurrentRow(), it can be used to iterate through all the matches. \sa setCurrentRow(), currentIndex() */ QString KexiCompleter::currentCompletion() const { return pathFromIndex(d->proxy->currentIndex(true)); } /*! Returns the completion model. The completion model is a read-only list model that contains all the possible matches for the current completion prefix. The completion model is auto-updated to reflect the current completions. \note The return value of this function is defined to be an QAbstractItemModel purely for generality. This actual kind of model returned is an instance of an QAbstractProxyModel subclass. \sa completionPrefix, model() */ QAbstractItemModel *KexiCompleter::completionModel() const { return d->proxy; } /*! Returns the path for the given \a index. The completer object uses this to obtain the completion text from the underlying model. The default implementation returns the \l{Qt::EditRole}{edit role} of the item for list models. It returns the absolute file path if the model is a QFileSystemModel. \sa splitPath() */ QString KexiCompleter::pathFromIndex(const QModelIndex& index) const { if (!index.isValid()) return QString(); QAbstractItemModel *sourceModel = d->proxy->sourceModel(); if (!sourceModel) return QString(); bool isDirModel = false; bool isFsModel = false; #ifndef QT_NO_DIRMODEL isDirModel = qobject_cast(d->proxy->sourceModel()) != 0; #endif #ifndef QT_NO_FILESYSTEMMODEL isFsModel = qobject_cast(d->proxy->sourceModel()) != 0; #endif if (!isDirModel && !isFsModel) return sourceModel->data(index, d->role).toString(); QModelIndex idx = index; QStringList list; do { QString t; if (isDirModel) t = sourceModel->data(idx, Qt::EditRole).toString(); #ifndef QT_NO_FILESYSTEMMODEL else t = sourceModel->data(idx, QFileSystemModel::FileNameRole).toString(); #endif list.prepend(t); QModelIndex parent = idx.parent(); idx = parent.sibling(parent.row(), index.column()); } while (idx.isValid()); #if (!defined(Q_OS_WIN) || defined(Q_OS_WINCE)) && !defined(Q_OS_SYMBIAN) if (list.count() == 1) // only the separator or some other text return list[0]; list[0].clear(); // the join below will provide the separator #endif return list.join(QDir::separator()); } /*! Splits the given \a path into strings that are used to match at each level in the model(). The default implementation of splitPath() splits a file system path based on QDir::separator() when the sourceModel() is a QFileSystemModel. When used with list models, the first item in the returned list is used for matching. \sa pathFromIndex(), {Handling Tree Models} */ QStringList KexiCompleter::splitPath(const QString& path) const { bool isDirModel = false; bool isFsModel = false; #ifndef QT_NO_DIRMODEL isDirModel = qobject_cast(d->proxy->sourceModel()) != 0; #endif #ifndef QT_NO_FILESYSTEMMODEL #ifdef QT_NO_DIRMODEL #endif isFsModel = qobject_cast(d->proxy->sourceModel()) != 0; #endif if ((!isDirModel && !isFsModel) || path.isEmpty()) return QStringList(completionPrefix()); QString pathCopy = QDir::toNativeSeparators(path); QString sep = QDir::separator(); #if defined(Q_OS_SYMBIAN) if (pathCopy == QLatin1String("\\")) return QStringList(pathCopy); #elif defined(Q_OS_WIN) && !defined(Q_OS_WINCE) if (pathCopy == QLatin1String("\\") || pathCopy == QLatin1String("\\\\")) return QStringList(pathCopy); QString doubleSlash(QLatin1String("\\\\")); if (pathCopy.startsWith(doubleSlash)) pathCopy.remove(0, 2); else doubleSlash.clear(); #endif QRegularExpression re(QLatin1Char('[') + QRegularExpression::escape(sep) + QLatin1Char(']')); QStringList parts = pathCopy.split(re); #if defined(Q_OS_SYMBIAN) // Do nothing #elif defined(Q_OS_WIN) && !defined(Q_OS_WINCE) if (!doubleSlash.isEmpty()) parts[0].prepend(doubleSlash); #else if (pathCopy[0] == sep[0]) // readd the "/" at the beginning as the split removed it parts[0] = QDir::fromNativeSeparators(QString(sep[0])); #endif return parts; } /*! \fn void KexiCompleter::activated(const QModelIndex& index) This signal is sent when an item in the popup() is activated by the user. (by clicking or pressing return). The item's \a index in the completionModel() is given. */ /*! \fn void KexiCompleter::activated(const QString &text) This signal is sent when an item in the popup() is activated by the user (by clicking or pressing return). The item's \a text is given. */ /*! \fn void KexiCompleter::highlighted(const QModelIndex& index) This signal is sent when an item in the popup() is highlighted by the user. It is also sent if complete() is called with the completionMode() set to KexiCompleter::InlineCompletion. The item's \a index in the completionModel() is given. */ /*! \fn void KexiCompleter::highlighted(const QString &text) This signal is sent when an item in the popup() is highlighted by the user. It is also sent if complete() is called with the completionMode() set to KexiCompleter::InlineCompletion. The item's \a text is given. */ void KexiCompleter::_q_complete(const QModelIndex& index) { d->_q_complete(index); } void KexiCompleter::_q_completionSelected(const QItemSelection& selection) { d->_q_completionSelected(selection); } void KexiCompleter::_q_autoResizePopup() { d->_q_autoResizePopup(); } void KexiCompleter::_q_fileSystemModelDirectoryLoaded(const QString& dir) { d->_q_fileSystemModelDirectoryLoaded(dir); } KexiCompletionModelPrivate::KexiCompletionModelPrivate(KexiCompletionModel *q) : q(q) { } void KexiCompletionModelPrivate::_q_sourceModelDestroyed() { q->setSourceModel(KexiAbstractItemModelPrivate::staticEmptyModel()); } #include "KexiCompleter.moc" #endif // QT_NO_COMPLETER diff --git a/src/kexiutils/completer/KexiCompleter.h b/src/kexiutils/completer/KexiCompleter.h index 5064c2552..41b78467e 100644 --- a/src/kexiutils/completer/KexiCompleter.h +++ b/src/kexiutils/completer/KexiCompleter.h @@ -1,170 +1,170 @@ /**************************************************************************** ** ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of the QtGui module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** GNU Lesser General Public License Usage ** This file may be used under the terms of the GNU Lesser General Public ** License version 2.1 as published by the Free Software Foundation and ** appearing in the file LICENSE.LGPL included in the packaging of this ** file. Please review the following information to ensure the GNU Lesser ** General Public License version 2.1 requirements will be met: -** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU General ** Public License version 3.0 as published by the Free Software Foundation ** and appearing in the file LICENSE.GPL included in the packaging of this ** file. Please review the following information to ensure the GNU General ** Public License version 3.0 requirements will be met: -** http://www.gnu.org/copyleft/gpl.html. +** https://www.gnu.org/copyleft/gpl.html. ** ** Other Usage ** Alternatively, this file may be used in accordance with the terms and ** conditions contained in a signed written agreement between you and Nokia. ** ** ** ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #ifndef KEXICOMPLETER_H #define KEXICOMPLETER_H #include #include #include #include #include #ifndef QT_NO_COMPLETER class QAbstractItemView; class QWidget; class QItemSelection; class KexiCompleterPrivate; class KEXIUTILS_EXPORT KexiCompleter : public QObject { Q_OBJECT Q_PROPERTY(QString completionPrefix READ completionPrefix WRITE setCompletionPrefix) Q_PROPERTY(ModelSorting modelSorting READ modelSorting WRITE setModelSorting) Q_PROPERTY(CompletionMode completionMode READ completionMode WRITE setCompletionMode) Q_PROPERTY(int completionColumn READ completionColumn WRITE setCompletionColumn) Q_PROPERTY(int completionRole READ completionRole WRITE setCompletionRole) Q_PROPERTY(int maxVisibleItems READ maxVisibleItems WRITE setMaxVisibleItems) Q_PROPERTY(Qt::CaseSensitivity caseSensitivity READ caseSensitivity WRITE setCaseSensitivity) Q_PROPERTY(bool substringCompletion READ substringCompletion WRITE setSubstringCompletion) Q_PROPERTY(bool wrapAround READ wrapAround WRITE setWrapAround) public: enum CompletionMode { PopupCompletion, UnfilteredPopupCompletion, InlineCompletion }; enum ModelSorting { UnsortedModel = 0, CaseSensitivelySortedModel, CaseInsensitivelySortedModel }; explicit KexiCompleter(QObject *parent = 0); explicit KexiCompleter(QAbstractItemModel *model, QObject *parent = 0); #ifndef QT_NO_STRINGLISTMODEL explicit KexiCompleter(const QStringList& completions, QObject *parent = 0); #endif ~KexiCompleter(); void setWidget(QWidget *widget); QWidget *widget() const; void setModel(QAbstractItemModel *c); QAbstractItemModel *model() const; void setCompletionMode(CompletionMode mode); CompletionMode completionMode() const; QAbstractItemView *popup() const; void setPopup(QAbstractItemView *popup); void setCaseSensitivity(Qt::CaseSensitivity caseSensitivity); Qt::CaseSensitivity caseSensitivity() const; void setSubstringCompletion(bool substringCompletion); bool substringCompletion() const; void setModelSorting(ModelSorting sorting); ModelSorting modelSorting() const; void setCompletionColumn(int column); int completionColumn() const; void setCompletionRole(int role); int completionRole() const; bool wrapAround() const; int maxVisibleItems() const; void setMaxVisibleItems(int maxItems); int completionCount() const; bool setCurrentRow(int row); int currentRow() const; QModelIndex currentIndex() const; QString currentCompletion() const; QAbstractItemModel *completionModel() const; QString completionPrefix() const; public Q_SLOTS: void setCompletionPrefix(const QString &prefix); void complete(const QRect& rect = QRect()); void setWrapAround(bool wrap); public: virtual QString pathFromIndex(const QModelIndex &index) const; virtual QStringList splitPath(const QString &path) const; protected: bool eventFilter(QObject *o, QEvent *e) override; bool event(QEvent *) override; Q_SIGNALS: void activated(const QString &text); void activated(const QModelIndex &index); void highlighted(const QString &text); void highlighted(const QModelIndex &index); private: Q_DISABLE_COPY(KexiCompleter) friend class KexiCompleterPrivate; KexiCompleterPrivate * const d; private Q_SLOTS: void _q_complete(const QModelIndex&); void _q_completionSelected(const QItemSelection&); void _q_autoResizePopup(); void _q_fileSystemModelDirectoryLoaded(const QString&); }; #endif // QT_NO_COMPLETER #endif // KEXICOMPLETER_H diff --git a/src/kexiutils/completer/KexiCompleter_p.h b/src/kexiutils/completer/KexiCompleter_p.h index ed71af35c..fe402c380 100644 --- a/src/kexiutils/completer/KexiCompleter_p.h +++ b/src/kexiutils/completer/KexiCompleter_p.h @@ -1,267 +1,267 @@ /**************************************************************************** ** ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of the QtGui module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** GNU Lesser General Public License Usage ** This file may be used under the terms of the GNU Lesser General Public ** License version 2.1 as published by the Free Software Foundation and ** appearing in the file LICENSE.LGPL included in the packaging of this ** file. Please review the following information to ensure the GNU Lesser ** General Public License version 2.1 requirements will be met: -** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU General ** Public License version 3.0 as published by the Free Software Foundation ** and appearing in the file LICENSE.GPL included in the packaging of this ** file. Please review the following information to ensure the GNU General ** Public License version 3.0 requirements will be met: -** http://www.gnu.org/copyleft/gpl.html. +** https://www.gnu.org/copyleft/gpl.html. ** ** Other Usage ** Alternatively, this file may be used in accordance with the terms and ** conditions contained in a signed written agreement between you and Nokia. ** ** ** ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #ifndef KEXICOMPLETER_P_H #define KEXICOMPLETER_P_H // // W A R N I N G // ------------- // // This file is not part of the Qt API. It exists purely as an // implementation detail. This header file may change from version to // version without notice, or even be removed. // // We mean it. // #ifndef QT_NO_COMPLETER #include #include #include #include #include #include "KexiCompleter.h" #include "private/KexiAbstractItemModel_p.h" class KexiCompletionModel; class KexiCompleterPrivate { public: explicit KexiCompleterPrivate(KexiCompleter *q); ~KexiCompleterPrivate() { delete popup; } void init(QAbstractItemModel *model = 0); QPointer widget; KexiCompletionModel *proxy; QAbstractItemView *popup; KexiCompleter::CompletionMode mode; QString prefix; Qt::CaseSensitivity cs; bool substringCompletion; int role; int column; int maxVisibleItems; KexiCompleter::ModelSorting sorting; bool wrap; bool eatFocusOut; QRect popupRect; bool hiddenBecauseNoMatch; void showPopup(const QRect&); void _q_complete(QModelIndex, bool = false); void _q_completionSelected(const QItemSelection&); void _q_autoResizePopup(); void _q_fileSystemModelDirectoryLoaded(const QString &path); void setCurrentIndex(QModelIndex, bool = true); KexiCompleter * const q; }; class KexiIndexMapper { public: KexiIndexMapper() : v(false), f(0), t(-1) { } KexiIndexMapper(int f, int t) : v(false), f(f), t(t) { } explicit KexiIndexMapper(QVector vec) : v(true), vector(vec), f(-1), t(-1) { } inline int count() const { return v ? vector.count() : t - f + 1; } inline int operator[] (int index) const { return v ? vector[index] : f + index; } inline int indexOf(int x) const { return v ? vector.indexOf(x) : ((t < f) ? -1 : x - f); } inline bool isValid() const { return !isEmpty(); } inline bool isEmpty() const { return v ? vector.isEmpty() : (t < f); } inline void append(int x) { Q_ASSERT(v); vector.append(x); } inline int first() const { return v ? vector.first() : f; } inline int last() const { return v ? vector.last() : t; } inline int from() const { Q_ASSERT(!v); return f; } inline int to() const { Q_ASSERT(!v); return t; } inline int cost() const { return vector.count()+2; } private: bool v; QVector vector; int f, t; }; struct KexiMatchData { KexiMatchData() : exactMatchIndex(-1), partial(false) { } KexiMatchData(const KexiIndexMapper& indices, int em, bool p) : indices(indices), exactMatchIndex(em), partial(p) { } KexiIndexMapper indices; inline bool isValid() const { return indices.isValid(); } int exactMatchIndex; bool partial; }; class KexiCompletionEngine { public: typedef QMap CacheItem; typedef QMap Cache; explicit KexiCompletionEngine(KexiCompleterPrivate *c) : c(c), curRow(-1), cost(0) { } virtual ~KexiCompletionEngine() { } void filter(const QStringList &parts); KexiMatchData filterHistory(); bool matchHint(QString, const QModelIndex&, KexiMatchData*); void saveInCache(QString, const QModelIndex&, const KexiMatchData&); bool lookupCache(QString part, const QModelIndex& parent, KexiMatchData *m); virtual void filterOnDemand(int) { } virtual KexiMatchData filter(const QString&, const QModelIndex&, int) = 0; int matchCount() const { return curMatch.indices.count() + historyMatch.indices.count(); } KexiMatchData curMatch, historyMatch; KexiCompleterPrivate *c; QStringList curParts; QModelIndex curParent; int curRow; Cache cache; int cost; }; class QSortedModelEngine : public KexiCompletionEngine { public: explicit QSortedModelEngine(KexiCompleterPrivate *c) : KexiCompletionEngine(c) { } KexiMatchData filter(const QString&, const QModelIndex&, int) override; KexiIndexMapper indexHint(QString, const QModelIndex&, Qt::SortOrder); Qt::SortOrder sortOrder(const QModelIndex&) const; }; class QUnsortedModelEngine : public KexiCompletionEngine { public: explicit QUnsortedModelEngine(KexiCompleterPrivate *c) : KexiCompletionEngine(c) { } void filterOnDemand(int) override; KexiMatchData filter(const QString&, const QModelIndex&, int) override; private: int buildIndices(const QString& str, const QModelIndex& parent, int n, const KexiIndexMapper& iv, KexiMatchData* m); }; class KexiCompleterItemDelegate : public QItemDelegate { Q_OBJECT public: explicit KexiCompleterItemDelegate(QAbstractItemView *view) : QItemDelegate(view), view(view) { } void paint(QPainter *p, const QStyleOptionViewItem& opt, const QModelIndex& idx) const override { QStyleOptionViewItem optCopy = opt; optCopy.showDecorationSelected = true; if (view->currentIndex() == idx) optCopy.state |= QStyle::State_HasFocus; QItemDelegate::paint(p, optCopy, idx); } private: QAbstractItemView *view; }; class KexiCompletionModelPrivate; class KexiCompletionModel : public QAbstractProxyModel { Q_OBJECT public: KexiCompletionModel(KexiCompleterPrivate *c, QObject *parent); ~KexiCompletionModel(); void createEngine(); void setFiltered(bool); void filter(const QStringList& parts); int completionCount() const; int currentRow() const { return engine->curRow; } bool setCurrentRow(int row); QModelIndex currentIndex(bool) const; void resetModel(); QModelIndex index(int row, int column, const QModelIndex & = QModelIndex()) const override; int rowCount(const QModelIndex &index = QModelIndex()) const override; int columnCount(const QModelIndex &index = QModelIndex()) const override; bool hasChildren(const QModelIndex &parent = QModelIndex()) const override; QModelIndex parent(const QModelIndex & = QModelIndex()) const override { return QModelIndex(); } QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; void setSourceModel(QAbstractItemModel *sourceModel) override; QModelIndex mapToSource(const QModelIndex& proxyIndex) const override; QModelIndex mapFromSource(const QModelIndex& sourceIndex) const override; KexiCompleterPrivate *c; QScopedPointer engine; bool showAll; Q_SIGNALS: void rowsAdded(); public Q_SLOTS: void invalidate(); void rowsInserted(); void modelDestroyed(); private: KexiCompletionModelPrivate * const d; }; class KexiCompletionModelPrivate : public KexiAbstractItemModelPrivate { explicit KexiCompletionModelPrivate(KexiCompletionModel *q); virtual ~KexiCompletionModelPrivate() { } virtual void _q_sourceModelDestroyed(); KexiCompletionModel * const q; friend class KexiCompletionModel; }; #endif // QT_NO_COMPLETER #endif // KEXICOMPLETER_P_H diff --git a/src/kexiutils/completer/private/KexiAbstractItemModel_p.h b/src/kexiutils/completer/private/KexiAbstractItemModel_p.h index 578e5555b..f6520b2cf 100644 --- a/src/kexiutils/completer/private/KexiAbstractItemModel_p.h +++ b/src/kexiutils/completer/private/KexiAbstractItemModel_p.h @@ -1,169 +1,169 @@ /**************************************************************************** ** ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of the QtCore module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** GNU Lesser General Public License Usage ** This file may be used under the terms of the GNU Lesser General Public ** License version 2.1 as published by the Free Software Foundation and ** appearing in the file LICENSE.LGPL included in the packaging of this ** file. Please review the following information to ensure the GNU Lesser ** General Public License version 2.1 requirements will be met: -** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU General ** Public License version 3.0 as published by the Free Software Foundation ** and appearing in the file LICENSE.GPL included in the packaging of this ** file. Please review the following information to ensure the GNU General ** Public License version 3.0 requirements will be met: -** http://www.gnu.org/copyleft/gpl.html. +** https://www.gnu.org/copyleft/gpl.html. ** ** Other Usage ** Alternatively, this file may be used in accordance with the terms and ** conditions contained in a signed written agreement between you and Nokia. ** ** ** ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #ifndef KEXIABSTRACTITEMMODEL_P_H #define KEXIABSTRACTITEMMODEL_P_H // // W A R N I N G // ------------- // // This file is not part of the Qt API. It exists for the convenience // of QAbstractItemModel*. This header file may change from version // to version without notice, or even be removed. // // We mean it. // // #include #include class QPersistentModelIndexData { public: QPersistentModelIndexData() : model(0) {} QPersistentModelIndexData(const QModelIndex &idx) : index(idx), model(idx.model()) {} QModelIndex index; QAtomicInt ref; const QAbstractItemModel *model; static QPersistentModelIndexData *create(const QModelIndex &index); static void destroy(QPersistentModelIndexData *data); }; class KexiAbstractItemModelPrivate { public: KexiAbstractItemModelPrivate() : supportedDragActions(-1), roleNames(defaultRoleNames()) {} void removePersistentIndexData(QPersistentModelIndexData *data); void movePersistentIndexes(QVector indexes, int change, const QModelIndex &parent, Qt::Orientation orientation); void rowsAboutToBeInserted(const QModelIndex &parent, int first, int last); void rowsInserted(const QModelIndex &parent, int first, int last); void rowsAboutToBeRemoved(const QModelIndex &parent, int first, int last); void rowsRemoved(const QModelIndex &parent, int first, int last); void columnsAboutToBeInserted(const QModelIndex &parent, int first, int last); void columnsInserted(const QModelIndex &parent, int first, int last); void columnsAboutToBeRemoved(const QModelIndex &parent, int first, int last); void columnsRemoved(const QModelIndex &parent, int first, int last); static QAbstractItemModel *staticEmptyModel(); static bool variantLessThan(const QVariant &v1, const QVariant &v2); void itemsAboutToBeMoved(const QModelIndex &srcParent, int srcFirst, int srcLast, const QModelIndex &destinationParent, int destinationChild, Qt::Orientation); void itemsMoved(const QModelIndex &srcParent, int srcFirst, int srcLast, const QModelIndex &destinationParent, int destinationChild, Qt::Orientation orientation); bool allowMove(const QModelIndex &srcParent, int srcFirst, int srcLast, const QModelIndex &destinationParent, int destinationChild, Qt::Orientation orientation); #if 0 inline QModelIndex createIndex(int row, int column, void *data = 0) const { return q_func()->createIndex(row, column, data); } inline QModelIndex createIndex(int row, int column, int id) const { return q_func()->createIndex(row, column, id); } inline bool indexValid(const QModelIndex &index) const { return (index.row() >= 0) && (index.column() >= 0) && (index.model() == q_func()); } #endif inline void invalidatePersistentIndexes() { foreach (QPersistentModelIndexData *data, persistent.indexes) { data->index = QModelIndex(); data->model = 0; } persistent.indexes.clear(); } /*! \internal clean the QPersistentModelIndex relative to the index if there is one. To be used before an index is invalided */ inline void invalidatePersistentIndex(const QModelIndex &index) { QHash::iterator it = persistent.indexes.find(index); if(it != persistent.indexes.end()) { QPersistentModelIndexData *data = *it; persistent.indexes.erase(it); data->index = QModelIndex(); data->model = 0; } } struct Change { Change() : first(-1), last(-1), needsAdjust(false) {} Change(const Change &c) : parent(c.parent), first(c.first), last(c.last), needsAdjust(c.needsAdjust) {} Change(const QModelIndex &p, int f, int l) : parent(p), first(f), last(l), needsAdjust(false) {} QModelIndex parent; int first, last; // In cases such as this: // - A // - B // - C // - - D // - - E // - - F // // If B is moved to above E, C is the source parent in the signal and its row is 2. When the move is // completed however, C is at row 1 and there is no row 2 at the same level in the model at all. // The QModelIndex is adjusted to correct that in those cases before reporting it though the // rowsMoved signal. bool needsAdjust; bool isValid() { return first >= 0 && last >= 0; } }; QStack changes; struct Persistent { Persistent() {} QHash indexes; QStack > moved; QStack > invalidated; void insertMultiAtEnd(const QModelIndex& key, QPersistentModelIndexData *data); } persistent; Qt::DropActions supportedDragActions; QHash roleNames; static const QHash &defaultRoleNames(); }; #endif // KEXIABSTRACTITEMMODEL_P_H diff --git a/src/kexiutils/completer/private/KexiAbstractProxyModel_p.h b/src/kexiutils/completer/private/KexiAbstractProxyModel_p.h index 89bc5e438..9b3d1f7cc 100644 --- a/src/kexiutils/completer/private/KexiAbstractProxyModel_p.h +++ b/src/kexiutils/completer/private/KexiAbstractProxyModel_p.h @@ -1,71 +1,71 @@ /**************************************************************************** ** ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of the QtGui module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** GNU Lesser General Public License Usage ** This file may be used under the terms of the GNU Lesser General Public ** License version 2.1 as published by the Free Software Foundation and ** appearing in the file LICENSE.LGPL included in the packaging of this ** file. Please review the following information to ensure the GNU Lesser ** General Public License version 2.1 requirements will be met: -** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU General ** Public License version 3.0 as published by the Free Software Foundation ** and appearing in the file LICENSE.GPL included in the packaging of this ** file. Please review the following information to ensure the GNU General ** Public License version 3.0 requirements will be met: -** http://www.gnu.org/copyleft/gpl.html. +** https://www.gnu.org/copyleft/gpl.html. ** ** Other Usage ** Alternatively, this file may be used in accordance with the terms and ** conditions contained in a signed written agreement between you and Nokia. ** ** ** ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #ifndef KEXIABSTRACTPROXYMODEL_P_H #define KEXIABSTRACTPROXYMODEL_P_H // // W A R N I N G // ------------- // // This file is not part of the Qt API. It exists for the convenience // of QAbstractItemModel*. This header file may change from version // to version without notice, or even be removed. // // We mean it. // // #include "private/KexiAbstractItemModel_p.h" #ifndef QT_NO_PROXYMODEL class KexiAbstractProxyModelPrivate : public KexiAbstractItemModelPrivate { public: KexiAbstractProxyModelPrivate() : KexiAbstractItemModelPrivate()/*, model(0)*/ {} //QAbstractItemModel *model; virtual void _q_sourceModelDestroyed(); }; #endif // QT_NO_PROXYMODEL #endif // KEXIABSTRACTPROXYMODEL_P_H diff --git a/src/main/KexiMainWindow.cpp b/src/main/KexiMainWindow.cpp index 3d6802e6b..20d0562fd 100644 --- a/src/main/KexiMainWindow.cpp +++ b/src/main/KexiMainWindow.cpp @@ -1,4619 +1,4619 @@ /* This file is part of the KDE project Copyright (C) 2003 Lucijan Busch Copyright (C) 2003-2018 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 "KexiObjectViewWidget.h" #include "KexiObjectViewTabWidget.h" #include #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 #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 #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) 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(); } //------------------------------------------------- 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) || !KexiStyle::setupApplication(&errorMessage)) { 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) : QMainWindow(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(); setupMainMenu(); 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 { if (!d->objectViewWidget || !d->objectViewWidget->tabWidget()) { return 0; } return windowForTab(d->objectViewWidget->tabWidget()->currentIndex()); } KexiWindow* KexiMainWindow::windowForTab(int tabIndex) const { if (!d->objectViewWidget || !d->objectViewWidget->tabWidget()) return 0; return d->objectViewWidget->tabWidget()->window(tabIndex); } 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)); action->setShortcutContext(Qt::ApplicationShortcut); //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)); action->setText(xi18n("&New Project...")); 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->setText(xi18n("&Open Project...")); action->setIcon(koIcon("project-open")); 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_open_recent", action = KStandardAction::openRecent(this, SLOT(slotProjectOpen()), this)); action->setToolTip(xi18n("Open a project that was recently opened.")); action->setWhatsThis(xi18n("Opens a project that was recently opened.")); { 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("project-development-close"), xi18n("&Close Project"), 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(xi18nc("@info:whatsthis", "Quits %1 application.", QApplication::applicationDisplayName())); setupMainMenuActionShortcut(action); #ifdef KEXI_SHOW_UNIMPLEMENTED d->action_project_relations = addAction("project_relations", KexiIcon("database-relations"), futureI18n("&Relationships...")); 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_SHOW_UNIMPLEMENTED 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())); #else d->action_project_print = d->dummy_action; d->action_project_print_preview = d->dummy_action; d->action_project_print_setup = d->dummy_action; #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)); ac->addAction("edit_replace", d->action_edit_replace = KStandardAction::replace( this, SLOT(slotEditReplace()), this)); d->action_edit_replace_all = addAction("edit_replace_all", xi18n("Replace All")); 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) { ac->addAction("view_navigator", d->action_show_nav = new KToggleAction(xi18n("Show Project Navigator"), this)); d->action_show_nav->setChecked(true); d->action_show_nav->setShortcut(QKeySequence("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(slotToggleProjectNavigator())); } else { d->action_show_nav = 0; } if (d->isProjectNavigatorVisible) { - // Shortcut taken from "Activate Projects pane" http://doc.qt.io/qtcreator/creator-keyboard-shortcuts.html + // Shortcut taken from "Activate Projects pane" https://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) { ac->addAction("view_propeditor", d->action_show_propeditor = new KToggleAction(xi18n("Show Property Pane"), this)); d->action_show_propeditor->setShortcut(QKeySequence("Alt+3")); d->action_show_propeditor->setToolTip(xi18n("Show the Property pane")); d->action_show_propeditor->setWhatsThis(xi18n("Shows the Property pane.")); connect(d->action_show_propeditor, SIGNAL(triggered()), this, SLOT(slotTogglePropertyEditor())); } else { d->action_show_propeditor = 0; } if (!d->userMode) { d->action_activate_propeditor = addAction("activate_propeditor", xi18n("Activate Property Pane"), "Alt+-"); d->action_activate_propeditor->setToolTip(xi18n("Activate the Property pane")); d->action_activate_propeditor->setWhatsThis(xi18n("Activates the Property pane. If it is hidden, shows it first.")); connect(d->action_activate_propeditor, SIGNAL(triggered()), this, SLOT(slotActivatePropertyPane())); } else { d->action_activate_propeditor = 0; } d->action_tools_locate = addAction("tools_locate", xi18n("Locate..."), "Ctrl+K"); d->action_tools_locate->setToolTip(xi18n("Switch to Global Locate box")); d->action_tools_locate->setWhatsThis(xi18n("Switches to Global Locate box.")); // (connection is added elsewhere) //DATA MENU d->action_data_save_row = createSharedAction(xi18n("&Accept"), koIconName("dialog-ok"), QKeySequence(Qt::SHIFT + Qt::Key_Return), "data_save_row"); d->action_data_save_row->setToolTip(xi18n("Accept changes made to the current record")); d->action_data_save_row->setWhatsThis(xi18n("Accepts 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"), 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 #ifdef KEXI_SHOW_UNIMPLEMENTED 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.")); #else d->action_format_font = d->dummy_action; #endif //TOOLS MENU //WINDOW MENU // additional 'Window' menu items d->action_window_next = addAction("window_next", xi18n("&Next Window"), "Alt+Right"); 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"), "Alt+Left"); 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_close_tab = addAction("close_tab", koIcon("tab-close"), xi18n("&Close Tab"), "Ctrl+W"); d->action_close_tab->setToolTip(xi18n("Close the current tab")); d->action_close_tab->setWhatsThis(xi18n("Closes the current tab.")); connect(d->action_close_tab, SIGNAL(triggered()), this, SLOT(closeCurrentWindow())); d->action_close_all_tabs = addAction("close_all_tabs", QIcon(), xi18n("Cl&ose All Tabs")); d->action_close_all_tabs->setToolTip(xi18n("Close all tabs")); d->action_close_all_tabs->setWhatsThis(xi18n("Closes all tabs.")); connect(d->action_close_all_tabs, SIGNAL(triggered()), this, SLOT(closeAllWindows())); d->action_tab_next = addAction("tab_next", koIcon("go-next"), KStandardShortcut::label(KStandardShortcut::TabNext)); d->action_tab_next->setWhatsThis(KStandardShortcut::whatsThis(KStandardShortcut::TabNext)); d->action_tab_next->setShortcuts(KStandardShortcut::shortcut(KStandardShortcut::TabNext)); connect(d->action_tab_next, &QAction::triggered, this, &KexiMainWindow::activateNextTab); d->action_tab_previous = addAction("tab_previous", koIcon("go-previous"), KStandardShortcut::label(KStandardShortcut::TabPrev)); d->action_tab_previous->setWhatsThis(KStandardShortcut::whatsThis(KStandardShortcut::TabPrev)); d->action_tab_previous->setShortcuts(KStandardShortcut::shortcut(KStandardShortcut::TabPrev)); connect(d->action_tab_previous, &QAction::triggered, this, &KexiMainWindow::activatePreviousTab); d->action_window_fullscreen = KStandardAction::fullScreen(this, SLOT(toggleFullScreen(bool)), this, ac); ac->addAction("full_screen", d->action_window_fullscreen); //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 // ----- 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("close_tab", Kexi::GlobalActionCategory | Kexi::WindowActionCategory); acat->setAllObjectTypesSupported("close_tab", true); acat->addAction("close_all_tabs", Kexi::GlobalActionCategory | Kexi::WindowActionCategory); acat->setAllObjectTypesSupported("close_all_tabs", true); acat->addAction("tab_next", Kexi::GlobalActionCategory); acat->addAction("tab_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::setupMainMenu() { KActionCollection *ac = actionCollection(); QMenuBar *menu = menuBar(); { QMenu *fileMenu = menu->addMenu(xi18n("&File")); if (!d->userMode) { d->addAction(fileMenu, "project_new"); fileMenu->addSeparator(); d->addAction(fileMenu, "project_open"); d->addAction(fileMenu, "project_open_recent"); fileMenu->addSeparator(); } fileMenu->addAction(d->action_save); if (!d->userMode) { fileMenu->addAction(d->action_save_as); } fileMenu->addSeparator(); if (!d->userMode) { fileMenu->addAction(d->action_tools_import_project); fileMenu->addSeparator(); } #ifdef KEXI_SHOW_UNIMPLEMENTED fileMenu->addAction(d->action_project_print); fileMenu->addAction(d->action_project_print_preview); fileMenu->addAction(d->action_project_print_setup); fileMenu->addSeparator(); #endif #ifdef KEXI_SHOW_UNIMPLEMENTED fileMenu->addAction(d->action_project_properties); fileMenu->addSeparator(); #endif fileMenu->addAction(d->action_close_tab); fileMenu->addAction(d->action_close_all_tabs); if (!d->userMode) { fileMenu->addAction(d->action_close); } fileMenu->addSeparator(); d->addAction(fileMenu, "quit"); } { QMenu *editMenu = menu->addMenu(xi18n("&Edit")); editMenu->addAction(d->action_edit_undo); editMenu->addAction(d->action_edit_redo); editMenu->addSeparator(); editMenu->addAction(d->action_edit_cut); editMenu->addAction(d->action_edit_copy); editMenu->addAction(d->action_edit_copy_special_data_table); editMenu->addAction(d->action_edit_paste); if (!d->userMode) { editMenu->addAction(d->action_edit_paste_special_data_table); } editMenu->addSeparator(); editMenu->addAction(d->action_edit_select_all); editMenu->addSeparator(); { QMenu *findReplaceMenu = editMenu->addMenu(xi18n("&Find/Replace")); findReplaceMenu->addAction(d->action_edit_find); findReplaceMenu->addAction(d->action_edit_findnext); findReplaceMenu->addAction(d->action_edit_findprev); #ifdef KEXI_SHOW_UNIMPLEMENTED findReplaceMenu->addSeparator(); findReplaceMenu->addAction(d->action_edit_replace); findReplaceMenu->addAction(d->action_edit_replace_all); #endif } editMenu->addSeparator(); editMenu->addAction(d->action_edit_delete); // in local toolbar already: editMenu->addAction(d->action_data_save_row); // in local toolbar already: editMenu->addAction(d->action_data_cancel_row_changes); //TODO move to local menu: editMenu->addAction(d->action_edit_edititem); //TODO move to local menu: editMenu->addAction(d->action_edit_insert_empty_row); //TODO move to local menu: editMenu->addAction(d->action_data_execute); //editMenu->addSeparator(); //TODO move to local menu: editMenu->addAction(d->action_edit_delete); //TODO move to local menu: editMenu->addAction(d->action_edit_delete_row); //in local menu already editMenu->addAction(d->action_edit_clear_table); } #ifdef KEXI_SHOW_UNIMPLEMENTED { QMenu *formatMenu = menu->addMenu(xi18n("F&ormat")); formatMenu->addAction(d->action_format_font); } #endif { QMenu *dataMenu = menu->addMenu(xi18n("&Data")); if (!d->userMode) { dataMenu->addAction(d->action_project_import_data_table); dataMenu->addAction(d->action_tools_data_import); dataMenu->addSeparator(); } dataMenu->addAction(d->action_project_export_data_table); } { QMenu *toolsMenu = menu->addMenu(xi18n("&Tools")); toolsMenu->addAction(d->action_tools_locate); toolsMenu->addSeparator(); #ifdef KEXI_SHOW_UNIMPLEMENTED toolsMenu->addAction(d->action_project_relations); #endif toolsMenu->addAction(d->action_tools_compact_database); } { QMenu *settingsMenu = menu->addMenu(xi18n("&Settings")); settingsMenu->addAction(d->action_window_fullscreen); settingsMenu->addSeparator(); #ifdef KEXI_SHOW_UNIMPLEMENTED settingsMenu->addAction(d->action_settings); #endif } { QMenu *windowMenu = menu->addMenu(xi18n("&Window")); windowMenu->addAction(d->action_tab_next); windowMenu->addAction(d->action_tab_previous); windowMenu->addSeparator(); windowMenu->addAction(d->action_show_nav); windowMenu->addAction(d->action_show_propeditor); } { // add help menu actions... (KexiTabbedToolBar depends on them) KHelpMenu *helpMenu = new KHelpMenu(this, KAboutData::applicationData(), true/*showWhatsThis*/); QAction* help_report_bug_action = helpMenu->action(KHelpMenu::menuReportBug); 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, &QAction::triggered, this, &KexiMainWindow::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 = helpMenu->action(KHelpMenu::menuWhatsThis); 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 = helpMenu->action(KHelpMenu::menuHelpContents); 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 = helpMenu->action(KHelpMenu::menuAboutApp); 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 = helpMenu->action(KHelpMenu::menuAboutKDE); ac->addAction(help_about_kde_action->objectName(), help_about_kde_action); help_about_kde_action->setWhatsThis(xi18n("Shows information about KDE.")); menu->addMenu(helpMenu->menu()); } } 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); if (d->objectViewWidget) { d->action_close_tab->setEnabled(has_window); d->action_close_all_tabs->setEnabled(has_window); } //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_SHOW_UNIMPLEMENTED 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); //CREATE MENU if (d->tabbedToolBar && d->tabbedToolBar->createWidgetToolBar()) d->tabbedToolBar->createWidgetToolBar()->setEnabled(d->prj); // DATA MENU // WINDOW MENU if (d->objectViewWidget) { d->action_tab_next->setEnabled(d->objectViewWidget->tabWidget()->count() > 1); d->action_tab_previous->setEnabled(d->objectViewWidget->tabWidget()->count() > 1); } //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( //! @todo Support compacting of non-opened projects /*!d->prj ||*/ (!readOnly && d->prj && d->prj->dbConnection() && (d->prj->dbConnection()->driver()->features() & KDbDriver::CompactingDatabaseSupported)) ); //DOCKS if (d->objectViewWidget && d->objectViewWidget->projectNavigator()) { d->objectViewWidget->projectNavigator()->setEnabled(d->prj); } if (d->objectViewWidget && d->objectViewWidget->propertyPane()) { d->objectViewWidget->propertyPane()->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 " "%2." "Do you want to import it as a new KEXI project?", projectData.infoString(), QApplication::applicationDisplayName()), 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(); d->modeSelector->setCurrentMode(Kexi::EditGlobalMode); 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->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->objectViewWidget && d->objectViewWidget->projectNavigator()) { d->objectViewWidget->projectNavigator()->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) { for (const 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->objectViewWidget && d->objectViewWidget->projectNavigator()) { d->navWasVisibleBeforeProjectClosing = d->objectViewWidget->projectNavigator()->isVisible(); d->setProjectNavigatorVisible(false); d->objectViewWidget->projectNavigator()->setProject(0); //slotProjectNavigatorVisibilityChanged(true); // hide side tab } if (d->objectViewWidget && d->objectViewWidget->propertyPane()) { d->objectViewWidget->propertyPane()->hide(); d->action_show_propeditor->setChecked(false); } d->clearWindows(); //sanity! delete d->prj; d->prj = 0; updateReadOnlyState(); invalidateActions(); updateAppCaption(); if (d->userMode) { d->modeSelector->setCurrentMode(Kexi::WelcomeGlobalMode); } 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() { QWidget *centralWidget = new QWidget; setCentralWidget(centralWidget); QVBoxLayout *vlyr = new QVBoxLayout(centralWidget); 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_tools_locate); connect(d->action_tools_locate, 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); d->modeSelector = new KexiGlobalViewModeSelector; connect(d->modeSelector, &KexiGlobalViewModeSelector::currentModeChanged, this, &KexiMainWindow::slotCurrentModeChanged); mainWidgetContainerLyr->addWidget(d->modeSelector); if (d->userMode) { d->modeSelector->hide(); } d->globalViewStack = new QStackedWidget; mainWidgetContainerLyr->addWidget(d->globalViewStack, 1); } //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::setupObjectView() { if (d->objectViewWidget) { return; } KexiObjectViewWidget::Flags flags; if (d->isProjectNavigatorVisible) { flags |= KexiObjectViewWidget::ProjectNavigatorEnabled; } if (!d->userMode) { flags |= KexiObjectViewWidget::PropertyPaneEnabled; } d->objectViewWidget = new KexiObjectViewWidget(flags); connect(d->objectViewWidget, &KexiObjectViewWidget::activeWindowChanged, this, &KexiMainWindow::activeWindowChanged); connect(d->objectViewWidget, &KexiObjectViewWidget::closeWindowRequested, this, &KexiMainWindow::closeWindowForTab); connect(d->objectViewWidget, &KexiObjectViewWidget::closeAllWindowsRequested, this, &KexiMainWindow::closeAllWindows); connect(d->objectViewWidget, &KexiObjectViewWidget::projectNavigatorAnimationFinished, this, &KexiMainWindow::slotProjectNavigatorVisibilityChanged); slotProjectNavigatorVisibilityChanged(d->objectViewWidget->projectNavigator()); // Restore settings //! @todo FIX LAYOUT PROBLEMS KConfigGroup propertyEditorGroup(d->config->group("PropertyEditor")); QFont f(KexiStyle::propertyPane().font()); 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); } } if (d->objectViewWidget->propertyPane()) { d->objectViewWidget->propertyPane()->setFont(f); KConfigGroup mainWindowGroup(d->config->group("MainWindow")); const QSize projectNavigatorSize = mainWindowGroup.readEntry("ProjectNavigatorSize", QSize()); const QSize propertyEditorSize = mainWindowGroup.readEntry("PropertyEditorSize", QSize()); d->objectViewWidget->setSidebarWidths(projectNavigatorSize.isValid() ? projectNavigatorSize.width() : -1, propertyEditorSize.isValid() ? propertyEditorSize.width() : -1); } d->globalViewStack->addWidget(d->objectViewWidget); KexiProjectNavigator* navigator = d->objectViewWidget->projectNavigator(); if (navigator) { //connect(d->navDockWidget, SIGNAL(visibilityChanged(bool)), // this, SLOT(slotProjectNavigatorVisibilityChanged(bool))); //Nav2 Signals connect(navigator, SIGNAL(openItem(KexiPart::Item*,Kexi::ViewMode)), this, SLOT(openObject(KexiPart::Item*,Kexi::ViewMode))); connect(navigator, SIGNAL(openOrActivateItem(KexiPart::Item*,Kexi::ViewMode)), this, SLOT(openObjectFromNavigator(KexiPart::Item*,Kexi::ViewMode))); connect(navigator, SIGNAL(newItem(KexiPart::Info*)), this, SLOT(newObject(KexiPart::Info*))); connect(navigator, SIGNAL(removeItem(KexiPart::Item*)), this, SLOT(removeObject(KexiPart::Item*))); connect(navigator->model(), SIGNAL(renameItem(KexiPart::Item*,QString,bool*)), this, SLOT(renameObject(KexiPart::Item*,QString,bool*))); connect(navigator->model(), SIGNAL(changeItemCaption(KexiPart::Item*,QString,bool*)), this, SLOT(setObjectCaption(KexiPart::Item*,QString,bool*))); connect(navigator, SIGNAL(executeItem(KexiPart::Item*)), this, SLOT(executeItem(KexiPart::Item*))); connect(navigator, SIGNAL(exportItemToClipboardAsDataTable(KexiPart::Item*)), this, SLOT(copyItemToClipboardAsDataTable(KexiPart::Item*))); connect(navigator, SIGNAL(exportItemToFileAsDataTable(KexiPart::Item*)), this, SLOT(exportItemAsDataTable(KexiPart::Item*))); #ifdef KEXI_QUICK_PRINTING_SUPPORT connect(navigator, SIGNAL(printItem(KexiPart::Item*)), this, SLOT(printItem(KexiPart::Item*))); connect(navigator, SIGNAL(pageSetupForItem(KexiPart::Item*)), this, SLOT(showPageSetupForItem(KexiPart::Item*))); #endif connect(navigator, SIGNAL(selectionChanged(KexiPart::Item*)), this, SLOT(slotPartItemSelectedInNavigator(KexiPart::Item*))); } if (navigator) { connect(d->prj, SIGNAL(newItemStored(KexiPart::Item*)), navigator->model(), SLOT(slotAddItem(KexiPart::Item*))); connect(d->prj, SIGNAL(itemRemoved(KexiPart::Item)), navigator->model(), SLOT(slotRemoveItem(KexiPart::Item))); navigator->setFocus(); /*if (d->forceShowProjectNavigatorOnCreation) { slotShowNavigator(); d->forceShowProjectNavigatorOnCreation = false; } else if (d->forceHideProjectNavigatorOnCreation) { d->forceHideProjectNavigatorOnCreation = false; }*/ } invalidateActions(); } void KexiMainWindow::updateObjectView() { setupObjectView(); if (d->prj && d->prj->isConnected()) { KexiProjectNavigator* navigator = d->objectViewWidget->projectNavigator(); if (navigator && !navigator->model()->project()) { QString partManagerErrorMessages; navigator->setProject(d->prj, QString()/*all classes*/, &partManagerErrorMessages); if (partManagerErrorMessages.isEmpty()) { d->setProjectNavigatorVisible(true); } else { showWarningContinueMessage(partManagerErrorMessages, QString(), "ShowWarningsRelatedToPluginsLoading"); } } } } void KexiMainWindow::slotLastActions() { } 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) { if (queryClose()) { ev->accept(); } else { ev->ignore(); } } void KexiMainWindow::resizeEvent(QResizeEvent *e) { QMainWindow::resizeEvent(e); //qDebug() << "===" << e->size() << size() << isVisible(); } 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); } } } 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->objectViewWidget) { int projectNavigatorWidth; int propertyEditorWidth; d->objectViewWidget->getSidebarWidths(&projectNavigatorWidth, &propertyEditorWidth); if (projectNavigatorWidth > 0) { mainWindowGroup.writeEntry("ProjectNavigatorSize", QSize(projectNavigatorWidth, 1)); } if (propertyEditorWidth > 0) { mainWindowGroup.writeEntry("PropertyEditorSize", QSize(propertyEditorWidth, 1)); } } 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->objectViewWidget || !d->objectViewWidget->propertyPane()) return; if ( !curWindowPart || (/*prevWindowPart &&*/ curWindowPart && (prevWindowPart != curWindowPart || prevViewMode != curViewMode) ) ) { #ifdef __GNUC__ #warning TODO KexiMainWindow::updateCustomPropertyPanelTabs() #else #pragma WARNING(TODO KexiMainWindow::updateCustomPropertyPanelTabs()) #endif #if 0 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->objectViewWidget->propertyPane()->currentIndex()); } } //delete old custom tabs (other than 'property' tab) const int count = d->objectViewWidget->propertyEditorTabWidget()->count(); for (int i = 1; i < count; i++) d->objectViewWidget->propertyEditorTabWidget()->removeTab(1); #endif } //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 d->objectViewWidget->propertyPane()->removeAllSections(); curWindowPart->setupPropertyPane(d->objectViewWidget->propertyPane()); #ifdef __GNUC__ #warning TODO KexiMainWindow::updateCustomPropertyPanelTabs() #else #pragma WARNING(TODO KexiMainWindow::updateCustomPropertyPanelTabs()) #endif #if 0 //restore current page number for this part if (d->recentlySelectedPropertyPanelPages.contains(curWindowPart)) { d->objectViewWidget->propertyEditorTabWidget()->setCurrentIndex( d->recentlySelectedPropertyPanelPages[ curWindowPart ] ); } #endif } //#endif //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) { setCurrentMode(viewModeToGlobal(currentWindow()->currentViewMode())); //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) { #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->objectViewWidget->tabWidget()->setCurrentWidget(window.parentWidget()/*container*/); window.activate(); return true; } void KexiMainWindow::activateNextWindow() { // Case 1: go to next assistant page KexiAssistantPage *page = d->visibleMainMenuWidgetPage(); if (page) { page->next(); return; } // Case 2: go to next tab activateNextTab(); } void KexiMainWindow::activatePreviousWindow() { // Case 1: go to previous assistant page KexiAssistantPage *page = d->visibleMainMenuWidgetPage(); if (page) { page->tryBack(); return; } // Case 2: go to previous tab activatePreviousTab(); } void KexiMainWindow::activateNextTab() { int index = d->objectViewWidget->tabWidget()->currentIndex() + 1; if (index >= d->objectViewWidget->tabWidget()->count()) { index = 0; } d->objectViewWidget->tabWidget()->setCurrentIndex(index); } void KexiMainWindow::activatePreviousTab() { int index = d->objectViewWidget->tabWidget()->currentIndex() - 1; if (index < 0) { index = d->objectViewWidget->tabWidget()->count() - 1; } d->objectViewWidget->tabWidget()->setCurrentIndex(index); } 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->objectViewWidget && d->objectViewWidget->projectNavigator()){ connect(prj, SIGNAL(itemRemoved(KexiPart::Item)), d->objectViewWidget->projectNavigator()->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(); d->prj->data()->setLastOpened(QDateTime::currentDateTime()); Kexi::recentProjects()->addProjectData(*d->prj->data()); d->modeSelector->setCurrentMode(Kexi::EditGlobalMode); 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); if (w) { 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->objectViewWidget || !d->objectViewWidget->projectNavigator()) { return; } d->objectViewWidget->projectNavigator()->setFocus(); } void KexiMainWindow::slotActivateMainArea() { if (currentWindow()) currentWindow()->setFocus(); } void KexiMainWindow::slotActivatePropertyPane() { if (!d->objectViewWidget || !d->objectViewWidget->propertyPane()) { return; } d->objectViewWidget->propertyPane()->setFocus(); // if (d->objectViewWidget->propertyPane()->currentWidget()) { // d->objectViewWidget->propertyPane()->currentWidget()->setFocus(); // } } void KexiMainWindow::slotToggleProjectNavigator() { if (d->objectViewWidget && d->objectViewWidget->projectNavigator()) { d->setProjectNavigatorVisible(!d->objectViewWidget->projectNavigator()->isVisible(), Private::ShowAnimated); } } void KexiMainWindow::slotTogglePropertyEditor() { if (d->objectViewWidget && d->objectViewWidget->propertyPane()) { d->objectViewWidget->setPropertyPaneVisible(!d->objectViewWidget->propertyPane()->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; } setCurrentMode(viewModeToGlobal(viewMode)); 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 override { 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::closeAllWindows() { if (!d->objectViewWidget || !d->objectViewWidget->tabWidget()) return true; QList windowList; for (int i = 0; i < d->objectViewWidget->tabWidget()->count(); ++i) { KexiWindow *window = windowForTab(i); if (window) { windowList.append(window); } } tristate alternateResult = true; for (KexiWindow *window : windowList) { const tristate result = closeWindow(window); if (~result) { return result; } if (result == false) { alternateResult = false; } } return alternateResult; } 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->propertyEditor()) { // ah, closing detached window - better switch off property buffer right now... d->propertySet = 0; d->propertyEditor()->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->objectViewWidget && d->objectViewWidget->projectNavigator()) { d->objectViewWidget->projectNavigator()->updateItemName(*window->partItem(), false); } } hideDesignTab(previousItemId, QString()); d->removeWindow(window_id); d->setWindowContainerExistsFor(window->partItem()->identifier(), false); QWidget *windowContainer = window->parentWidget(); d->objectViewWidget->tabWidget()->removeTab( d->objectViewWidget->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->objectViewWidget && d->objectViewWidget->projectNavigator()) { d->objectViewWidget->projectNavigator()->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->objectViewWidget->slotCurrentTabIndexChanged(d->objectViewWidget->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->objectViewWidget->propertyPane()) { 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; QWidget *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); const int tabIndex = d->objectViewWidget->tabWidget()->addEmptyContainerTab( part ? part->info()->icon() : koIcon("object"), KexiWindow::windowTitleForItem(*item)); // open new tab earlier d->setWindowContainerExistsFor(item->identifier(), true); d->objectViewWidget->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->objectViewWidget->tabWidget()->setTabWhatsThis(tabIndex, whatsThisText); d->objectViewWidget->tabWidget()->setCurrentIndex(tabIndex); #ifndef KEXI_NO_PENDING_DIALOGS d->addItemToPendingWindows(item, Private::WindowOpeningJob); #endif windowContainer = d->objectViewWidget->tabWidget()->widget(tabIndex); window = d->prj->openObject(windowContainer, item, viewMode, staticObjectArgs); if (window) { d->objectViewWidget->tabWidget()->setWindowForTab(tabIndex, window); // update text and icon d->objectViewWidget->tabWidget()->setTabText(tabIndex, window->windowTitle()); d->objectViewWidget->tabWidget()->setTabIcon(tabIndex, window->windowIcon()); } } if (!window || !activateWindow(*window)) { #ifndef KEXI_NO_PENDING_DIALOGS d->removePendingWindow(item->identifier()); #endif d->setWindowContainerExistsFor(item->identifier(), false); d->objectViewWidget->tabWidget()->removeTab( d->objectViewWidget->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->objectViewWidget->projectNavigator()->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 Delete ?", "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 delete 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()); KGuiItem closeAndRenameItem(KStandardGuiItem::closeWindow()); closeAndRenameItem.setText(xi18n("Close Window and Rename")); const int r = KMessageBox::questionYesNo(this, msg, QString(), closeAndRenameItem, KStandardGuiItem::cancel()); if (r != KMessageBox::Yes) { *success = false; return; } const tristate closeResult = closeWindow(window); if (closeResult != true) { *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; } *success = true; } 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; } *success = true; } 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->propertyEditor()) { d->propertyEditor()->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->propertyEditor()) { 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; } d->objectViewWidget->propertyPane()->changePropertySet( d->propertySet, propertyToSelect, options, (_currentWindow && _currentWindow->selectedView()) ? _currentWindow->selectedView()->textToDisplayForNullSet() : QString()); } } } } void KexiMainWindow::slotDirtyFlagChanged(KexiWindow* window) { KexiPart::Item *item = window->partItem(); //update text in navigator and app. caption if (!d->userMode) { d->objectViewWidget->projectNavigator()->updateItemName(*item, window->isDirty()); } invalidateActions(); updateAppCaption(); d->objectViewWidget->tabWidget()->setTabText( d->objectViewWidget->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) { //! @todo Support compacting of non-opened projects return; #if 0 KexiStartupDialog dlg( KexiStartupDialog::OpenExisting, 0, Kexi::connset(), this); if (dlg.exec() != QDialog::Accepted) return; if (dlg.selectedFile().isEmpty()) { //! @todo add support for server based if needed? return; } KDbConnectionData cdata; cdata.setDatabaseName(dlg.selectedFile()); //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); #endif } 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->objectViewWidget->projectNavigator()->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) { if (!d->prj) return; KexiPart::Item *item = d->prj->itemForPluginId(pluginId, name); if (!item) return; if (d->objectViewWidget && d->objectViewWidget->projectNavigator()) { d->setProjectNavigatorVisible(true, Private::ShowAnimated); d->objectViewWidget->projectNavigator()->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() { if (d->objectViewWidget && d->objectViewWidget->propertyPane()) { d->objectViewWidget->propertyPane()->updateInfoLabelForPropertySet( (currentWindow() && currentWindow()->selectedView()) ? currentWindow()->selectedView()->textToDisplayForNullSet() : QString()); } } void KexiMainWindow::beginPropertyPaneUpdate() { if (d->propertyPaneAnimation) { d->propertyPaneAnimation->hide(); d->propertyPaneAnimation->deleteLater(); } d->propertyPaneAnimation = new KexiFadeWidgetEffect(d->objectViewWidget->propertyPane()); } void KexiMainWindow::endPropertyPaneUpdate() { if (d->propertyPaneAnimation) { d->objectViewWidget->propertyPane()->repaint(); d->propertyPaneAnimation->start(150); } } void KexiMainWindow::addSearchableModel(KexiSearchableModel *model) { if (d->tabbedToolBar) { d->tabbedToolBar->addSearchableModel(model); } } void KexiMainWindow::removeSearchableModel(KexiSearchableModel *model) { if (d->tabbedToolBar) { d->tabbedToolBar->removeSearchableModel(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(); } } Kexi::GlobalViewMode KexiMainWindow::currentMode() const { return d->modeSelector->currentMode(); } void KexiMainWindow::setCurrentMode(Kexi::GlobalViewMode mode) { d->modeSelector->setCurrentMode(mode); } void KexiMainWindow::slotCurrentModeChanged(Kexi::GlobalViewMode previousMode) { const Kexi::ViewMode viewMode = currentWindow() ? currentWindow()->currentViewMode() : Kexi::NoViewMode; switch (d->modeSelector->currentMode()) { case Kexi::WelcomeGlobalMode: break; case Kexi::ProjectGlobalMode: break; case Kexi::EditGlobalMode: updateObjectView(); d->globalViewStack->setCurrentWidget(d->objectViewWidget); if (viewMode == Kexi::DesignViewMode || viewMode == Kexi::TextViewMode) { if (true != switchToViewMode(*currentWindow(), Kexi::DataViewMode)) { setCurrentMode(previousMode); } } break; case Kexi::DesignGlobalMode: updateObjectView(); d->globalViewStack->setCurrentWidget(d->objectViewWidget); if (viewMode == Kexi::DataViewMode) { Kexi::ViewMode newViewMode; if (currentWindow()->supportsViewMode(Kexi::TextViewMode) && d->modeSelector->keyboardModifiers() == Qt::CTRL) { newViewMode = Kexi::TextViewMode; } else { newViewMode = Kexi::DesignViewMode; } if (true != switchToViewMode(*currentWindow(), newViewMode)) { setCurrentMode(previousMode); } } break; case Kexi::HelpGlobalMode: break; case Kexi::NoGlobalMode: break; } } void KexiMainWindow::slotProjectNavigatorVisibilityChanged(bool visible) { if (d->objectViewWidget && d->objectViewWidget->projectNavigator()) { d->modeSelector->setArrowColor(visible ? d->objectViewWidget->projectNavigator()->palette().color(QPalette::Window) : d->objectViewWidget->tabWidget()->palette().color(QPalette::Window)); } } diff --git a/src/main/KexiUserFeedbackAgent.cpp b/src/main/KexiUserFeedbackAgent.cpp index 57c92f4a9..082d6457a 100644 --- a/src/main/KexiUserFeedbackAgent.cpp +++ b/src/main/KexiUserFeedbackAgent.cpp @@ -1,455 +1,455 @@ /* This file is part of the KDE project Copyright (C) 2012 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 "KexiUserFeedbackAgent.h" #include #include #include #include #include #include // no kdeversion.h anymore but this is nice enough here #include #include #include #include #include #include #include #include #if defined Q_OS_WIN #include #include #endif #if defined HAVE_UNAME # include #endif /*! Version of the feedback data format. Changelog: 1.0: initial version 1.1: added JSON-compatible character escaping; added uid 1.2: added os_release, os_machine, screen_count 1.3: added running_desktop, running_desktop_version */ static const char KexiUserFeedbackAgent_VERSION[] = "1.3"; class Q_DECL_HIDDEN KexiUserFeedbackAgent::Private { public: Private() : configGroup(KConfigGroup(KSharedConfig::openConfig()->group("User Feedback"))) , areas(KexiUserFeedbackAgent::NoAreas) , sentDataInThisSession(KexiUserFeedbackAgent::NoAreas) , url(QLatin1String("http://www.kexi-project.org/feedback")) , redirectChecked(false) { } void updateData(); KConfigGroup configGroup; KexiUserFeedbackAgent::Areas areas; KexiUserFeedbackAgent::Areas sentDataInThisSession; QList keys; QMap data; QMap areasForKeys; //! Unique user ID handy if used does not want to disclose username //! but agrees to be identified as unique user of the application. QUuid uid; QString url; bool redirectChecked; }; void KexiUserFeedbackAgent::Private::updateData() { keys.clear(); data.clear(); areasForKeys.clear(); #define ADD(key, val, area) { \ keys.append(QByteArray(key)); data.insert(QByteArray(key), QVariant(val)); \ areasForKeys.insert(key, KexiUserFeedbackAgent::area); \ } ADD("ver", KexiUserFeedbackAgent_VERSION, BasicArea); ADD("uid", uid.toString(), AnonymousIdentificationArea); ADD("app_ver", Kexi::versionString(), BasicArea); ADD("app_ver_major", Kexi::versionMajor(), BasicArea); ADD("app_ver_minor", Kexi::versionMinor(), BasicArea); ADD("app_ver_release", Kexi::versionRelease(), BasicArea); //! @TODO KEXI3 For kde_ver_* use runtime information like KDE::versionMajor(), not the macro (KF5 libs lack it unfortunately) ADD("kde_ver", KWIDGETSADDONS_VERSION_STRING, BasicArea); ADD("kde_ver_major", KWIDGETSADDONS_VERSION_MAJOR, BasicArea); ADD("kde_ver_minor", KWIDGETSADDONS_VERSION_MINOR, BasicArea); ADD("kde_ver_release", KWIDGETSADDONS_VERSION_PATCH, BasicArea); #ifdef Q_OS_LINUX { ADD("os", "linux", SystemInfoArea); const QByteArray desktop = KexiUtils::detectedDesktopSession(); const QByteArray gdm = qgetenv("GDMSESSION").trimmed().toUpper(); const bool kdeSession = qgetenv("KDE_FULL_SESSION").trimmed().toLower() == "true"; // detect running desktop - // http://standards.freedesktop.org/menu-spec/latest/apb.html + // https://standards.freedesktop.org/menu-spec/latest/apb.html QString runningDesktop; QString runningDesktopVersion; //! @todo set runningDesktopVersion for other desktops if (desktop.contains("KDE") || kdeSession || gdm.contains("KDE")) { runningDesktop = "KDE Plasma"; //! @todo run kde{4|5}-config --kde-version to know full version and save in runningDesktopVersion runningDesktopVersion = qgetenv("KDE_SESSION_VERSION").trimmed(); } else if (desktop.contains("UNITY")) { runningDesktop = "Unity"; } else if (desktop.contains("RAZOR")) { runningDesktop = "Razor-qt"; } else if (desktop.contains("ROX")) { runningDesktop = "ROX"; } else if (desktop.contains("TDE")) { runningDesktop = "Trinity"; } else if (desktop.contains("MATE")) { runningDesktop = "MATE"; } else if (desktop.contains("LXDE") || gdm.contains("LUBUNTU")) { runningDesktop = "LXDE"; } else if (desktop.contains("XFCE") || gdm.contains("XFCE")) { runningDesktop = "Xfce"; } else if (desktop.contains("EDE")) { runningDesktop = "EDE"; } else if (desktop.contains("CINNAMON")) { runningDesktop = "Cinnamon"; } else if (desktop.contains("GNOME") || gdm.contains("GNOME")) { if (gdm.contains("cinnamon")) { runningDesktop = "Cinnamon"; } else if (gdm.contains("CLASSIC")) { runningDesktop = "GNOME Classic"; } else { runningDesktop = "GNOME"; } } else { if (!desktop.isEmpty()) { runningDesktop = "Other: " + desktop.toLower(); } else if (!gdm.isEmpty()) { runningDesktop = "Other: " + gdm.toLower(); } } if (!runningDesktop.isEmpty()) { ADD("running_desktop", runningDesktop, SystemInfoArea); } if (!runningDesktopVersion.isEmpty()) { ADD("running_desktop_version", runningDesktopVersion, SystemInfoArea); } // retrieve distribution name and release QProcess p; p.start("lsb_release", QStringList() << "-i" << "-r" << "-d"); if (p.waitForFinished()) { QString info = p.readLine().replace("Distributor ID:", "").trimmed(); - if (info.toLower() == "ubuntu") { // Ubuntu derivatives (http://askubuntu.com/a/227669/226642) + if (info.toLower() == "ubuntu") { // Ubuntu derivatives (https://askubuntu.com/a/227669/226642) if (runningDesktop == "KDE Plasma") { info = "Kubuntu"; } else if (runningDesktop == "LXDE") { info = "Lubuntu"; } else if (runningDesktop == "Xfce") { info = "Xubuntu"; } else if (runningDesktop.contains(QLatin1String("gnome"), Qt::CaseInsensitive)) { info = "Ubuntu GNOME"; } } if (!info.isEmpty()) { ADD("linux_id", info, SystemInfoArea); } info = p.readLine().replace("Description:", "").trimmed(); if (!info.isEmpty()) { ADD("linux_desc", info, SystemInfoArea); } info = p.readLine().replace("Release:", "").trimmed(); if (!info.isEmpty()) { ADD("linux_rel", info, SystemInfoArea); } } p.close(); } #elif defined(Q_OS_MACOS) ADD("os", "mac", SystemInfoArea); #elif defined(Q_OS_WIN) ADD("os", "windows", SystemInfoArea); #else //! @todo BSD? ADD("os", "other", SystemInfoArea); #endif #if defined HAVE_UNAME struct utsname buf; if (uname(&buf) == 0) { ADD("os_release", buf.release, SystemInfoArea); ADD("os_machine", buf.machine, SystemInfoArea); } #elif defined(Q_OS_WIN) OSVERSIONINFO versionInfo; SYSTEM_INFO sysInfo; char* releaseStr; releaseStr = new char[6]; // "xx.xx\0" versionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); #pragma warning(push) #pragma warning(disable: 4996) // easy way, VerifyVersionInfo() is not a 100% equivalent or easy to use GetVersionEx(&versionInfo); #pragma warning(pop) GetSystemInfo(&sysInfo); _snprintf(releaseStr, 6, "%2d.%2d", versionInfo.dwMajorVersion, versionInfo.dwMinorVersion); ADD("os_release", releaseStr, SystemInfoArea); delete [6] releaseStr; switch(sysInfo.wProcessorArchitecture) { case PROCESSOR_ARCHITECTURE_AMD64: ADD("os_machine", "x86_64", SystemInfoArea); break; case PROCESSOR_ARCHITECTURE_IA64: ADD("os_machine", "ia64", SystemInfoArea); break; case PROCESSOR_ARCHITECTURE_INTEL: ADD("os_machine", "x86", SystemInfoArea); break; default: ADD("os_machine", "unknown", SystemInfoArea); } #endif QSize screen(QApplication::desktop()->screenGeometry( KexiMainWindowIface::global()->thisWidget()).size()); ADD("screen_width", screen.width(), ScreenInfoArea); ADD("screen_height", screen.height(), ScreenInfoArea); ADD("screen_count", QApplication::desktop()->screenCount(), ScreenInfoArea); QLocale locale; ADD("country", QLocale::countryToString(locale.country()), RegionalSettingsArea); ADD("language", QLocale::languageToString(locale.language()), RegionalSettingsArea); ADD("date_format", locale.dateFormat(QLocale::LongFormat), RegionalSettingsArea); ADD("short_date_format", locale.dateFormat(QLocale::ShortFormat), RegionalSettingsArea); ADD("time_format", locale.timeFormat(QLocale::LongFormat), RegionalSettingsArea); //! @todo KEXI3 "short_time_format" ADD("right_to_left", QApplication::isRightToLeft(), RegionalSettingsArea); #undef ADD } // --- KexiUserFeedbackAgent::KexiUserFeedbackAgent(QObject* parent) : QObject(parent), d(new Private) { if (d->configGroup.readEntry("BasicInfo", false)) { d->areas |= BasicArea | AnonymousIdentificationArea; } if (d->configGroup.readEntry("SystemInfo", false)) { d->areas |= SystemInfoArea; } if (d->configGroup.readEntry("ScreenInfo", false)) { d->areas |= ScreenInfoArea; } if (d->configGroup.readEntry("RegionalSettings", false)) { d->areas |= RegionalSettingsArea; } // load or create uid QString uidString = d->configGroup.readEntry("Uid", QString()); d->uid = QUuid(uidString); if (d->uid.isNull()) { d->uid = QUuid::createUuid(); d->configGroup.writeEntry("Uid", d->uid.toString()); } d->updateData(); sendData(); } KexiUserFeedbackAgent::~KexiUserFeedbackAgent() { delete d; } void KexiUserFeedbackAgent::setEnabledAreas(Areas areas) { if (areas != NoAreas && areas != AllAreas && !(areas & BasicArea)) { areas |= BasicArea; // fix, Basic Info is required } if (d->areas == areas) { return; } d->areas = areas; d->configGroup.writeEntry("BasicInfo", bool(d->areas & BasicArea)); d->configGroup.writeEntry("SystemInfo", bool(d->areas & SystemInfoArea)); d->configGroup.writeEntry("ScreenInfo", bool(d->areas & ScreenInfoArea)); d->configGroup.writeEntry("RegionalSettings", bool(d->areas & RegionalSettingsArea)); d->configGroup.sync(); if ((d->sentDataInThisSession & d->areas) != d->areas) { d->updateData(); sendData(); } } KexiUserFeedbackAgent::Areas KexiUserFeedbackAgent::enabledAreas() const { return d->areas; } -//! Escapes string for json format (see http://json.org/string.gif). +//! Escapes string for JSON format (see https://json.org). inline QString escapeJson(const QString& s) { QString res; for (int i=0; iareas == NoAreas) { return; } if (!Kexi::isKexiInstance()) { // Do not send feedback if this is not really Kexi but a test app based on Kexi return; } if (!d->redirectChecked) { sendRedirectQuestion(); return; } QByteArray postData; foreach (const QByteArray& key, d->keys) { Area area = d->areasForKeys.value(key); if (area != NoAreas && (d->areas & area)) { if (!postData.isEmpty()) { postData += ','; } postData += (QByteArray("\"") + key + "\":\"" + escapeJson(d->data.value(key).toString()).toUtf8() + '"'); } } //qDebug() << postData; KIO::Job* sendJob = KIO::storedHttpPost(postData, QUrl(d->url + "/send"), KIO::HideProgressInfo); connect(sendJob, SIGNAL(result(KJob*)), this, SLOT(sendDataFinished(KJob*))); sendJob->addMetaData("content-type", "Content-Type: application/x-www-form-urlencoded"); } void KexiUserFeedbackAgent::sendDataFinished(KJob* job) { if (job->error()) { //! @todo error... return; } KIO::StoredTransferJob* sendJob = qobject_cast(job); QByteArray result = sendJob->data(); result.chop(1); // remove \n //qDebug() << result; if (result == "ok") { d->sentDataInThisSession = d->areas; } } QVariant KexiUserFeedbackAgent::value(const QString& key) const { return d->data.value(key.toLatin1()); } void KexiUserFeedbackAgent::sendRedirectQuestion() { QByteArray postData = "get_url"; //qDebug() << postData; KIO::Job* sendJob = KIO::storedHttpPost(postData, QUrl(d->url + "/send"), KIO::HideProgressInfo); connect(sendJob, SIGNAL(result(KJob*)), this, SLOT(sendRedirectQuestionFinished(KJob*))); sendJob->addMetaData("content-type", "Content-Type: application/x-www-form-urlencoded"); } void KexiUserFeedbackAgent::sendRedirectQuestionFinished(KJob* job) { if (job->error()) { //! @todo error... qWarning() << "Error, no URL Redirect"; } else { KIO::StoredTransferJob* sendJob = qobject_cast(job); QByteArray result = sendJob->data(); result.chop(1); // remove \n //qDebug() << result; if (result.isEmpty()) { //qDebug() << "No URL Redirect"; } else { d->url = QString::fromUtf8(result); //qDebug() << "URL Redirect to" << d->url; } } d->redirectChecked = true; emit redirectLoaded(); sendData(); } QString KexiUserFeedbackAgent::serviceUrl() const { return d->url; } void KexiUserFeedbackAgent::waitForRedirect(QObject *receiver, const char* slot) { if (!receiver) { return; } if (d->redirectChecked) { QMetaObject::invokeMethod(receiver, slot); } else { connect(this, SIGNAL(redirectLoaded()), receiver, slot, Qt::UniqueConnection); if (d->areas == NoAreas) { sendRedirectQuestion(); } } } diff --git a/src/migration/CMakeLists.txt b/src/migration/CMakeLists.txt index 144863a0f..18d052d69 100644 --- a/src/migration/CMakeLists.txt +++ b/src/migration/CMakeLists.txt @@ -1,117 +1,117 @@ include_directories(${CMAKE_SOURCE_DIR}/src/core ${CMAKE_SOURCE_DIR}/src/widget ${CMAKE_BINARY_DIR}/src/widget ) add_definitions(-DKDE_DEFAULT_DEBUG_AREA=44000) simple_option(KEXI_MIGRATEMANAGER_DEBUG "Enable debugging for the migrate driver manager" OFF) set(KEXI_MIGRATE_PLUGIN_INSTALL_DIR ${KEXI_PLUGIN_INSTALL_DIR}/migrate) # ----------------------- function(build_and_install_kexi_migrate_driver _name _srcs _extra_libs _includes _defines) set(_target keximigrate_${_name}) ecm_create_qm_loader(_srcs ${_target}_qt) add_library(${_target} MODULE ${_srcs}) target_link_libraries(${_target} PUBLIC keximigrate ${_extra_libs} ) target_include_directories(${_target} PRIVATE ${_includes}) target_compile_definitions(${_target} PRIVATE ${_defines}) # Needed for examples and autotests: set_target_properties(${_target} PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/plugins/migrate") install(TARGETS ${_target} DESTINATION ${KEXI_MIGRATE_PLUGIN_INSTALL_DIR}) endfunction() # ----------------------- add_subdirectory(tsv) find_package(MySQL) set_package_properties(MySQL PROPERTIES TYPE RECOMMENDED PURPOSE "Required by KEXI MySQL migration driver") if(MySQL_FOUND) add_subdirectory(mysql) endif() find_package(PostgreSQL) set_package_properties(PostgreSQL PROPERTIES TYPE RECOMMENDED PURPOSE "Required by KEXI PostgreSQL migration driver") if(PostgreSQL_FOUND) add_subdirectory(postgresql) endif() if(false) # TODO KEXI3 find_package(FreeTDS) set_package_properties(FreeTDS PROPERTIES DESCRIPTION "Open source implementation of the TDS (Tabular Data Stream) protocol" - URL "http://www.freetds.org" + URL "https://www.freetds.org" TYPE RECOMMENDED PURPOSE "Required by KEXI Sybase migration driver" ) if(FREETDS_FOUND) add_subdirectory(sybase) endif() find_package(XBase) set_package_properties(XBase PROPERTIES DESCRIPTION "XBase compatible C++ class library" - URL "http://linux.techass.com/projects/xdb" + URL "https://linux.techass.com/projects/xdb" TYPE RECOMMENDED PURPOSE "Required by KEXI XBase migration driver" ) if(XBASE_FOUND) add_subdirectory(xbase) endif() endif() # KEXI3 find_package(GLIB2) set(_REQUIRED_BY_MDB "Required by KEXI MS Access migration driver") set_package_properties(GLIB2 PROPERTIES TYPE RECOMMENDED PURPOSE "${_REQUIRED_BY_MDB}") find_package(Iconv) set_package_properties(Iconv PROPERTIES TYPE RECOMMENDED PURPOSE "${_REQUIRED_BY_MDB}") if(GLIB2_FOUND AND Iconv_FOUND) add_subdirectory(mdb) endif() if(BUILD_TESTING) add_subdirectory(tests) endif() ########### next target ############### set(keximigrate_LIB_SRCS AlterSchemaTableModel.cpp KexiMigratePluginMetaData.cpp keximigrate.cpp keximigratedata.cpp KexiSqlMigrate.cpp migratemanager.cpp importwizard.cpp importtablewizard.cpp importoptionsdlg.cpp AlterSchemaWidget.cpp) kexi_add_library(keximigrate SHARED ${keximigrate_LIB_SRCS}) target_link_libraries(keximigrate PUBLIC kexiextendedwidgets ) generate_export_header(keximigrate) install(TARGETS keximigrate ${INSTALL_TARGETS_DEFAULT_ARGS}) ########### install files ############### if(FALSE) # TODO: install when we move to independent place install(FILES KexiMigratePluginMetaData.h keximigrate.h keximigratedata.h KexiSqlMigrate.h migratemanager.h DESTINATION ${INCLUDE_INSTALL_DIR}/kexidb COMPONENT Devel ) endif() diff --git a/src/plugins/reports/KexiDBReportDataSource.cpp b/src/plugins/reports/KexiDBReportDataSource.cpp index 08f3a0c17..0aae1a2c7 100644 --- a/src/plugins/reports/KexiDBReportDataSource.cpp +++ b/src/plugins/reports/KexiDBReportDataSource.cpp @@ -1,395 +1,395 @@ /* * Kexi Report Plugin * Copyright (C) 2007-2017 by Adam Pigg * Copyright (C) 2017-2018 Jarosław Staniek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public -* License along with this library. If not, see . +* License along with this library. If not, see . */ #include "KexiDBReportDataSource.h" #include "kexireportpart.h" #include #include #include #include #include #include #include #include class Q_DECL_HIDDEN KexiDBReportDataSource::Private { public: explicit Private(KexiReportPartTempData *data) : cursor(0), tempData(data), originalSchema(0), copySchema(0) { } ~Private() { delete copySchema; delete originalSchema; } QString objectName; KDbCursor *cursor; KexiReportPartTempData *tempData; KDbQuerySchema *originalSchema; KDbQuerySchema *copySchema; KDbEscapedString schemaSql; QList currentParams; }; KexiDBReportDataSource::KexiDBReportDataSource(const QString &objectName, const QString &pluginId, KexiReportPartTempData *data) : d(new Private(data)) { d->objectName = objectName; getSchema(pluginId); } void KexiDBReportDataSource::setSorting(const QList& sorting) { if (d->copySchema) { if (sorting.isEmpty()) return; KDbOrderByColumnList order; for (int i = 0; i < sorting.count(); i++) { if (!order.appendField(d->tempData->connection(), d->copySchema, sorting[i].field(), KDbOrderByColumn::fromQt(sorting[i].order()))) { qWarning() << "Cannot set sort field" << i << sorting[i].field(); return; } } d->copySchema->setOrderByColumnList(order); } else { qWarning() << "Unable to sort null schema"; } } void KexiDBReportDataSource::addCondition(const QString &field, const QVariant &value, const QString& relation) { if (d->copySchema) { KDbField *fld = d->copySchema->findTableField(field); if (fld) { if (relation.length() == 1) { QString errorMessage; QString errorDescription; if (!d->copySchema->addToWhereExpression(fld, value, KDbToken(relation.toLatin1()[0]), &errorMessage, &errorDescription)) { qWarning() << "Invalid expression cannot be added to WHERE:" << fld << relation << value; qWarning() << "addToWhereExpression() failed, message=" << errorMessage << "description=" << errorDescription; } } else { qWarning() << "Invalid relation passed in:" << relation; } } } else { qWarning() << "Unable to add expresstion to null schema"; } } KexiDBReportDataSource::~KexiDBReportDataSource() { close(); delete d; } bool KexiDBReportDataSource::open() { if ( d->tempData->connection() && d->cursor == 0 ) { if ( d->objectName.isEmpty() ) { return false; } else if ( d->copySchema) { //qDebug() << "Opening cursor.." // << KDbConnectionAndQuerySchema(d->tempData->connection(), *d->copySchema); bool ok; KexiUtils::WaitCursorRemover remover; d->currentParams = KexiQueryParameters::getParameters(0, d->tempData->connection(), d->originalSchema, &ok); if (!ok) { return false; } d->cursor = d->tempData->connection()->executeQuery(d->copySchema, d->currentParams, KDbCursor::Option::Buffered); } if ( d->cursor ) { //qDebug() << "Moving to first record.."; return d->cursor->moveFirst(); } else return false; } return false; } bool KexiDBReportDataSource::close() { if (d->cursor) { const bool ok = d->cursor->close(); d->tempData->connection()->deleteCursor(d->cursor); d->cursor = nullptr; return ok; } return true; } bool KexiDBReportDataSource::getSchema(const QString& pluginId) { if (d->tempData->connection()) { KDbTableSchemaChangeListener::unregisterForChanges(d->tempData->connection(), d->tempData); delete d->originalSchema; d->originalSchema = 0; delete d->copySchema; d->copySchema = 0; KDbTableSchema *table = nullptr; KDbQuerySchema *query = nullptr; if ((pluginId.isEmpty() || pluginId == "org.kexi-project.table") && (table = d->tempData->connection()->tableSchema(d->objectName))) { //qDebug() << d->objectName << "is a table.."; d->originalSchema = new KDbQuerySchema(table); } else if ((pluginId.isEmpty() || pluginId == "org.kexi-project.query") && (query = d->tempData->connection()->querySchema(d->objectName))) { //qDebug() << d->objectName << "is a query.."; //qDebug() << KDbConnectionAndQuerySchema(d->tempData->connection(), *query); d->originalSchema = new KDbQuerySchema(*query, d->tempData->connection()); } if (d->originalSchema) { const KDbNativeStatementBuilder builder(d->tempData->connection(), KDb::DriverEscaping); KDbEscapedString sql; if (builder.generateSelectStatement(&sql, d->originalSchema, d->currentParams)) { //qDebug() << "Original:" << sql; } else { qDebug() << "Original: ERROR"; return false; } //qDebug() << KDbConnectionAndQuerySchema(d->tempData->connection(), *d->originalSchema); d->copySchema = new KDbQuerySchema(*d->originalSchema, d->tempData->connection()); //qDebug() << KDbConnectionAndQuerySchema(d->tempData->connection(), *d->copySchema); if (builder.generateSelectStatement(&d->schemaSql, d->copySchema, d->currentParams)) { //qDebug() << "Copy:" << d->schemaSql; } else { qDebug() << "Copy: ERROR"; return false; } if (table) { KDbTableSchemaChangeListener::registerForChanges(d->tempData->connection(), d->tempData, table); } else if (query) { KDbTableSchemaChangeListener::registerForChanges(d->tempData->connection(), d->tempData, query); } } return true; } return false; } QString KexiDBReportDataSource::sourceName() const { return d->objectName; } int KexiDBReportDataSource::fieldNumber ( const QString &fld ) const { if (!d->cursor || !d->cursor->query()) { return -1; } const KDbQueryColumnInfo::Vector fieldsExpanded(d->cursor->query()->fieldsExpanded( d->tempData->connection(), KDbQuerySchema::FieldsExpandedMode::Unique)); for (int i = 0; i < fieldsExpanded.size(); ++i) { if (0 == QString::compare(fld, fieldsExpanded[i]->aliasOrName(), Qt::CaseInsensitive)) { return i; } } return -1; } QStringList KexiDBReportDataSource::fieldNames() const { if (!d->originalSchema) { return QStringList(); } QStringList names; const KDbQueryColumnInfo::Vector fieldsExpanded(d->originalSchema->fieldsExpanded( d->tempData->connection(), KDbQuerySchema::FieldsExpandedMode::Unique)); for (int i = 0; i < fieldsExpanded.size(); i++) { //! @todo in some Kexi mode captionOrAliasOrName() would be used here (more user-friendly) names.append(fieldsExpanded[i]->aliasOrName()); } return names; } QVariant KexiDBReportDataSource::value (int i) const { if ( d->cursor ) return d->cursor->value ( i ); return QVariant(); } QVariant KexiDBReportDataSource::value ( const QString &fld ) const { int i = fieldNumber ( fld ); if (d->cursor && i >= 0) return d->cursor->value ( i ); return QVariant(); } bool KexiDBReportDataSource::moveNext() { if ( d->cursor ) return d->cursor->moveNext(); return false; } bool KexiDBReportDataSource::movePrevious() { if ( d->cursor ) return d->cursor->movePrev(); return false; } bool KexiDBReportDataSource::moveFirst() { if ( d->cursor ) return d->cursor->moveFirst(); return false; } bool KexiDBReportDataSource::moveLast() { if ( d->cursor ) return d->cursor->moveLast(); return false; } qint64 KexiDBReportDataSource::at() const { if ( d->cursor ) return d->cursor->at(); return 0; } qint64 KexiDBReportDataSource::recordCount() const { if (d->copySchema) { return d->tempData->connection()->recordCount(d->copySchema); } return 1; } double KexiDBReportDataSource::runAggregateFunction(const QString &function, const QString &field, const QMap &conditions) { double numberResult = 0.0; if (d->schemaSql.isEmpty()) { qWarning() << "No query for running aggregate function" << function << field; return numberResult; } KDbEscapedString whereSql; KDbConnection *conn = d->tempData->connection(); if (!conditions.isEmpty()) { for (QMap::ConstIterator it = conditions.constBegin(); it != conditions.constEnd(); ++it) { if (!whereSql.isEmpty()) { whereSql.append(" AND "); } KDbQueryColumnInfo *cinfo = d->copySchema->columnInfo(conn, it.key()); if (!cinfo) { qWarning() << "Could not find column" << it.key() << "for condition" << it.key() << "=" << it.value(); return numberResult; } whereSql.append( KDbEscapedString(d->tempData->connection()->escapeIdentifier(cinfo->aliasOrName())) + " = " + d->tempData->connection()->driver()->valueToSql(cinfo->field(), it.value())); } whereSql.prepend(" WHERE "); } const KDbEscapedString sql = KDbEscapedString("SELECT " + function + "(" + field + ") FROM (" + d->schemaSql + ")" + whereSql); QString stringResult; const tristate res = d->tempData->connection()->querySingleString(sql, &stringResult); if (res != true) { qWarning() << "Failed to execute query for running aggregate function" << function << field; return numberResult; } bool ok; numberResult = stringResult.toDouble(&ok); if (!ok) { qWarning() << "Result of query for running aggregate function" << function << field << "is not a number (" << stringResult << ")"; return numberResult; } return numberResult; } QStringList KexiDBReportDataSource::dataSourceNames() const { //Get the list of queries in the database QStringList qs; if (d->tempData->connection() && d->tempData->connection()->isConnected()) { QList tids = d->tempData->connection()->tableIds(); qs << ""; for (int i = 0; i < tids.size(); ++i) { KDbTableSchema* tsc = d->tempData->connection()->tableSchema(tids[i]); if (tsc) qs << tsc->name(); } QList qids = d->tempData->connection()->queryIds(); qs << ""; for (int i = 0; i < qids.size(); ++i) { KDbQuerySchema* qsc = d->tempData->connection()->querySchema(qids[i]); if (qsc) qs << qsc->name(); } } return qs; } KReportDataSource* KexiDBReportDataSource::create(const QString& source) const { return new KexiDBReportDataSource(source, QString(), d->tempData); } diff --git a/src/plugins/reports/KexiDBReportDataSource.h b/src/plugins/reports/KexiDBReportDataSource.h index 27f3bc9b7..653f89aab 100644 --- a/src/plugins/reports/KexiDBReportDataSource.h +++ b/src/plugins/reports/KexiDBReportDataSource.h @@ -1,96 +1,96 @@ /* * Kexi Report Plugin * Copyright (C) 2007-2017 by Adam Pigg * Copyright (C) 2017-2018 Jarosław Staniek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public -* License along with this library. If not, see . +* License along with this library. If not, see . */ #ifndef __KEXIDBREPORTDATA_H__ #define __KEXIDBREPORTDATA_H__ #include #include #include class KexiReportPartTempData; //! @brief Implementation of database report data source class KexiDBReportDataSource : public KReportDataSource { public: /*! * @a pluginId specifies type of @a objectName, a table or query. * Types accepted: * -"org.kexi-project.table" * -"org.kexi-project.query" * -empty QString() - attempt to resolve @a objectName */ KexiDBReportDataSource(const QString &objectName, const QString &pluginId, KexiReportPartTempData *data); virtual ~KexiDBReportDataSource(); virtual QStringList fieldNames() const override; virtual void setSorting(const QList& sorting) override; //! Adds a condition to the data source. //! @note Only single-character relation operators such as "=" or ">" are supported now. //! @todo Use KDb parser to support all relation operators such as ">=". virtual void addCondition(const QString &field, const QVariant &value, const QString &relation = QLatin1String("=")) override; virtual QString sourceName() const override; virtual int fieldNumber(const QString &field) const override; virtual QVariant value(int) const override; virtual QVariant value(const QString &field) const override; virtual bool open() override; virtual bool close() override; virtual bool moveNext() override; virtual bool movePrevious() override; virtual bool moveFirst() override; virtual bool moveLast() override; virtual qint64 at() const override; virtual qint64 recordCount() const override; /** * Runs aggregate function @a function on the data source * * @param function name such as max, min, avg * @param field name of field for which the aggregation should be executed * @param conditions optional conditions that limit the record set * @return value of the function, 0.0 on failure * * @warning SQL injection warning: validity of @a function name is not checked, this should not * be part of a public API. * @todo Move SQL aggregate functions to KDb. Current code depends on support for subqueries. */ double runAggregateFunction(const QString &function, const QString &field, const QMap &conditions); //Utility Functions virtual QStringList dataSourceNames() const override; virtual Q_REQUIRED_RESULT KReportDataSource *create(const QString &source) const override; private: class Private; Private * const d; bool getSchema(const QString& pluginId); }; #endif diff --git a/src/plugins/reports/keximigratereportdata.cpp b/src/plugins/reports/keximigratereportdata.cpp index dab5a6b7b..d9eec5c88 100644 --- a/src/plugins/reports/keximigratereportdata.cpp +++ b/src/plugins/reports/keximigratereportdata.cpp @@ -1,191 +1,191 @@ /* * Kexi Report Plugin * Copyright (C) 2007-2009 by Adam Pigg (adam@piggz.co.uk) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public -* License along with this library. If not, see . +* License along with this library. If not, see . */ #include "keximigratereportdata.h" #include class Q_DECL_HIDDEN KexiMigrateReportData::Private { public: Private() : schema(0) , kexiMigrate(0) , position(0) { } ~Private() { if (kexiMigrate) { delete kexiMigrate; kexiMigrate = 0; } if (schema) { delete schema; schema = 0; } } QString qstrName; QString qstrQuery; bool valid; KDbTableSchema tableSchema; KDbTableOrQuerySchema *schema; KexiMigration::KexiMigrate *kexiMigrate; qint64 position; }; //!Connect to an external data source //!connStr is in the form driver|connection_string|table KexiMigrateReportData::KexiMigrateReportData(const QString & connStr) : d(new Private) { QStringList extConn = connStr.split('|'); if (extConn.size() == 3) { KexiMigration::MigrateManager mm; d->kexiMigrate = mm.driver(extConn[0]); KDbConnectionData cd; KexiMigration::Data dat; cd.setFileName(extConn[1]); dat.source = &cd; d->kexiMigrate->setData(&dat); d->valid = d->kexiMigrate->connectSource(); QStringList names; if (d->valid) { d->valid = d->kexiMigrate->readTableSchema(extConn[2], d->tableSchema); } if (d->valid) { d->schema = new KDbTableOrQuerySchema(d->tableSchema); } d->valid = d->kexiMigrate->tableNames(names); if (d->valid && names.contains(extConn[2])) { d->valid = d->kexiMigrate->readFromTable(extConn[2]); } } } KexiMigrateReportData::~KexiMigrateReportData() { delete d; } int KexiMigrateReportData::fieldNumber(const QString &fld) const { KDbQueryColumnInfo::Vector flds; int x = -1; if (d->schema) { flds = d->schema->columns(); for (int i = 0; i < flds.size() ; ++i) { if (fld.toLower() == flds[i]->aliasOrName().toLower()) { x = i; } } } return x; } QStringList KexiMigrateReportData::fieldNames() const { KDbQueryColumnInfo::Vector flds; QStringList names; if (d->schema) { flds = d->schema->columns(); for (int i = 0; i < flds.size() ; ++i) { names << flds[i]->field->name(); } } return names; } QVariant KexiMigrateReportData::value(unsigned int i) const { if (!d->valid) return QVariant(); return d->kexiMigrate->value(i); } QVariant KexiMigrateReportData::value(const QString &fld) const { if (!d->valid) return QVariant(); int i = fieldNumber(fld); return d->kexiMigrate->value(i); } bool KexiMigrateReportData::moveNext() { if (!d->valid) return false; d->position++; return d->kexiMigrate->moveNext(); } bool KexiMigrateReportData::movePrevious() { if (!d->valid) return false; if (d->position > 0) d->position--; return d->kexiMigrate->movePrevious(); } bool KexiMigrateReportData::moveFirst() { if (!d->valid) return false; d->position = 1; return d->kexiMigrate->moveFirst(); } bool KexiMigrateReportData::moveLast() { if (!d->valid) return false; return d->kexiMigrate->moveLast(); } qint64 KexiMigrateReportData::at() const { return d->position; } qint64 KexiMigrateReportData::recordCount() const { return 1; } diff --git a/src/plugins/reports/keximigratereportdata.h b/src/plugins/reports/keximigratereportdata.h index 720b0ada1..7bd6837ae 100644 --- a/src/plugins/reports/keximigratereportdata.h +++ b/src/plugins/reports/keximigratereportdata.h @@ -1,66 +1,66 @@ /* * Kexi Report Plugin * Copyright (C) 2007-2009 by Adam Pigg (adam@piggz.co.uk) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public -* License along with this library. If not, see . +* License along with this library. If not, see . */ #ifndef __KEXIMIGRATEREPORTDATA_H__ #define __KEXIMIGRATEREPORTDATA_H__ #include #include #include #include #include #include class KexiMigrateReportData : public KoReportData { public: explicit KexiMigrateReportData(const QString &); virtual ~KexiMigrateReportData(); virtual int fieldNumber(const QString &field) const; virtual QStringList fieldNames() const; virtual QVariant value(unsigned int) const; virtual QVariant value(const QString &field) const; virtual bool open() { return true; } virtual bool close() { return true; } virtual bool moveNext(); virtual bool movePrevious(); virtual bool moveFirst(); virtual bool moveLast(); virtual qint64 at() const; virtual qint64 recordCount() const; private: class Private; Private * const d; }; #endif diff --git a/src/plugins/reports/kexireportdesignview.cpp b/src/plugins/reports/kexireportdesignview.cpp index 98c53131a..c4c5cd616 100644 --- a/src/plugins/reports/kexireportdesignview.cpp +++ b/src/plugins/reports/kexireportdesignview.cpp @@ -1,267 +1,267 @@ /* * Kexi Report Plugin * Copyright (C) 2007-2009 by Adam Pigg * Copyright (C) 2011-2017 Jarosław Staniek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public -* License along with this library. If not, see . +* License along with this library. If not, see . */ #include "kexireportdesignview.h" #include #include #include "kexisourceselector.h" #include #include #include #include #include #include #include #include KexiReportDesignView::KexiReportDesignView(QWidget *parent, KexiSourceSelector *s) : KexiView(parent) { m_scrollArea = new QScrollArea(this); setViewWidget(m_scrollArea); m_sourceSelector = s; m_reportDesigner = 0; m_editCutAction = KStandardAction::cut(this); m_editCutAction->setProperty("iconOnly", true); m_editCopyAction = KStandardAction::copy(this); m_editCopyAction->setProperty("iconOnly", true); m_editPasteAction = KStandardAction::paste(this); m_editPasteAction->setProperty("iconOnly", true); const KGuiItem del = KStandardGuiItem::del(); m_editDeleteAction = new QAction(del.icon(), del.text(), this); m_editDeleteAction->setObjectName("editdelete"); m_editDeleteAction->setToolTip(del.toolTip()); m_editDeleteAction->setWhatsThis(del.whatsThis()); m_editDeleteAction->setProperty("iconOnly", true); m_editSectionAction = new QAction(xi18n("Edit Sections"), this); m_editSectionAction->setObjectName("sectionedit"); m_itemRaiseAction = new QAction(koIcon("object-order-front"), xi18n("Raise"), this); m_itemRaiseAction->setObjectName("itemraise"); m_itemLowerAction = new QAction(koIcon("object-order-back"), xi18n("Lower"), this); m_itemLowerAction->setObjectName("itemlower"); QList al; QAction *sep = new QAction(QString(), this); sep->setSeparator(true); al << m_editCutAction << m_editCopyAction << m_editPasteAction << m_editDeleteAction << sep << m_editSectionAction << sep << m_itemLowerAction << m_itemRaiseAction; setViewActions(al); } KexiReportDesignView::~KexiReportDesignView() { } KPropertySet *KexiReportDesignView::propertySet() { return m_reportDesigner->selectedItemPropertySet(); } //! Finds or creates property static KProperty* findOrCreateProperty(KPropertySet *set, const char *name, const QVariant &value) { KProperty *prop; if (set->contains(name)) { prop = &set->property(name); prop->setValue(value); } else { prop = new KProperty(name, value); prop->setVisible(false); set->addProperty(prop); } return prop; } void KexiReportDesignView::slotDesignerPropertySetChanged() { KPropertySet *set = propertySet(); if (set) { KProperty *prop = findOrCreateProperty(set, "this:visibleObjectNameProperty", "name"); Q_UNUSED(prop) } propertySetReloaded(true); propertySetSwitched(); } KDbObject* KexiReportDesignView::storeNewData(const KDbObject& object, KexiView::StoreNewDataOptions options, bool *cancel) { KDbObject *s = KexiView::storeNewData(object, options, cancel); if (!s || *cancel) { delete s; return 0; } //qDebug() << "new id:" << s->id(); if (!storeData()) { //failure: remove object's object data to avoid garbage KDbConnection *conn = KexiMainWindowIface::global()->project()->dbConnection(); conn->removeObject(s->id()); delete s; return 0; } return s; } tristate KexiReportDesignView::storeData(bool dontAsk) { Q_UNUSED(dontAsk); QDomDocument doc("kexireport"); QDomElement root = doc.createElement("kexireport"); QDomElement conndata = connectionData(); if (conndata.isNull()) { //qDebug() << "Null conn data!"; } root.appendChild(m_reportDesigner->document()); root.appendChild(conndata); doc.appendChild(root); QString src = doc.toString(); //qDebug() << src; if (storeDataBlock(src, "layout")) { //qDebug() << "Saved OK"; setDirty(false); return true; } //qDebug() << "NOT Saved OK"; return false; } tristate KexiReportDesignView::beforeSwitchTo(Kexi::ViewMode mode, bool *dontStore) { //qDebug() << mode; *dontStore = true; if (m_reportDesigner && mode == Kexi::DataViewMode) { //qDebug() << "Saving temp data"; tempData()->reportDefinition = m_reportDesigner->document(); //qDebug() << m_reportDesigner->document().toDocument().toString(); tempData()->reportSchemaChangedInPreviousView = true; } return true; } tristate KexiReportDesignView::afterSwitchFrom(Kexi::ViewMode mode) { Q_UNUSED(mode); if (tempData()->reportDefinition.isNull()) { m_reportDesigner = new KReportDesigner(this); } else { if (m_reportDesigner) { m_scrollArea->takeWidget(); delete m_reportDesigner; m_reportDesigner = 0; } m_reportDesigner = new KReportDesigner(this, tempData()->reportDefinition); setConnectionData(tempData()->connectionDefinition); m_reportDesigner->setScriptSource(qobject_cast(part())); } connect(m_reportDesigner, SIGNAL(itemInserted(QString)), this, SIGNAL(itemInserted(QString))); m_scrollArea->setWidget(m_reportDesigner); connect(m_reportDesigner, SIGNAL(propertySetChanged()), this, SLOT(slotDesignerPropertySetChanged())); connect(m_reportDesigner, SIGNAL(dirty()), this, SLOT(setDirty())); //Added default keyboard shortcuts for the actions QShortcut *cutShortcut = new QShortcut(QKeySequence(QKeySequence::Cut), m_reportDesigner); QShortcut *copyShortcut = new QShortcut(QKeySequence(QKeySequence::Copy), m_reportDesigner); QShortcut *pasteShortcut = new QShortcut(QKeySequence(QKeySequence::Paste), m_reportDesigner); QShortcut *deleteShortcut = new QShortcut(QKeySequence(QKeySequence::Delete), m_reportDesigner); connect(cutShortcut, SIGNAL(activated()), m_reportDesigner, SLOT(slotEditCut())); connect(copyShortcut, SIGNAL(activated()), m_reportDesigner, SLOT(slotEditCopy())); connect(pasteShortcut, SIGNAL(activated()), m_reportDesigner, SLOT(slotEditPaste())); connect(deleteShortcut, SIGNAL(activated()), m_reportDesigner, SLOT(slotEditDelete())); //Edit Actions connect(m_editCutAction, SIGNAL(triggered()), m_reportDesigner, SLOT(slotEditCut())); connect(m_editCopyAction, SIGNAL(triggered()), m_reportDesigner, SLOT(slotEditCopy())); connect(m_editPasteAction, SIGNAL(triggered()), m_reportDesigner, SLOT(slotEditPaste())); connect(m_editDeleteAction, SIGNAL(triggered()), m_reportDesigner, SLOT(slotEditDelete())); connect(m_editSectionAction, SIGNAL(triggered()), m_reportDesigner, SLOT(slotSectionEditor())); //Raise/Lower connect(m_itemRaiseAction, SIGNAL(triggered()), m_reportDesigner, SLOT(slotRaiseSelected())); connect(m_itemLowerAction, SIGNAL(triggered()), m_reportDesigner, SLOT(slotLowerSelected())); return true; } KexiReportPartTempData* KexiReportDesignView::tempData() const { return static_cast(window()->data()); } void KexiReportDesignView::slotDataSourceChanged() { if (m_sourceSelector->isSelectionValid()) { m_reportDesigner->setDataSource(new KexiDBReportDataSource( m_sourceSelector->selectedName(), m_sourceSelector->selectedPluginId(), tempData())); tempData()->connectionDefinition = connectionData(); } else { m_reportDesigner->setDataSource(nullptr); tempData()->connectionDefinition = QDomElement(); } setDirty(true); } void KexiReportDesignView::triggerAction(const QString &action) { m_reportDesigner->slotItem(action); } QDomElement KexiReportDesignView::connectionData() const { QDomDocument dd; QDomElement conndata = dd.createElement("connection"); conndata.setAttribute("type", "internal"); // for backward compatibility, currently always // internal, we used to have "external" in old KEXI conndata.setAttribute("source", m_sourceSelector->selectedName()); conndata.setAttribute("class", m_sourceSelector->selectedPluginId()); return conndata; } void KexiReportDesignView::setConnectionData(const QDomElement &c) { //qDebug() << c; if (c.attribute("type") == "internal") { QString sourceClass(c.attribute("class")); if (sourceClass != "org.kexi-project.table" && sourceClass != "org.kexi-project.query") { sourceClass.clear(); // KexiDataSourceComboBox will try to find table, then query } m_sourceSelector->setDataSource(sourceClass, c.attribute("source")); slotDataSourceChanged(); } } diff --git a/src/plugins/reports/kexireportdesignview.h b/src/plugins/reports/kexireportdesignview.h index 305643baf..395f6db32 100644 --- a/src/plugins/reports/kexireportdesignview.h +++ b/src/plugins/reports/kexireportdesignview.h @@ -1,84 +1,84 @@ /* * Kexi Report Plugin * Copyright (C) 2007-2009 by Adam Pigg * Copyright (C) 2011-2017 Jarosław Staniek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public -* License along with this library. If not, see . +* License along with this library. If not, see . */ #ifndef KEXIREPORTDESIGNVIEW_H #define KEXIREPORTDESIGNVIEW_H #include #include #include #include #include class QScrollArea; class KexiSourceSelector; /** */ class KexiReportDesignView : public KexiView { Q_OBJECT public: KexiReportDesignView(QWidget *parent, KexiSourceSelector*); ~KexiReportDesignView(); virtual tristate afterSwitchFrom(Kexi::ViewMode mode) override; virtual tristate beforeSwitchTo(Kexi::ViewMode mode, bool *dontStore) override; void triggerAction(const QString &); Q_SIGNALS: void itemInserted(const QString& entity); private: KexiReportPartTempData* tempData() const; QDomElement connectionData() const; void setConnectionData(const QDomElement &c); KReportDesigner *m_reportDesigner; QScrollArea * m_scrollArea; //Actions QAction *m_editCutAction; QAction *m_editCopyAction; QAction *m_editPasteAction; QAction *m_editDeleteAction; QAction *m_editSectionAction; QAction *m_itemRaiseAction; QAction *m_itemLowerAction; KexiSourceSelector *m_sourceSelector; protected: virtual KPropertySet *propertySet() override; virtual tristate storeData(bool dontAsk = false) override; virtual KDbObject* storeNewData(const KDbObject& object, KexiView::StoreNewDataOptions options, bool *cancel) override; private Q_SLOTS: void slotDesignerPropertySetChanged(); public Q_SLOTS: void slotDataSourceChanged(); }; #endif diff --git a/src/plugins/reports/kexireportpart.cpp b/src/plugins/reports/kexireportpart.cpp index a59d89782..9a5e6b770 100644 --- a/src/plugins/reports/kexireportpart.cpp +++ b/src/plugins/reports/kexireportpart.cpp @@ -1,338 +1,338 @@ /* * Kexi Report Plugin * Copyright (C) 2007-2008 by Adam Pigg * Copyright (C) 2011-2017 Jarosław Staniek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library. If not, see . + * License along with this library. If not, see . */ #include "kexireportpart.h" #include #include #include #include #include #include "kexireportview.h" #include "kexireportdesignview.h" #include #include #include "kexisourceselector.h" #include #include //! @internal class Q_DECL_HIDDEN KexiReportPart::Private { public: Private() : toolboxActionGroup(0) { sourceSelector = 0; } ~Private() { } KexiSourceSelector *sourceSelector; QActionGroup toolboxActionGroup; QMap toolboxActionsByName; }; static bool isInterpreterSupported(const QString &interpreterName) { return 0 == interpreterName.compare(QLatin1String("javascript"), Qt::CaseInsensitive) || 0 == interpreterName.compare(QLatin1String("qtscript"), Qt::CaseInsensitive); } KexiReportPart::KexiReportPart(QObject *parent, const QVariantList &l) : KexiPart::Part(parent, xi18nc("Translate this word using only lowercase alphanumeric characters (a..z, 0..9). " "Use '_' character instead of spaces. First character should be a..z character. " "If you cannot use latin characters in your language, use english word.", "report"), xi18nc("tooltip", "Create new report"), xi18nc("what's this", "Creates new report."), l) , d(new Private) { setInternalPropertyValue("newObjectsAreDirty", true); // needed for custom "pixmap" property editor widget KexiCustomPropertyFactory::init(); } KexiReportPart::~KexiReportPart() { delete d; } KLocalizedString KexiReportPart::i18nMessage( const QString& englishMessage, KexiWindow* window) const { Q_UNUSED(window); if (englishMessage == "Design of object %1 has been modified.") return kxi18nc(I18NC_NOOP("@info", "Design of report %1 has been modified.")); if (englishMessage == "Object %1 already exists.") return kxi18nc(I18NC_NOOP("@info", "Report %1 already exists.")); return Part::i18nMessage(englishMessage, window); } KexiView* KexiReportPart::createView(QWidget *parent, KexiWindow* window, KexiPart::Item *item, Kexi::ViewMode viewMode, QMap*) { Q_ASSERT(item); Q_UNUSED(window); Q_UNUSED(item); KexiView* view = 0; if (viewMode == Kexi::DataViewMode) { view = new KexiReportView(parent); } else if (viewMode == Kexi::DesignViewMode) { view = new KexiReportDesignView(parent, d->sourceSelector); connect(d->sourceSelector, &KexiSourceSelector::dataSourceChanged, qobject_cast(view), &KexiReportDesignView::slotDataSourceChanged); connect(view, SIGNAL(itemInserted(QString)), this, SLOT(slotItemInserted(QString))); } return view; } void KexiReportPart::initPartActions() { KexiMainWindowIface *win = KexiMainWindowIface::global(); QList reportActions = KReportDesigner::itemActions(&d->toolboxActionGroup); foreach(QAction* action, reportActions) { connect(action, SIGNAL(triggered(bool)), this, SLOT(slotToolboxActionTriggered(bool))); win->addToolBarAction("report", action); d->toolboxActionsByName.insert(action->objectName(), action); } } KDbObject* KexiReportPart::loadSchemaObject( KexiWindow *window, const KDbObject& object, Kexi::ViewMode viewMode, bool *ownedByWindow) { Q_ASSERT(ownedByWindow); QString layout; if ( !loadDataBlock(window, &layout, "layout") == true && !loadDataBlock(window, &layout, "pgzreport_layout") == true /* compat */) { return 0; } QDomDocument doc; if (!doc.setContent(layout)) { return 0; } KexiReportPartTempData * temp = static_cast(window->data()); const QDomElement root = doc.documentElement(); temp->reportDefinition = root.firstChildElement("report:content"); if (temp->reportDefinition.isNull()) { qWarning() << "no report report:content element found in report" << window->partItem()->name(); return 0; } temp->connectionDefinition = root.firstChildElement("connection"); if (temp->connectionDefinition.isNull()) { qWarning() << "no report report:connection element found in report" << window->partItem()->name(); return 0; } return KexiPart::Part::loadSchemaObject(window, object, viewMode, ownedByWindow); } KexiWindowData* KexiReportPart::createWindowData(KexiWindow* window) { KexiMainWindowIface *win = KexiMainWindowIface::global(); return new KexiReportPartTempData(window, win->project()->dbConnection()); } //---------------- class Q_DECL_HIDDEN KexiReportPartTempData::Private { public: Private() { } KDbConnection *conn; }; KexiReportPartTempData::KexiReportPartTempData(KexiWindow* parent, KDbConnection *conn) : KexiWindowData(parent) , reportSchemaChangedInPreviousView(true /*to force reloading on startup*/) , d(new Private) { d->conn = conn; setName(KexiUtils::localizedStringToHtmlSubstring( kxi18nc("@info", "Report %1").subs(parent->partItem()->name()))); } KexiReportPartTempData::~KexiReportPartTempData() { KDbTableSchemaChangeListener::unregisterForChanges(d->conn, this); delete d; } KDbConnection* KexiReportPartTempData::connection() { return d->conn; } tristate KexiReportPartTempData::closeListener() { KexiWindow* window = static_cast(parent()); qDebug() << window->partItem()->name(); return KexiMainWindowIface::global()->closeWindow(window); } void KexiReportPart::setupPropertyPane(KexiPropertyPaneWidget *pane) { if (!d->sourceSelector) { d->sourceSelector = new KexiSourceSelector(KexiMainWindowIface::global()->project()); } pane->addSection(d->sourceSelector, xi18n("Data source")); } void KexiReportPart::slotToolboxActionTriggered(bool checked) { if (!checked) return; QObject *theSender = sender(); if (!theSender) return; QString senderName = sender()->objectName(); KexiMainWindowIface *mainwin = KexiMainWindowIface::global(); KexiWindow *win = mainwin->currentWindow(); if (!win) return; KexiView *designView = win->viewForMode(Kexi::DesignViewMode); if (designView) { KexiReportDesignView *dv = dynamic_cast(designView); if (!dv) return; dv->triggerAction(senderName); } } void KexiReportPart::slotItemInserted(const QString& entity) { Q_UNUSED(entity); // uncheck toolbox action after it is used QAction * a = d->toolboxActionGroup.checkedAction(); if (a) { a->setChecked(false); } } QStringList KexiReportPart::scriptList() const { QStringList scripts; KexiMainWindowIface *win = KexiMainWindowIface::global(); if (win->project() && win->project()->dbConnection()) { QList scriptids = win->project()->dbConnection()->objectIds(KexiPart::ScriptObjectType); QStringList scriptnames = win->project()->dbConnection()->objectNames(KexiPart::ScriptObjectType); qDebug() << scriptids << scriptnames; int i = 0; foreach(int id, scriptids) { qDebug() << "ID:" << id; tristate res; QString script; res = win->project()->dbConnection()->loadDataBlock(id, &script, QString()); if (res == true) { QDomDocument domdoc; bool parsed = domdoc.setContent(script, false); QDomElement scriptelem = domdoc.namedItem("script").toElement(); if (parsed && !scriptelem.isNull()) { if (scriptelem.attribute("scripttype") == "object" && isInterpreterSupported(scriptelem.attribute("language"))) { scripts << scriptnames[i]; } } else { qWarning() << "Unable to parse script"; } } else { qWarning() << "Unable to loadDataBlock"; } ++i; } qDebug() << scripts; } return scripts; } QString KexiReportPart::scriptCode(const QString& scriptname) const { QString scripts; KexiMainWindowIface *win = KexiMainWindowIface::global(); if (win->project() && win->project()->dbConnection()) { QList scriptids = win->project()->dbConnection()->objectIds(KexiPart::ScriptObjectType); QStringList scriptnames = win->project()->dbConnection()->objectNames(KexiPart::ScriptObjectType); int i = 0; foreach(int id, scriptids) { qDebug() << "ID:" << id; tristate res; QString script; res = win->project()->dbConnection()->loadDataBlock(id, &script, QString()); if (res == true) { QDomDocument domdoc; bool parsed = domdoc.setContent(script, false); if (! parsed) { qWarning() << "XML parsing error"; return QString(); } QDomElement scriptelem = domdoc.namedItem("script").toElement(); if (scriptelem.isNull()) { qWarning() << "script domelement is null"; return QString(); } QString interpretername = scriptelem.attribute("language"); qDebug() << scriptelem.attribute("scripttype"); qDebug() << scriptname << scriptnames[i]; if ((isInterpreterSupported(interpretername) && scriptelem.attribute("scripttype") == "module") || scriptname == scriptnames[i]) { scripts += '\n' + scriptelem.text().toUtf8(); } ++i; } else { qWarning() << "Unable to loadDataBlock"; } } } return scripts; } diff --git a/src/plugins/reports/kexireportpart.h b/src/plugins/reports/kexireportpart.h index 6cef97f6e..4f1b445e1 100644 --- a/src/plugins/reports/kexireportpart.h +++ b/src/plugins/reports/kexireportpart.h @@ -1,106 +1,106 @@ /* * Kexi Report Plugin * Copyright (C) 2007-2008 by Adam Pigg * Copyright (C) 2011-2017 Jarosław Staniek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library. If not, see . + * License along with this library. If not, see . */ #ifndef _KEXIREPORTPART_H_ #define _KEXIREPORTPART_H_ #include #include #include #include #include #include class KexiReportPartTempData : public KexiWindowData, public KDbTableSchemaChangeListener { Q_OBJECT public: KexiReportPartTempData(KexiWindow* parent, KDbConnection *conn); ~KexiReportPartTempData(); QDomElement reportDefinition; QDomElement connectionDefinition; /*! true, if \a document member has changed in previous view. Used on view switching. Check this flag to see if we should refresh data for DataViewMode. */ bool reportSchemaChangedInPreviousView; KDbConnection *connection(); protected: //! This temp-data acts as a listener for tracking changes in table schema //! used by the report. This method closes the report on request. tristate closeListener() override; private: Q_DISABLE_COPY(KexiReportPartTempData) class Private; Private * const d; }; /** * @short Application Main Window */ class KexiReportPart : public KexiPart::Part, public KReportScriptSource { Q_OBJECT public: /** * Default Constructor */ KexiReportPart(QObject *parent, const QVariantList &l); /** * Default Destructor */ virtual ~KexiReportPart(); virtual void setupPropertyPane(KexiPropertyPaneWidget *pane) override; virtual KLocalizedString i18nMessage(const QString& englishMessage, KexiWindow* window) const override; QStringList scriptList() const override; QString scriptCode(const QString& script) const override; protected: Q_REQUIRED_RESULT KexiView *createView(QWidget *parent, KexiWindow *win, KexiPart::Item *item, Kexi::ViewMode = Kexi::DataViewMode, QMap *staticObjectArgs = nullptr) override; Q_REQUIRED_RESULT KexiWindowData *createWindowData(KexiWindow* window) override; virtual void initPartActions() override; virtual KDbObject* loadSchemaObject(KexiWindow *window, const KDbObject& object, Kexi::ViewMode viewMode, bool *ownedByWindow) override; private Q_SLOTS: void slotToolboxActionTriggered(bool checked); //! Unchecks toolbox action for @a entity after it is used. void slotItemInserted(const QString& entity); private: class Private; Private* d; }; #endif // _KEXIREPORTPART_H_ diff --git a/src/plugins/reports/kexireports.cpp b/src/plugins/reports/kexireports.cpp index c005c2c95..68dd29454 100644 --- a/src/plugins/reports/kexireports.cpp +++ b/src/plugins/reports/kexireports.cpp @@ -1,23 +1,23 @@ /* * Kexi Report Plugin * Copyright (C) 2007-2008 by Adam Pigg (adam@piggz.co.uk) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library. If not, see . + * License along with this library. If not, see . */ #include "kexireportpart.h" KEXI_PLUGIN_FACTORY(KexiReportPart, "kexi_reportplugin.json") #include "kexireports.moc" diff --git a/src/plugins/reports/kexireportview.cpp b/src/plugins/reports/kexireportview.cpp index b57057b5d..ec1aa3363 100644 --- a/src/plugins/reports/kexireportview.cpp +++ b/src/plugins/reports/kexireportview.cpp @@ -1,480 +1,480 @@ /* * Kexi Report Plugin * Copyright (C) 2007-2008 by Adam Pigg (adam@piggz.co.uk) * Copyright (C) 2014-2018 Jarosław Staniek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library. If not, see . + * License along with this library. If not, see . */ #include "kexireportview.h" #include #include "KexiDBReportDataSource.h" #ifndef KEXI_MOBILE #include #endif #include #include #include #include //! @todo KEXI3 include "../scripting/kexiscripting/kexiscriptadaptor.h" #include #include #include #include #include "krscriptfunctions.h" #include #include #include #include #include #include #include #include #include #include #include #include #include KexiReportView::KexiReportView(QWidget *parent) : KexiView(parent), m_preRenderer(0), m_functions(0) //! @todo KEXI3, m_kexi(0) { setObjectName("KexiReportDesigner_DataView"); m_reportView = new KReportView(this); setViewWidget(m_reportView); KexiStyle::setupFrame(m_reportView->scrollArea()); #ifndef KEXI_MOBILE m_pageSelector = new KexiRecordNavigator(*m_reportView->scrollArea(), m_reportView); m_pageSelector->setInsertingButtonVisible(false); m_pageSelector->setInsertingEnabled(false); m_pageSelector->setLabelText(xi18nc("Page selector label", "Page:")); m_pageSelector->setButtonToolTipText(KexiRecordNavigator::ButtonFirst, xi18n("Go to first page")); m_pageSelector->setButtonWhatsThisText(KexiRecordNavigator::ButtonFirst, xi18n("Goes to first page")); m_pageSelector->setButtonToolTipText(KexiRecordNavigator::ButtonPrevious, xi18n("Go to previous page")); m_pageSelector->setButtonWhatsThisText(KexiRecordNavigator::ButtonPrevious, xi18n("Goes to previous page")); m_pageSelector->setButtonToolTipText(KexiRecordNavigator::ButtonNext, xi18n("Go to next page")); m_pageSelector->setButtonWhatsThisText(KexiRecordNavigator::ButtonNext, xi18n("Goes to next page")); m_pageSelector->setButtonToolTipText(KexiRecordNavigator::ButtonLast, xi18n("Go to last page")); m_pageSelector->setButtonWhatsThisText(KexiRecordNavigator::ButtonLast, xi18n("Goes to last page")); m_pageSelector->setNumberFieldToolTips(xi18n("Current page number"), xi18n("Number of pages")); m_pageSelector->setRecordHandler(this); #endif // -- setup local actions QList viewActions; QAction* a; #ifndef KEXI_MOBILE viewActions << (a = new QAction(koIcon("document-print"), xi18n("Print"), this)); a->setObjectName("print_report"); a->setToolTip(xi18n("Print report")); a->setWhatsThis(xi18n("Prints the current report.")); connect(a, SIGNAL(triggered()), this, SLOT(slotPrintReport())); KActionMenu *exportMenu = new KActionMenu(koIcon("document-export"), xi18nc("@title:menu","E&xport As"), this); exportMenu->setObjectName("report_export_as"); exportMenu->setDelayed(false); #endif #ifdef KEXI_SHOW_UNFINISHED #ifdef KEXI_MOBILE viewActions << (a = new QAction(xi18n("Export:"), this)); a->setEnabled(false); //!TODO this is a bit of a dirty way to add what looks like a label to the toolbar! // " ", not "", is said to be needed in maemo, the icon didn't display properly without it viewActions << (a = new QAction(koIcon("application-vnd.oasis.opendocument.text"), QLatin1String(" "), this)); #else exportMenu->addAction(a = new QAction(koIcon("application-vnd.oasis.opendocument.text"), xi18nc("open dialog to export as text document", "Text Document..."), this)); #endif a->setObjectName("export_as_text_document"); a->setToolTip(xi18n("Export the report as a text document (in OpenDocument Text format)")); a->setWhatsThis(xi18n("Exports the report as a text document (in OpenDocument Text format).")); a->setEnabled(true); connect(a, SIGNAL(triggered()), this, SLOT(slotExportAsTextDocument())); #endif #ifdef KEXI_MOBILE viewActions << (a = new QAction(koIcon("application-pdf"), QLatin1String(" "), this)); #else exportMenu->addAction(a = new QAction(koIcon("application-pdf"), xi18nc("Portable Document Format...", "PDF..."), this)); #endif a->setObjectName("export_as_pdf"); a->setToolTip(xi18n("Export as PDF")); a->setWhatsThis(xi18n("Exports the current report as PDF.")); a->setEnabled(true); connect(a, SIGNAL(triggered()), this, SLOT(slotExportAsPdf())); #ifdef KEXI_SHOW_UNFINISHED #ifdef KEXI_MOBILE viewActions << (a = new QAction(koIcon("application-vnd.oasis.opendocument.spreadsheet"), QLatin1String(" "), this)); #else exportMenu->addAction(a = new QAction(koIcon("application-vnd.oasis.opendocument.spreadsheet"), xi18nc("open dialog to export as spreadsheet", "Spreadsheet..."), this)); #endif a->setObjectName("export_as_spreadsheet"); a->setToolTip(xi18n("Export the report as a spreadsheet (in OpenDocument Spreadsheet format)")); a->setWhatsThis(xi18n("Exports the report as a spreadsheet (in OpenDocument Spreadsheet format).")); a->setEnabled(true); connect(a, SIGNAL(triggered()), this, SLOT(slotExportAsSpreadsheet())); #endif #ifdef KEXI_MOBILE viewActions << (a = new QAction(koIcon("text-html"), QLatin1String(" "), this)); #else exportMenu->addAction(a = new QAction(koIcon("text-html"), xi18nc("open dialog to export as web page", "Web Page..."), this)); #endif a->setObjectName("export_as_web_page"); a->setToolTip(xi18n("Export the report as a web page (in HTML format)")); a->setWhatsThis(xi18n("Exports the report as a web page (in HTML format).")); a->setEnabled(true); connect(a, SIGNAL(triggered()), this, SLOT(slotExportAsWebPage())); setViewActions(viewActions); #ifndef KEXI_MOBILE // setup main menu actions QList mainMenuActions; mainMenuActions << exportMenu; setMainMenuActions(mainMenuActions); #endif } KexiReportView::~KexiReportView() { delete m_preRenderer; } void KexiReportView::slotPrintReport() { QScopedPointer renderer(m_factory.createInstance("print")); if (!renderer) { return; } QPrinter printer(QPrinter::HighResolution); QPrintDialog dialog(&printer, this); if (dialog.exec() == QDialog::Accepted) { KReportRendererContext cxt; QPainter painter; cxt.setPrinter(&printer); cxt.setPainter(&painter); if (!renderer->render(cxt, m_preRenderer->document())) { KMessageBox::error(this, xi18n("Printing the report failed."), xi18n("Print Failed")); } } } void KexiReportView::slotExportAsPdf() { QScopedPointer renderer(m_factory.createInstance("print")); if (renderer) { KReportRendererContext cxt; cxt.setUrl(getExportUrl(QLatin1String("application/pdf"), xi18n("Export Report as PDF"), "kfiledialog:///LastVisitedPDFExportPath/", "pdf")); if (!cxt.url().isValid()) { return; } QPrinter printer; QPainter painter; printer.setOutputFileName(cxt.url().path()); printer.setOutputFormat(QPrinter::PdfFormat); printer.setColorMode(QPrinter::Color); painter.begin(&printer); cxt.setPrinter(&printer); cxt.setPainter(&painter); if (!renderer->render(cxt, m_preRenderer->document())) { KMessageBox::error(this, xi18n("Exporting the report as PDF to %1 failed.", cxt.url().toDisplayString()), xi18n("Export Failed")); } else { openExportedDocument(cxt.url()); } } } QUrl KexiReportView::getExportUrl(const QString &mimetype, const QString &caption, const QString &lastExportPath, const QString &extension) { QString defaultSavePath; QString recentDirClass; //TODO use utils defaultSavePath = KFileWidget::getStartUrl(QUrl(lastExportPath), recentDirClass).toLocalFile() + '/' + window()->partItem()->captionOrName() + '.' + extension; // loop until an url has been chosen or the file selection has been cancelled const QMimeDatabase db; const QString filterString = db.mimeTypeForName(mimetype).filterString(); return QFileDialog::getSaveFileUrl(this, caption, QUrl(defaultSavePath), filterString); } void KexiReportView::openExportedDocument(const QUrl &destination) { const int answer = KMessageBox::questionYesNo( this, xi18n("Do you want to open exported document?"), QString(), KStandardGuiItem::open(), KStandardGuiItem::close()); if (answer == KMessageBox::Yes) { (void)new KRun(destination, this->topLevelWidget()); } } #ifdef KEXI_SHOW_UNFINISHED void KexiReportView::slotExportAsSpreadsheet() { QScopedPointer renderer(m_factory.createInstance("ods")); if (renderer) { KReportRendererContext cxt; cxt.setUrl(getExportUrl(QLatin1String("application/vnd.oasis.opendocument.spreadsheet"), xi18n("Export Report as Spreadsheet"), "kfiledialog:///LastVisitedODSExportPath/", "ods")); if (!cxt.url().isValid()) { return; } if (!renderer->render(cxt, m_preRenderer->document())) { KMessageBox::error(this, xi18n("Failed to export the report as spreadsheet to %1.", cxt.url().toDisplayString()), xi18n("Export Failed")); } else { openExportedDocument(cxt.url()); } } } void KexiReportView::slotExportAsTextDocument() { QScopedPointer renderer(m_factory.createInstance("odt")); //! @todo Show error or don't show the commands to the user if the plugin isn't available. //! The same for other createInstance() calls. if (renderer) { KReportRendererContext cxt; cxt.setUrl(getExportUrl(QLatin1String("application/vnd.oasis.opendocument.text"), xi18n("Export Report as Text Document"), "kfiledialog:///LastVisitedODTExportPath/", "odt")); if (!cxt.url().isValid()) { return; } if (!renderer->render(cxt, m_preRenderer->document())) { KMessageBox::error(this, xi18n("Exporting the report as text document to %1 failed.", cxt.url().toDisplayString()), xi18n("Export Failed")); } else { openExportedDocument(cxt.url()); } } } #endif void KexiReportView::slotExportAsWebPage() { const QString dialogTitle = xi18n("Export Report as Web Page"); KReportRendererContext cxt; cxt.setUrl(getExportUrl(QLatin1String("text/html"), dialogTitle, "kfiledialog:///LastVisitedHTMLExportPath/", "html")); if (!cxt.url().isValid()) { return; } const int answer = KMessageBox::questionYesNo( this, xi18nc("@info", "Would you like to use Cascading Style Sheets (CSS) in the exported " "web page or use HTML tables?" "CSS give output closer to the original."), dialogTitle, KGuiItem(xi18nc("@action:button", "Use CSS")), KGuiItem(xi18nc("@action:button", "Use Table"))); QScopedPointer renderer( m_factory.createInstance(answer == KMessageBox::Yes ? "htmlcss" : "htmltable")); if (!renderer) { return; } if (!renderer->render(cxt, m_preRenderer->document())) { KMessageBox::error(this, xi18n("Exporting the report as web page to %1 failed.", cxt.url().toDisplayString()), xi18n("Export Failed")); } else { openExportedDocument(cxt.url()); } } tristate KexiReportView::beforeSwitchTo(Kexi::ViewMode mode, bool *dontStore) { Q_UNUSED(mode); Q_UNUSED(dontStore); return true; } tristate KexiReportView::afterSwitchFrom(Kexi::ViewMode mode) { Q_UNUSED(mode); if (tempData()->reportSchemaChangedInPreviousView) { tempData()->reportSchemaChangedInPreviousView = false; delete m_preRenderer; //qDebug() << tempData()->reportDefinition.tagName(); m_preRenderer = new KReportPreRenderer(tempData()->reportDefinition); if (m_preRenderer->isValid()) { KexiDBReportDataSource *reportData = nullptr; if (!tempData()->connectionDefinition.isNull()) { reportData = createDataSource(tempData()->connectionDefinition); } m_preRenderer->setDataSource(reportData); m_preRenderer->setScriptSource(qobject_cast(part())); m_preRenderer->setName(window()->partItem()->name()); //Add a kexi object to provide kexidb and extra functionality //! @todo KEXI3 if we want this if(!m_kexi) { // m_kexi = new KexiScriptAdaptor(); // } // m_preRenderer->registerScriptObject(m_kexi, "Kexi"); //If using a kexidb source, add a functions scripting object if (reportData && tempData()->connectionDefinition.attribute("type") == "internal") { m_functions = new KRScriptFunctions(reportData); m_preRenderer->registerScriptObject(m_functions, "field"); connect(m_preRenderer, SIGNAL(groupChanged(QMap)), m_functions, SLOT(setGroupData(QMap))); } connect(m_preRenderer, SIGNAL(finishedAllASyncItems()), this, SLOT(finishedAllASyncItems())); if (!m_preRenderer->generateDocument()) { qWarning() << "Could not generate report document"; return false; } m_reportView->setDocument(m_preRenderer->document()); #ifndef KEXI_MOBILE m_pageSelector->setRecordCount(m_reportView->pageCount()); m_pageSelector->setCurrentRecordNumber(1); #endif } else { KMessageBox::error(this, xi18n("Report schema appears to be invalid or corrupt"), xi18n("Opening failed")); } } return true; } KexiDBReportDataSource* KexiReportView::createDataSource(const QDomElement &e) { if (e.attribute("type") == "internal" && !e.attribute("source").isEmpty()) { return new KexiDBReportDataSource(e.attribute("source"), e.attribute("class"), tempData()); } return nullptr; } KexiReportPartTempData* KexiReportView::tempData() const { return static_cast(window()->data()); } void KexiReportView::addNewRecordRequested() { } void KexiReportView::moveToFirstRecordRequested() { m_reportView->moveToFirstPage(); #ifndef KEXI_MOBILE m_pageSelector->setCurrentRecordNumber(m_reportView->currentPage()); #endif } void KexiReportView::moveToLastRecordRequested() { m_reportView->moveToLastPage(); #ifndef KEXI_MOBILE m_pageSelector->setCurrentRecordNumber(m_reportView->currentPage()); #endif } void KexiReportView::moveToNextRecordRequested() { m_reportView->moveToNextPage(); #ifndef KEXI_MOBILE m_pageSelector->setCurrentRecordNumber(m_reportView->currentPage()); #endif } void KexiReportView::moveToPreviousRecordRequested() { m_reportView->moveToPreviousPage(); #ifndef KEXI_MOBILE m_pageSelector->setCurrentRecordNumber(m_reportView->currentPage()); #endif } void KexiReportView::moveToRecordRequested(int r) { #ifdef KEXI_MOBILE m_reportView->moveToPage(r + 1); #else // set in the navigator widget first, this will fix up the value it it's too small or large m_pageSelector->setCurrentRecordNumber(r + 1); m_reportView->moveToPage(m_pageSelector->currentRecordNumber()); #endif } int KexiReportView::currentRecord() const { return m_reportView->currentPage(); } int KexiReportView::recordCount() const { return m_reportView->pageCount(); } void KexiReportView::finishedAllASyncItems() { m_reportView->refreshCurrentPage(); } diff --git a/src/plugins/reports/kexireportview.h b/src/plugins/reports/kexireportview.h index 7da575562..2f421c7c2 100644 --- a/src/plugins/reports/kexireportview.h +++ b/src/plugins/reports/kexireportview.h @@ -1,91 +1,91 @@ /* * Kexi Report Plugin * Copyright (C) 2007-2008 by Adam Pigg (adam@piggz.co.uk) * Copyright (C) 2014-2018 Jarosław Staniek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library. If not, see . + * License along with this library. If not, see . */ #ifndef KEXIREPORTVIEW_H #define KEXIREPORTVIEW_H #include #include #include #include #include "kexireportpart.h" class KexiDBReportDataSource; class KReportPreRenderer; class ORODocument; class KReportView; //! @todo KEXI3 class KexiScriptAdaptor; class KRScriptFunctions; #ifndef KEXI_MOBILE class KexiRecordNavigator; #endif /** */ class KexiReportView : public KexiView, public KexiRecordNavigatorHandler { Q_OBJECT public: explicit KexiReportView(QWidget *parent); ~KexiReportView(); virtual tristate afterSwitchFrom(Kexi::ViewMode mode) override; virtual tristate beforeSwitchTo(Kexi::ViewMode mode, bool *dontStore) override; virtual void addNewRecordRequested() override; virtual void moveToFirstRecordRequested() override; virtual void moveToLastRecordRequested() override; virtual void moveToNextRecordRequested() override; virtual void moveToPreviousRecordRequested() override; virtual void moveToRecordRequested(int r) override; virtual int currentRecord() const override; virtual int recordCount() const override; private: KReportPreRenderer *m_preRenderer; KReportView *m_reportView; #ifndef KEXI_MOBILE KexiRecordNavigator *m_pageSelector; #endif KexiReportPartTempData* tempData() const; KexiDBReportDataSource* createDataSource(const QDomElement &e); //! @todo KEXI3 KexiScriptAdaptor *m_kexi; KRScriptFunctions *m_functions; KReportRendererFactory m_factory; QUrl getExportUrl(const QString &mimetype, const QString &caption, const QString &lastExportPathOrVariable, const QString &extension); private Q_SLOTS: void slotPrintReport(); void slotExportAsPdf(); void slotExportAsWebPage(); #ifdef KEXI_SHOW_UNFINISHED void slotExportAsSpreadsheet(); void slotExportAsTextDocument(); #endif void openExportedDocument(const QUrl &destination); void finishedAllASyncItems(); }; #endif diff --git a/src/plugins/reports/kexisourceselector.cpp b/src/plugins/reports/kexisourceselector.cpp index cd3c645c6..8365a93d4 100644 --- a/src/plugins/reports/kexisourceselector.cpp +++ b/src/plugins/reports/kexisourceselector.cpp @@ -1,90 +1,90 @@ /* * Kexi Report Plugin * Copyright (C) 2007-2009 by Adam Pigg * Copyright (C) 2017 Jarosław Staniek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public -* License along with this library. If not, see . +* License along with this library. If not, see . */ #include "kexisourceselector.h" #include "KexiDataSourceComboBox.h" #include #include #include #include #include #include class Q_DECL_HIDDEN KexiSourceSelector::Private { public: Private() { } ~Private() { } KDbConnection *conn; QVBoxLayout *layout; KexiDataSourceComboBox *dataSource; }; KexiSourceSelector::KexiSourceSelector(KexiProject* project, QWidget* parent) : QWidget(parent) , d(new Private) { d->conn = project->dbConnection(); d->layout = new QVBoxLayout(this); d->dataSource = new KexiDataSourceComboBox(this); d->dataSource->setProject(project); connect(d->dataSource, &KexiDataSourceComboBox::dataSourceChanged, this, &KexiSourceSelector::dataSourceChanged); QLabel *label = new QLabel(xi18n("Report's data source:")); label->setBuddy(d->dataSource); d->layout->addWidget(label); d->layout->addWidget(d->dataSource); d->layout->addStretch(); setLayout(d->layout); } KexiSourceSelector::~KexiSourceSelector() { delete d; } QString KexiSourceSelector::selectedPluginId() const { return d->dataSource->selectedPluginId(); } QString KexiSourceSelector::selectedName() const { return d->dataSource->selectedName(); } bool KexiSourceSelector::isSelectionValid() const { return d->dataSource->isSelectionValid(); } void KexiSourceSelector::setDataSource(const QString& pluginId, const QString& name) { d->dataSource->setDataSource(pluginId, name); } diff --git a/src/plugins/reports/kexisourceselector.h b/src/plugins/reports/kexisourceselector.h index 8f892044d..5f5ac2b85 100644 --- a/src/plugins/reports/kexisourceselector.h +++ b/src/plugins/reports/kexisourceselector.h @@ -1,70 +1,70 @@ /* * Kexi Report Plugin * Copyright (C) 2007-2016 by Adam Pigg * Copyright (C) 2017 Jarosław Staniek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public -* License along with this library. If not, see . +* License along with this library. If not, see . */ #ifndef KEXISOURCESELECTOR_H #define KEXISOURCESELECTOR_H #include #include #include "KexiDBReportDataSource.h" #ifdef HAVE_KEXI_MIGRATE #include "keximigratereportdata.h" #endif class QDomElement; class KexiProject; //! @todo rename to KexiReportDataSourcePage //! @todo use KexiPropertyPaneWidget class KexiSourceSelector : public QWidget { Q_OBJECT public: explicit KexiSourceSelector(KexiProject* project, QWidget* parent = 0); ~KexiSourceSelector(); Q_REQUIRED_RESULT KReportDataSource *createDataSource() const; QDomElement connectionData(); //! @return name plugin ID of selected item (a table or a query). Can return an empty string. QString selectedPluginId() const; //! @return name of selected table or query. QString selectedName() const; //! \return true if the current selection is valid bool isSelectionValid() const; public Q_SLOTS: /*! Sets item for data source described by \a pluginId and \a name. If \a pluginId is empty, either "org.kexi-project.table" and "org.kexi-project.query" are tried. */ void setDataSource(const QString& pluginId, const QString& name); Q_SIGNALS: void dataSourceChanged(); private: class Private; Private * const d; }; #endif // KEXISOURCESELECTOR_H diff --git a/src/plugins/reports/krscriptfunctions.cpp b/src/plugins/reports/krscriptfunctions.cpp index b311b7652..ae4a780be 100644 --- a/src/plugins/reports/krscriptfunctions.cpp +++ b/src/plugins/reports/krscriptfunctions.cpp @@ -1,82 +1,82 @@ /* * Kexi Report Plugin * Copyright (C) 2007-2008 by Adam Pigg * Copyright (C) 2012-2018 Jarosław Staniek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library. If not, see . + * License along with this library. If not, see . */ #include "krscriptfunctions.h" #include "KexiDBReportDataSource.h" #include #include #include #include KRScriptFunctions::KRScriptFunctions(KexiDBReportDataSource *datasource) : m_dataSource(datasource) { Q_ASSERT(m_dataSource); } KRScriptFunctions::~KRScriptFunctions() { } void KRScriptFunctions::setGroupData(const QMap& groupData) { m_groupData = groupData; } qreal KRScriptFunctions::math(const QString &function, const QString &field) { return m_dataSource->runAggregateFunction(function, field, m_groupData); } qreal KRScriptFunctions::sum(const QString &field) { return math("SUM", field); } qreal KRScriptFunctions::avg(const QString &field) { return math("AVG", field); } qreal KRScriptFunctions::min(const QString &field) { return math("MIN", field); } qreal KRScriptFunctions::max(const QString &field) { return math("MAX", field); } qreal KRScriptFunctions::count(const QString &field) { return math("COUNT", field); } QVariant KRScriptFunctions::value(const QString &field) { const QVariant val = m_dataSource->value(field); if (val.type() == QVariant::String) { // UTF-8 values are expected so convert this return val.toString().toUtf8(); } return val; } diff --git a/src/plugins/reports/krscriptfunctions.h b/src/plugins/reports/krscriptfunctions.h index b7461d839..8931e05ee 100644 --- a/src/plugins/reports/krscriptfunctions.h +++ b/src/plugins/reports/krscriptfunctions.h @@ -1,63 +1,63 @@ /* * Kexi Report Plugin * Copyright (C) 2007-2008 by Adam Pigg (adam@piggz.co.uk) * Copyright (C) 2012-2018 Jarosław Staniek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library. If not, see . + * License along with this library. If not, see . */ #ifndef KRSCRIPTFUNCTIONS_H #define KRSCRIPTFUNCTIONS_H #include #include #include class KexiDBReportDataSource; class KDbConnection; class KDbCursor; /** */ class KRScriptFunctions : public KReportGroupTracker { Q_OBJECT public: KRScriptFunctions(KexiDBReportDataSource *dataSource); ~KRScriptFunctions(); private: KexiDBReportDataSource * const m_dataSource; QString m_source; //! @todo Move SQL aggregate functions to KDb qreal math(const QString &, const QString &); QMap m_groupData; public Q_SLOTS: virtual void setGroupData(const QMap &groupData) override; qreal sum(const QString &); qreal avg(const QString &); qreal min(const QString &); qreal max(const QString &); qreal count(const QString &); QVariant value(const QString &); }; #endif diff --git a/src/tools/find_custom_widget_headers_in_ui.sh b/src/tools/find_custom_widget_headers_in_ui.sh index ce3e4aeee..490a44bb4 100755 --- a/src/tools/find_custom_widget_headers_in_ui.sh +++ b/src/tools/find_custom_widget_headers_in_ui.sh @@ -1,23 +1,23 @@ #!/bin/sh # # Shows all headers for custom (promoted) widgets in UI files of specified directory # Why is it useful? # To make sure global KF5 includes are used, i.e. , not "klineedit.h". # The latter can pick up an old header from kdelibs4. # # So it should be like: #
    KLineEdit
    # # And not like: #
    klineedit.h
    # # Instead of altering that in Designer, you can edit the file by hand. # -# PS: The UI file format: http://doc.qt.io/qt-5.4/designer-ui-file-format.html +# PS: The UI file format: https://doc.qt.io/qt-5.4/designer-ui-file-format.html # Relevant part: # # # # grep "\" `find $1 -name "*.ui"` diff --git a/src/widget/KexiFileRequester.cpp b/src/widget/KexiFileRequester.cpp index 29e651f7d..ec5e68284 100644 --- a/src/widget/KexiFileRequester.cpp +++ b/src/widget/KexiFileRequester.cpp @@ -1,612 +1,612 @@ /* This file is part of the KDE project Copyright (C) 2016-2018 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 "KexiFileRequester.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 namespace { enum KexiFileSystemModelColumnIds { NameColumnId, LastModifiedColumnId }; } //! A model for KexiFileRequester class KexiFileSystemModel : public QFileSystemModel { Q_OBJECT public: explicit KexiFileSystemModel(QObject *parent = nullptr) : QFileSystemModel(parent) { } int columnCount(const QModelIndex &parent = QModelIndex()) const override { Q_UNUSED(parent) return LastModifiedColumnId + 1; } QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override { const int col = index.column(); if (col == NameColumnId) { switch (role) { case Qt::DecorationRole: { if (isDir(index)) { return koIcon("folder"); } else { return QIcon::fromTheme(m_mimeDb.mimeTypeForFile(filePath(index)).iconName()); } } default: break; } return QFileSystemModel::data(index, role); } else if (col == LastModifiedColumnId) { const QWidget *parentWidget = qobject_cast(QObject::parent()); switch (role) { case Qt::DisplayRole: return parentWidget->locale().toString(QFileSystemModel::lastModified(index), QLocale::ShortFormat); default: break; } } return QVariant(); } Qt::ItemFlags flags(const QModelIndex& index) const override { Q_UNUSED(index) return Qt::ItemIsSelectable | Qt::ItemIsEnabled; } private: QMimeDatabase m_mimeDb; }; //! @internal class KexiUrlCompletion : public KUrlCompletion { public: explicit KexiUrlCompletion(QList *filterRegExps, QList *filterMimeTypes) : KUrlCompletion(KUrlCompletion::FileCompletion) , m_filterRegExps(filterRegExps) , m_filterMimeTypes(filterMimeTypes) { } using KUrlCompletion::postProcessMatches; //! Reimplemented to match the filter void postProcessMatches(QStringList *matches) const override { for (QStringList::Iterator matchIt = matches->begin(); matchIt != matches->end();) { if (fileMatchesFilter(*matchIt)) { ++matchIt; } else { matchIt = matches->erase(matchIt); } } } private: /** * @return @c true if @a fileName matches the current regular expression as well as the mime types * * The mime type matching allows to overcome issues with patterns such as *.doc being used * for text/plain mime types but really belonging to application/msword mime type. */ bool fileMatchesFilter(const QString &fileName) const { bool found = false; for (QRegExp *regexp : *m_filterRegExps) { if (regexp->exactMatch(fileName)) { found = true; break; } } if (!found) { return false; } const QMimeType mimeType(m_mimeDb.mimeTypeForFile(fileName)); qDebug() << mimeType; int i = m_filterMimeTypes->indexOf(mimeType); return i >= 0; } const QList * const m_filterRegExps; const QList * const m_filterMimeTypes; QMimeDatabase m_mimeDb; }; //! @internal class Q_DECL_HIDDEN KexiFileRequester::Private : public QObject { Q_OBJECT public: Private(KexiFileRequester *r) : q(r) { } ~Private() { qDeleteAll(filterRegExps); } static QString urlToPath(const QUrl &url) { QString filePath = QDir::toNativeSeparators(url.path(QUrl::RemoveScheme | QUrl::PreferLocalFile | QUrl::StripTrailingSlash)); #ifdef Q_OS_WIN if (filePath.startsWith('\\')) { filePath = filePath.mid(1); } else if (filePath.startsWith("file:\\")) { filePath = filePath.mid(6); } #endif return filePath; } public Q_SLOTS: void updateUrl(const QUrl &url) { updateFileName(urlToPath(url)); } void updateFileName(const QString &filePath) { const QFileInfo fileInfo(filePath); QString dirPath; if (fileInfo.isDir()) { dirPath = fileInfo.absoluteFilePath(); } else { dirPath = fileInfo.absolutePath(); } dirPath = QDir::toNativeSeparators(dirPath); if (filePath.isEmpty()) { // display Windows Explorer's "Computer" folder name for the top level #ifdef Q_OS_WIN QString computerNameString = QSettings( "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\" "CLSID\\{20D04FE0-3AEA-1069-A2D8-08002B30309D}", QSettings::NativeFormat).value("Default").toString(); if (computerNameString.isEmpty()) { computerNameString = xi18n("Computer"); } urlLabel->setText(computerNameString); folderIcon->setPixmap(koSmallIcon("computer")); upButton->setEnabled(false); #else urlLabel->setText("/"); folderIcon->setPixmap(koSmallIcon("folder")); upButton->setEnabled(false); #endif } else { urlLabel->setText(dirPath); folderIcon->setPixmap(koSmallIcon("folder")); upButton->setEnabled(filePath != "/"); } if (model->rootPath() != dirPath) { model->setRootPath(dirPath); list->setRootIndex(model->index(dirPath)); list->resizeColumnToContents(LastModifiedColumnId); urlCompletion->setDir(QUrl::fromLocalFile(dirPath)); } if (!fileInfo.isDir()) { list->clearSelection(); const QModelIndex fileIndex = model->index(filePath); list->scrollTo(fileIndex); list->selectionModel()->select(fileIndex, QItemSelectionModel::ClearAndSelect); /*qWarning() << model->rootPath() << fileIndex.isValid() << model->filePath(fileIndex) << model->fileName(fileIndex) << list->selectionModel()->selection().isEmpty() << q->filters()->isExistingFileRequired();*/ const QString newText(QFileInfo(filePath).fileName()); if (newText != locationEdit->lineEdit()->text()) { KexiUtils::BoolBlocker guard(&locationEditTextChangedEnabled, false); locationEdit->lineEdit()->setText(newText); } } } void itemClicked(const QModelIndex &index) { handleItem(index, std::bind(&KexiFileRequester::fileHighlighted, q, std::placeholders::_1), true); if (activateItemsOnSingleClick) { handleItem(index, std::bind(&KexiFileRequester::fileSelected, q, std::placeholders::_1), false); } } void itemActivated(const QModelIndex &index) { if (!activateItemsOnSingleClick) { handleItem(index, std::bind(&KexiFileRequester::fileSelected, q, std::placeholders::_1), true); } } void upButtonClicked() { QString dirPath(urlLabel->text()); QDir dir(dirPath); if (dirPath.isEmpty() || !dir.cdUp()) { updateFileName(QString()); } else { updateFileName(dir.absolutePath()); } //! @todo update button enable flag } void selectUrlButtonClicked() { QUrl dirUrl; #ifdef Q_OS_WIN - if (!upButton->isEnabled()) { // Computer folder, see http://doc.qt.io/qt-5/qfiledialog.html#setDirectoryUrl + if (!upButton->isEnabled()) { // Computer folder, see https://doc.qt.io/qt-5/qfiledialog.html#setDirectoryUrl dirUrl = QUrl("clsid:0AC0837C-BBF8-452A-850D-79D08E667CA7"); } #else if (false) { } #endif else { dirUrl = QUrl::fromLocalFile(urlLabel->text()); } QUrl selectedUrl = QFileDialog::getExistingDirectoryUrl(q, QString(), dirUrl); if (selectedUrl.isLocalFile()) { updateFileName(selectedUrl.toLocalFile()); } } void locationEditTextChanged(const QString &text) { if (!locationEditTextChangedEnabled) { return; } locationEdit->lineEdit()->setModified(true); if (text.isEmpty()) { list->clearSelection(); } QFileInfo info(model->rootPath() + '/' + text); if (info.isFile() && model->rootDirectory().exists(text)) { updateFileName(model->rootDirectory().absoluteFilePath(text)); // select file } else if (q->filters()->isExistingFileRequired()) { updateFileName(model->rootPath()); // only dir, unselect file } else { updateFileName(info.absoluteFilePath()); // only dir, unselect file } } void locationEditReturnPressed() { QString text(locationEdit->lineEdit()->text()); if (text.isEmpty()) { return; } if (text == QStringLiteral("~")) { text = QDir::homePath(); } else if (text.startsWith(QStringLiteral("~/"))) { text = QDir::home().absoluteFilePath(text.mid(2)); } if (QDir::isAbsolutePath(text)) { QFileInfo info(text); if (!info.isReadable()) { return; } if (info.isDir()) { // jump to absolute dir and clear the editor updateFileName(info.canonicalFilePath()); locationEdit->lineEdit()->clear(); } else { // jump to absolute dir and select the file in it updateFileName(info.dir().canonicalPath()); locationEdit->lineEdit()->setText(info.fileName()); locationEditReturnPressed(); } } else { // relative path QFileInfo info(model->rootPath() + '/' + text); if (info.isReadable() && info.isDir()) { // jump to relative dir and clear the editor updateFileName(info.canonicalFilePath()); locationEdit->lineEdit()->clear(); } else { // emit the file selection //not needed - preselected: updateFileName(text); emit q->fileSelected(q->selectedFile()); } } } void slotFilterComboChanged() { const QStringList patterns = filterCombo->currentFilter().split(' '); //qDebug() << patterns; model->setNameFilters(patterns); qDeleteAll(filterRegExps); filterRegExps.clear(); for (const QString &pattern : patterns) { filterRegExps.append(new QRegExp(pattern, Qt::CaseInsensitive, QRegExp::Wildcard)); } } //! @todo added to display select filename, still does not work void directoryLoaded() { if (!list->selectionModel()->selectedIndexes().isEmpty()) { list->scrollTo(list->selectionModel()->selectedIndexes().first()); } } private: void handleItem(const QModelIndex &index, std::function sig, bool silent) { const QString filePath(model->filePath(index)); if (model->isDir(index)) { QFileInfo info(filePath); if (info.isReadable()) { updateFileName(filePath); } else { if (silent) { KMessageBox::error(q, xi18n("Could not enter directory %1.", QDir::toNativeSeparators(info.absoluteFilePath()))); } } } else { emit sig(filePath); } } public: KexiFileRequester* const q; QPushButton *upButton; QLabel *folderIcon; QLabel *urlLabel; QPushButton *selectUrlButton; KexiFileSystemModel *model; QTreeView *list; bool activateItemsOnSingleClick; KUrlComboBox *locationEdit; KexiUrlCompletion *urlCompletion; KFileFilterCombo *filterCombo; QList filterRegExps; //!< Regular expression for the completer in the URL box QList filterMimeTypes; bool locationEditTextChangedEnabled = true; }; KexiFileRequester::KexiFileRequester(const QUrl &fileOrVariable, KexiFileFilters::Mode mode, const QString &fileName, QWidget *parent) : QWidget(parent), KexiFileWidgetInterface(fileOrVariable, fileName), d(new Private(this)) { init(); const QString actualFileName = Private::urlToPath(startUrl()); setMode(mode); d->updateFileName(actualFileName); // note: we had to call it after setMode(), not before } KexiFileRequester::KexiFileRequester(const QUrl &fileOrVariable, KexiFileFilters::Mode mode, QWidget *parent) : KexiFileRequester(fileOrVariable, mode, QString(), parent) { } KexiFileRequester::KexiFileRequester(const QString &selectedFileName, KexiFileFilters::Mode mode, QWidget *parent) : QWidget(parent) , KexiFileWidgetInterface(QUrl(selectedFileName), QString()) , d(new Private(this)) { init(); setMode(mode); d->updateFileName(selectedFileName); // note: we had to call it after setMode(), not before } KexiFileRequester::~KexiFileRequester() { const QString startDir(d->urlLabel->text()); addRecentDir(startDir); delete d; } void KexiFileRequester::init() { // [^] [Dir ][..] // [ files list ] // [ location ] // [ filter combo ] QVBoxLayout *lyr = new QVBoxLayout(this); setContentsMargins(QMargins()); lyr->setContentsMargins(QMargins()); QHBoxLayout *urlLyr = new QHBoxLayout; urlLyr->setContentsMargins(QMargins()); lyr->addLayout(urlLyr); d->upButton = new QPushButton; d->upButton->setFocusPolicy(Qt::NoFocus); d->upButton->setIcon(koIcon("go-up")); d->upButton->setToolTip(xi18n("Go to parent directory")); d->upButton->setFlat(true); connect(d->upButton, &QPushButton::clicked, d, &KexiFileRequester::Private::upButtonClicked); urlLyr->addWidget(d->upButton); d->folderIcon = new QLabel; urlLyr->addWidget(d->folderIcon); d->urlLabel = new QLabel; d->urlLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); d->urlLabel->setWordWrap(true); d->urlLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); urlLyr->addWidget(d->urlLabel, 1); d->selectUrlButton = new QPushButton; d->selectUrlButton->setFocusPolicy(Qt::NoFocus); d->selectUrlButton->setIcon(koIcon("folder")); d->selectUrlButton->setToolTip(xi18n("Select directory")); d->selectUrlButton->setFlat(true); connect(d->selectUrlButton, &QPushButton::clicked, d, &KexiFileRequester::Private::selectUrlButtonClicked); urlLyr->addWidget(d->selectUrlButton); d->list = new QTreeView; d->activateItemsOnSingleClick = KexiUtils::activateItemsOnSingleClick(d->list); connect(d->list, &QTreeView::clicked, d, &KexiFileRequester::Private::itemClicked); connect(d->list, &QTreeView::activated, d, &KexiFileRequester::Private::itemActivated); d->list->setRootIsDecorated(false); d->list->setItemsExpandable(false); d->list->header()->hide(); lyr->addWidget(d->list); d->model = new KexiFileSystemModel(d->list); d->model->setNameFilterDisables(false); connect(d->model, &QFileSystemModel::directoryLoaded, d, &Private::directoryLoaded); d->list->setModel(d->model); d->list->header()->setStretchLastSection(false); d->list->header()->setSectionResizeMode(NameColumnId, QHeaderView::Stretch); d->list->header()->setSectionResizeMode(LastModifiedColumnId, QHeaderView::ResizeToContents); QGridLayout *bottomLyr = new QGridLayout; lyr->addLayout(bottomLyr); QLabel *locationLabel = new QLabel(xi18n("Name:")); bottomLyr->addWidget(locationLabel, 0, 0, Qt::AlignVCenter | Qt::AlignRight); d->locationEdit = new KUrlComboBox(KUrlComboBox::Files, true); setFocusProxy(d->locationEdit); d->locationEdit->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLength); connect(d->locationEdit, &KUrlComboBox::editTextChanged, d, &KexiFileRequester::Private::locationEditTextChanged); #if QT_VERSION >= QT_VERSION_CHECK(5, 7, 0) connect(d->locationEdit, QOverload<>::of(&KUrlComboBox::returnPressed), d, &Private::locationEditReturnPressed); #else connect(d->locationEdit, static_cast(&KUrlComboBox::returnPressed), d, &Private::locationEditReturnPressed); #endif d->urlCompletion = new KexiUrlCompletion(&d->filterRegExps, &d->filterMimeTypes); d->locationEdit->setCompletionObject(d->urlCompletion); d->locationEdit->setAutoDeleteCompletionObject(true); d->locationEdit->lineEdit()->setClearButtonEnabled(true); locationLabel->setBuddy(d->locationEdit); bottomLyr->addWidget(d->locationEdit, 0, 1, Qt::AlignVCenter); QLabel *filterLabel = new QLabel(xi18n("Filter:")); bottomLyr->addWidget(filterLabel, 1, 0, Qt::AlignVCenter | Qt::AlignRight); d->filterCombo = new KFileFilterCombo; connect(d->filterCombo, &KFileFilterCombo::filterChanged, d, &Private::slotFilterComboChanged); filterLabel->setBuddy(d->filterCombo); bottomLyr->addWidget(d->filterCombo, 1, 1, Qt::AlignVCenter); } QString KexiFileRequester::selectedFile() const { const QModelIndexList list(d->list->selectionModel()->selectedIndexes()); if (list.isEmpty() || d->model->isDir(list.first())) { // no file selection but try entered filename const QString text(d->locationEdit->lineEdit()->text().trimmed()); if (!text.isEmpty() && !filters()->isExistingFileRequired()) { const QFileInfo info(currentDir() + '/' + text); if (info.isNativePath()) { return info.absoluteFilePath(); } } return QString(); } if (d->model->isDir(list.first())) { return QString(); } return d->model->filePath(list.first()); } QString KexiFileRequester::highlightedFile() const { return selectedFile(); } QString KexiFileRequester::currentDir() const { return d->model->rootPath(); } void KexiFileRequester::setSelectedFile(const QString &name) { d->updateFileName(name); } void KexiFileRequester::updateFilters() { // Update filters for the file model, filename completion and the filter combo const QStringList patterns = filters()->allGlobPatterns(); if (patterns != d->model->nameFilters()) { d->model->setNameFilters(patterns); qDeleteAll(d->filterRegExps); d->filterRegExps.clear(); for (const QString &pattern : patterns) { d->filterRegExps.append(new QRegExp(pattern, Qt::CaseInsensitive, QRegExp::Wildcard)); } d->filterMimeTypes = filters()->mimeTypes(); KexiFileFiltersFormat format; format.type = KexiFileFiltersFormat::Type::KDE; format.addAllFiles = true; d->filterCombo->setFilter(filters()->toString(format)); } } void KexiFileRequester::setWidgetFrame(bool set) { d->list->setFrameShape(set ? QFrame::StyledPanel : QFrame::NoFrame); d->list->setLineWidth(set ? 1 : 0); } void KexiFileRequester::applyEnteredFileName() { } QStringList KexiFileRequester::currentFilters() const { return QStringList(); } void KexiFileRequester::showEvent(QShowEvent *event) { setFiltersUpdated(false); updateFilters(); QWidget::showEvent(event); } #include "KexiFileRequester.moc" diff --git a/src/widget/dataviewcommon/kexidataawareobjectiface.h b/src/widget/dataviewcommon/kexidataawareobjectiface.h index be4860a87..eb265234c 100644 --- a/src/widget/dataviewcommon/kexidataawareobjectiface.h +++ b/src/widget/dataviewcommon/kexidataawareobjectiface.h @@ -1,1040 +1,1038 @@ /* This file is part of the KDE project Copyright (C) 2005-2015 Jarosław Staniek Based on KexiTableView code. Copyright (C) 2002 Till Busch Copyright (C) 2003 Lucijan Busch Copyright (C) 2003 Daniel Molkentin Copyright (C) 2003 Joseph Wenninger This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KEXIDATAAWAREOBJECTIFACE_H #define KEXIDATAAWAREOBJECTIFACE_H #include "kexidataviewcommon_export.h" #include #include #include #include #include #include #include #include class QHeaderView; class QScrollBar; class QMenu; class KDbRecordData; class KDbTableViewData; class KexiRecordNavigatorIface; //! default column width in pixels #define KEXI_DEFAULT_DATA_COLUMN_WIDTH 120 //! \brief The KexiDataAwareObjectInterface is an interface for record-based data object. /** This interface is implemented by KexiTableScrollArea and KexiFormView and used by KexiDataAwareView. If yu're implementing this interface, add KEXI_DATAAWAREOBJECTINTERFACE convenience macro just after Q_OBJECT. You should add following code to your destructor so data is deleted: \code if (m_owner) delete m_data; m_data = 0; \endcode This is not performed in KexiDataAwareObjectInterface because you may need to access m_data in your desctructor. */ class KEXIDATAVIEWCOMMON_EXPORT KexiDataAwareObjectInterface { public: KexiDataAwareObjectInterface(); virtual ~KexiDataAwareObjectInterface(); /*! Sets data for this object. if \a owner is true, the object will own \a data and therefore will be destroyed when needed, else: \a data is (possibly) shared and not owned by the widget. If widget already has _different_ data object assigned (and owns this data), old data is destroyed before new assignment. */ void setData(KDbTableViewData *data, bool owner = true); /*! \return data structure displayed for this object */ inline KDbTableViewData *data() const { return m_data; } /*! \return currently selected column number or -1. */ inline int currentColumn() const { return m_curColumn; } /*! \return number of the currently selected record number or -1. */ inline int currentRecord() const { return m_curRecord; } /*! \return last record visible on the screen (counting from 0). The returned value is guaranteed to be smaller or equal to currentRecord() or -1 if there are no records. */ virtual int lastVisibleRecord() const = 0; /*! \return currently selected record data or null. */ KDbRecordData *selectedRecord() const { return m_currentRecord; } /*! \return number of records in this view. */ int recordCount() const; /*! \return number of visible columns in this view. By default returns dataColumns(), what is proper table view. In case of form view, there can be a number of duplicated columns defined (data-aware widgets, see KexiFormScrollView::columnCount()), so columnCount() can return greater number than dataColumns(). */ virtual int columnCount() const; /*! Helper function. \return number of columns of data. */ int dataColumns() const; /*! \return true if data represented by this object is not editable (it can be editable with other ways although, outside of this object). */ virtual bool isReadOnly() const; /*! Sets readOnly flag for this object. Unless the flag is set, the widget inherits readOnly flag from it's data structure assigned with setData(). The default value if false. This method is useful when you need to switch on the flag indepentently from the data structure. Note: it is not allowed to force readOnly off when internal data is readOnly - in that case the method does nothing. You can check internal data flag calling data()->isReadOnly(). If \a set is true, insertingEnabled flag will be cleared automatically. \sa isInsertingEnabled() */ void setReadOnly(bool set); /*! \return true if sorting is enabled. */ inline bool isSortingEnabled() const { return m_isSortingEnabled; } /*! Sets sorting order on column @a column to @a order. This method do not work if sorting is disabled using setSortingEnabled(false). @a column may be -1, what means "no sorting". */ virtual void setSorting(int column, KDbOrderByColumn::SortOrder order = KDbOrderByColumn::SortOrder::Ascending); /*! Enables or disables sorting for this object This method is different that setSorting() because it prevents both user and programmer from sorting by clicking a column's header or calling setSorting(). By default sorting is enabled. */ virtual void setSortingEnabled(bool set); /*! \return sorted column number or -1 if no column is sorted within data or there is no data assigned at all. This does not mean that any sorting has been performed within GUI of this object, because the data could be changed in the meantime outside of this GUI object. */ int dataSortColumn() const; /*! \return sort order for data. This information does not mean that any sorting has been performed within GUI of this object, because the data could be changed in the meantime outside of this GUI object. dataSortColumn() should be checked first to see if sorting is enabled (and if there's data). */ KDbOrderByColumn::SortOrder dataSortOrder() const; /*! Sorts all records by column selected with setSorting(). If there is currently record edited, it is accepted. If acception failed, sort() will return false. \return true on success. */ virtual bool sort(); /*! Sorts currently selected column in ascending order. This slot is used typically for "data_sort_az" action. */ void sortAscending(); /*! Sorts currently selected column in descending order. This slot is used typically for "data_sort_za" action. */ void sortDescending(); /*! \return true if data inserting is enabled (the default). */ virtual bool isInsertingEnabled() const; /*! Sets insertingEnabled flag. If true, empty record is available at the end of this widget for new entering new data. Unless the flag is set, the widget inherits insertingEnabled flag from it's data structure assigned with setData(). The default value if false. Note: it is not allowed to force insertingEnabled on when internal data has insertingEnabled set off - in that case the method does nothing. You can check internal data flag calling data()->insertingEnabled(). Setting this flag to true will have no effect if read-only flag is true. \sa setReadOnly() */ void setInsertingEnabled(bool set); /*! \return true if record deleting is enabled. Equal to deletionPolicy() != NoDelete && !isReadOnly()). */ bool isDeleteEnabled() const; /*! \return true if inserting empty records is enabled (false by default). Mostly usable for not db-aware objects (e.g. used in Kexi Alter Table). Note, that if inserting is disabled, or the data set is read-only, this flag will be ignored. */ bool isEmptyRecordInsertingEnabled() const { return m_emptyRecordInsertingEnabled; } /*! Sets the emptyRecordInsertingEnabled flag. Note, that if inserting is disabled, this flag is ignored. */ void setEmptyRecordInsertingEnabled(bool set); /*! Enables or disables filtering. Filtering is enabled by default. */ virtual void setFilteringEnabled(bool set); /*! \return true if filtering is enabled. */ inline bool isFilteringEnabled() const { return m_isFilteringEnabled; } /*! Added for convenience: if @a set is true, this object will behave more like a spreadsheet (it's used for things like table designer view): - hides navigator - disables sorting, inserting and filtering - enables accepting record after cell accepting; see setAcceptsRecordEditAfterCellAccepting() - enables inserting empty record; see setEmptyRecordInsertingEnabled() */ virtual void setSpreadSheetMode(bool set); /*! \return true id "spreadSheetMode" is enabled. It's false by default. */ bool spreadSheetMode() const { return m_spreadSheetMode; } /*! \return number of currently edited record or -1. */ inline int recordEditing() const { return m_recordEditing; } enum DeletionPolicy { NoDelete = 0, AskDelete = 1, ImmediateDelete = 2, SignalDelete = 3 }; /*! \return deletion policy for this object. The default (after allocating) is AskDelete. */ DeletionPolicy deletionPolicy() const { return m_deletionPolicy; } virtual void setDeletionPolicy(DeletionPolicy policy); /*! Deletes currently selected record; does nothing if no record is currently selected. If record is in edit mode, editing is cancelled before deleting. */ virtual void deleteCurrentRecord(); /*! Inserts one empty record above \a pos. If \a pos is -1 (the default), new record is inserted above the current record (or above 1st record if there is no current). A new record becomes current if \a pos is -1 or if \a pos is equal currentRecord(). This method does nothing if: -inserting flag is disabled (see isInsertingEnabled()) -read-only flag is set (see isReadOnly()) \return inserted record's data */ virtual KDbRecordData *insertEmptyRecord(int pos = -1); /*! For reimplementation: called by deleteItem(). If returns false, deleting is aborted. Default implementation just returns true. */ virtual bool beforeDeleteItem(KDbRecordData *data); /*! Deletes \a record. Used by deleteCurrentRecord(). Calls beforeDeleteItem() before deleting, to double-check if deleting is allowed. \return true on success. */ bool deleteItem(KDbRecordData *data); /*! Inserts \a data at position \a pos. -1 means current record. Used by insertEmptyRecord(). */ void insertItem(KDbRecordData *data, int pos = -1); /*! Clears entire table data, its visible representation and deletes data at database backend (if this is db-aware object). Does not clear columns information. Does not destroy KDbTableViewData object (if present) but only clears its contents. Displays confirmation dialog if \a ask is true (the default is false). Repaints widget if \a repaint is true (the default). For empty tables, true is returned immediately. If isDeleteEnabled() is false, false is returned. For spreadsheet mode all current records are just replaced by empty records. \return true on success, false on failure, and cancelled if user cancelled deletion (only possible if \a ask is true). */ tristate deleteAllRecords(bool ask = false, bool repaint = true); /*! \return maximum number of records that can be displayed per one "page" for current view's size. */ virtual int recordsPerPage() const = 0; virtual void selectRecord(int record); virtual void selectNextRecord(); virtual void selectPreviousRecord(); virtual void selectNextPage(); //!< page down action virtual void selectPreviousPage(); //!< page up action virtual void selectFirstRecord(); virtual void selectLastRecord(); virtual void addNewRecordRequested(); /*! Clears the current selection. Current record and column will be now unspecified: currentRecord(), currentColumn() will return -1, and selectedRecord() will return null. */ virtual void clearSelection(); //! Flags for setCursorPosition() enum CursorPositionFlag { NoCursorPositionFlags = 0, //!< Default flag ForceSetCursorPosition = 1, //!< Update cursor position even if record and col doesn't //!< differ from actual position. DontEnsureCursorVisibleIfPositionUnchanged = 2 //!< Don't call ensureCellVisible() //!< when position is unchanged and //!< ForceSetCursorPosition is off. }; Q_DECLARE_FLAGS(CursorPositionFlags, CursorPositionFlag) /*! Moves cursor to \a record and \a col. If \a col is -1, current column number is used. If forceSet is true, cursor position is updated even if \a record and \a col doesn't differ from actual position. */ virtual void setCursorPosition(int record, int col = -1, CursorPositionFlags flags = NoCursorPositionFlags); /*! Ensures that cell at \a record and \a col is visible. If \a col is -1, current column number is used. \a record and \a col, if not -1, must be between 0 and recordCount()-1 (or columnCount()-1 accordingly). */ virtual void ensureCellVisible(int record, int col) = 0; /*! Ensures that column \a col is visible. If \a col is -1, current column number is used. \a col, if not -1, must be between 0 and columnCount()-1. */ virtual void ensureColumnVisible(int col) = 0; /*! Specifies, if this object automatically accepts record editing (using acceptRecordEdit()) on accepting any cell's edit (i.e. after acceptEditor()). \sa acceptsRecordEditAfterCellAccepting() */ virtual void setAcceptsRecordEditAfterCellAccepting(bool set); /*! \return true, if this object automatically accepts record editing (using acceptRecordEdit()) on accepting any cell's edit (i.e. after acceptEditor()). By default this flag is set to false. Not that if the query for this table has given constraints defined, like NOT NULL / NOT EMPTY for more than one field - editing a record would be impossible for the flag set to true, because of constraints violation. However, setting this flag to true can be useful especially for not-db-aware data set (it's used e.g. in Kexi Alter Table's field editor). */ bool acceptsRecordEditAfterCellAccepting() const { return m_acceptsRecordEditAfterCellAccepting; } /*! \return true, if this table accepts dropping data on the records. */ bool dropsAtRecordEnabled() const { return m_dropsAtRecordEnabled; } /*! Specifies, if this table accepts dropping data on the records. If enabled: - dragging over record is indicated by drawing a line at bottom side of this record - dragOverRecord() signal will be emitted on dragging, -droppedAtRecord() will be emitted on dropping By default this flag is set to false. */ virtual void setDropsAtRecordEnabled(bool set); /*! \return currently used data (field/cell) editor or 0 if there is no data editing. */ inline KexiDataItemInterface *editor() const { return m_editor; } /*! Cancels record editing. All changes made to the editing record during this current session will be undone. \return true on success or false on failure (e.g. when editor does not exist) */ virtual bool cancelRecordEditing(); /*! Accepts record editing. All changes made to the editing record during this current session will be accepted (saved). \return true if accepting was successful, false otherwise (e.g. when current record contains data that does not meet given constraints). */ virtual bool acceptRecordEditing(); virtual void removeEditor(); /*! Cancels changes made to the currently active editor. Reverts the editor's value to old one. \return true on success or false on failure (e.g. when editor does not exist) */ virtual bool cancelEditor(); //! Accepst changes made to the currently active editor. //! \return true on success or false on failure (e.g. when editor does not exist or there is data validation error) virtual bool acceptEditor(); //! Flags for use in createEditor() enum CreateEditorFlag { ReplaceOldValue = 1, //!< Remove old value replacing it with a new one EnsureCellVisible = 2, //!< Ensure the cell behind the editor is visible DefaultCreateEditorFlags = EnsureCellVisible //!< Default flags. }; Q_DECLARE_FLAGS(CreateEditorFlags, CreateEditorFlag) //! Creates editors and shows it, what usually means the beginning of a cell editing virtual void createEditor(int record, int col, const QString& addText = QString(), CreateEditorFlags flags = DefaultCreateEditorFlags) = 0; /*! Used when Return key is pressed on cell, the cell has been double clicked or "+" navigator's button is clicked. Also used when we want to continue editing a cell after "invalid value" message was displayed (in this case, \a setText is usually not empty, what means that text will be set in the cell replacing previous value). */ virtual void startEditCurrentCell(const QString& setText = QString(), CreateEditorFlags flags = DefaultCreateEditorFlags); /*! Deletes currently selected cell's contents, if allowed. In most cases delete is not accepted immediately but "record editing" mode is just started. */ virtual void deleteAndStartEditCurrentCell(); inline KDbRecordData *recordAt(int pos) const; /*! \return column information for column number \a col. Default implementation just returns column # col, but for Kexi Forms column data corresponding to widget number is used here (see KexiFormScrollView::fieldNumberForColumn()). */ virtual KDbTableViewColumn* column(int col); /*! \return field number within data model connected to a data-aware widget at column \a col. Can return -1 if there's no such column. */ virtual int fieldNumberForColumn(int col) { return col; } bool hasDefaultValueAt(const KDbTableViewColumn& tvcol); const QVariant* bufferedValueAt(int record, int col, bool useDefaultValueIfPossible = true); //! \return a type of column \a col - one of KDbField::Type int columnType(int col); //! \return default value for column \a col QVariant columnDefaultValue(int col) const; /*! \return true if column \a col is editable. Default implementation takes information about 'readOnly' flag from data member. Within forms, this is reimplemented for checking 'readOnly' flag from a widget ('readOnly' flag from data member is still checked though). */ virtual bool columnEditable(int col); /*! Redraws the current cell. To be implemented. */ virtual void updateCurrentCell() = 0; //! @return height of the horizontal header, 0 by default. virtual int horizontalHeaderHeight() const; //! signals virtual void itemChanged(KDbRecordData* data, int record, int column) = 0; virtual void itemChanged(KDbRecordData* data, int record, int column, const QVariant &oldValue) = 0; virtual void itemDeleteRequest(KDbRecordData* data, int record, int column) = 0; virtual void currentItemDeleteRequest() = 0; //! Emitted for spreadsheet mode when an item was deleted and a new item has been appended virtual void newItemAppendedForAfterDeletingInSpreadSheetMode() = 0; /*! Data has been refreshed on-screen - emitted from initDataContents(). */ virtual void dataRefreshed() = 0; virtual void dataSet(KDbTableViewData *data) = 0; /*! \return a pointer to context menu. This can be used to plug some actions there. */ QMenu* contextMenu() const { return m_contextMenu; } /*! \return true if the context menu is enabled (visible) for the view. True by default. */ bool contextMenuEnabled() const { return m_contextMenuEnabled; } /*! Enables or disables the context menu for the view. */ void setContextMenuEnabled(bool set) { m_contextMenuEnabled = set; } /*! Sets a title with icon for the context menu. Set empty icon and text to remove the title item. This method should be called before customizing the menu because it will be recreated by the method. */ void setContextMenuTitle(const QIcon &icon, const QString &text); /*! \return title text of the context menu. */ QString contextMenuTitleText() const { return m_contextMenuTitleText; } /*! \return title icon of the context menu. */ QIcon contextMenuTitleIcon() const { return m_contextMenuTitleIcon; } /*! \return true if vertical scrollbar's tooltips are enabled (true by default). */ bool scrollbarToolTipsEnabled() const; /*! Enables or disables vertical scrollbar's tooltip. */ void setScrollbarToolTipsEnabled(bool set); /*! Typically handles pressing Enter or F2 key: if current cell has boolean type, toggles it's value, otherwise starts editing (startEditCurrentCell()). */ void startEditOrToggleValue(); /*! \return true if the new record is edited; implies: recordEditing==true. */ inline bool newRecordEditing() const { return m_newRecordEditing; } /*! Reaction on toggling a boolean value of a cell: we're starting to edit the cell and inverting it's state. */ virtual void boolToggled(); virtual void connectCellSelectedSignal(const QObject* receiver, const char* intIntMember) = 0; virtual void connectRecordEditingStartedSignal(const QObject* receiver, const char* intMember) = 0; virtual void connectRecordEditingTerminatedSignal(const QObject* receiver, const char* voidMember) = 0; virtual void connectUpdateSaveCancelActionsSignal(const QObject* receiver, const char* voidMember) = 0; virtual void connectReloadActionsSignal(const QObject* receiver, const char* voidMember) = 0; virtual void connectDataSetSignal(const QObject* receiver, const char* kexiTableViewDataMember) = 0; virtual void connectToReloadDataSlot(const QObject* sender, const char* voidSignal) = 0; virtual void slotDataDestroying(); //! Copy current selection to a clipboard (e.g. cell) virtual void copySelection() = 0; //! Cut current selection to a clipboard (e.g. cell) virtual void cutSelection() = 0; //! Paste current clipboard contents (e.g. to a cell) virtual void paste() = 0; /*! Finds \a valueToFind within the data items \a options are used to control the process. Selection is moved to found value. If \a next is true, "find next" is performed, else "find previous" is performed. Searching behaviour also depends on status of the previous search: for every search, position of the cells containing the found value is stored internally by the data-aware interface (not in options). Moreover, position (start, end) of the found value is also stored. Thus, the subsequent search will reuse this information to be able to start searching exactly after the previously found value (or before for "find previous" option). The flags can be zeroed, what will lead to seaching from the first character of the current item (cell). \return true if value has been found, false if value has not been found, and cancelled if there is nothing to find or there is no data to search in. */ virtual tristate find(const QVariant& valueToFind, const KexiSearchAndReplaceViewInterface::Options& options, bool next); /*! Finds \a valueToFind within the data items and replaces with \a replacement \a options are used to control the process. \return true if value has been found and replaced, false if value has not been found and replaced, and cancelled if there is nothing to find or there is no data to search in or the data is read only. If \a replaceAll is true, all found values are replaced. */ virtual tristate findNextAndReplace(const QVariant& valueToFind, const QVariant& replacement, const KexiSearchAndReplaceViewInterface::Options& options, bool replaceAll); /*! \return vertical scrollbar */ virtual QScrollBar* verticalScrollBar() const = 0; /*! Used in KexiTableView::keyPressEvent() (and in continuous forms). \return true when the key press event \e was consumed. You should also check e->isAccepted(), if it's true, nothing should be done; if it is false, you should call setCursorPosition() for the altered \a curentColumn and \c currentRecord variables. If \a moveToFirstField is not 0, *moveToFirstField will be set to true when the cursor should be moved to the first field (in tab order) and to false otherwise. If \a moveToLastField is not 0, *moveToLastField will be set to true when the cursor should be moved to the last field (in tab order) and to false otherwise. Note for forms: if moveToFirstField and moveToLastField are not 0, \a currentColumn is altered after calling this method, so setCursorPosition() will set to the index of an appropriate column (field). This is needed because field widgets can be inserted and ordered in custom tab order, so the very first field in the data source can be other than the very first field in the form. Used by KexiTableView::keyPressEvent() and KexiTableView::keyPressEvent(). */ virtual bool handleKeyPress(QKeyEvent *e, int *currentRecord, int *currentColumn, bool fullRecordSelection, bool *moveToFirstField = 0, bool *moveToLastField = 0); protected: /*! Reimplementation for KexiDataAwareObjectInterface. Initializes data contents (resizes it, sets cursor at the first record). Sets record count for record navigator. Sets cursor positin (using setCursorPosition()) to first record or sets (-1, -1) position if no records are available. Called on setData(). Also called once on show event after refreshRequested() signal was received from TableViewData object. */ virtual void initDataContents(); /*! Clears columns information and thus all internal table data and its visible representation. Repaints widget if \a repaint is true. */ virtual void clearColumns(bool repaint = true); /*! Called by clearColumns() to clear internals of the object. */ virtual void clearColumnsInternal(bool repaint) = 0; /*! @internal for implementation \return sorting order (within GUI). currentLocalSortColumn() should be also checked, and if it returns -1, no particular sorting is set up. Even this does not mean that any sorting has been performed within GUI of this object, because the data could be changed in the meantime outside of this GUI object. @see dataSortOrder() currentLocalSortColumn() */ virtual KDbOrderByColumn::SortOrder currentLocalSortOrder() const = 0; /*! @internal for implementation \return sorted column number for this widget or -1 if no column is sorted witin GUI. This does not mean that the same sorting is performed within data member which is used by this widget, because the data could be changed in the meantime outside of this GUI widget. @see dataSortColumn() currentLocalSortOrder() */ virtual int currentLocalSortColumn() const = 0; /*! @internal for implementation Shows sorting indicator order in the GUI for column @a column. This should not perform any sorting in data assigned to this object. @a column may be -1, what means "no sorting". */ virtual void setLocalSortOrder(int column, KDbOrderByColumn::SortOrder order) = 0; /*! @internal Sets order for \a column: -1: descending, 1: ascending, 0: invert order */ virtual void sortColumnInternal(int col, int order = 0); /*! @internal for implementation Updates GUI after sorting. After sorting you need to ensure current record and column is visible to avoid user confusion. For exaple, in KexiTableView implementation, current cell is centered (if possible) and updateContents() is called. */ virtual void updateGUIAfterSorting(int previousRecord) = 0; /*! Emitted in initActions() to force reload actions You should remove existing actions and add them again. Define and emit reloadActions() signal here. */ virtual void reloadActions() = 0; /*! Reloads data for this object. */ virtual void reloadData(); /*! for implementation as a signal */ virtual void itemSelected(KDbRecordData *) = 0; /*! for implementation as a signal */ virtual void cellSelected(int record, int col) = 0; /*! for implementation as a signal */ virtual void sortedColumnChanged(int col) = 0; /*! for implementation as a signal */ virtual void recordEditingTerminated(int record) = 0; /*! for implementation as a signal */ virtual void updateSaveCancelActions() = 0; /*! Prototype for signal recordEditingStarted(int), implemented by KexiFormScrollView. */ virtual void recordEditingStarted(int record) = 0; /*! Clear temporary members like the pointer to current editor. If you reimplement this method, don't forget to call this one. */ virtual void clearVariables(); /*! @internal Creates editor structure without filling it with data. Used in createEditor() and few places to be able to display cell contents dependending on its type. If \a ignoreMissingEditor is false (the default), and editor cannot be instantiated, current record editing (if present) is cancelled. */ virtual KexiDataItemInterface *editor(int col, bool ignoreMissingEditor = false) = 0; /*! Updates editor's position, size and shows its focus (not the editor!) for \a record and \a column, using editor(). Does nothing if editor not found. */ virtual void editorShowFocus(int record, int column) = 0; /*! Redraws specified cell. */ virtual void updateCell(int record, int column) = 0; /*! Redraws all cells of specified record \a record. */ virtual void updateRecord(int record) = 0; /*! Updates contents of the widget. Just call update() here on your widget. */ virtual void updateWidgetContents() = 0; /*! Updates widget's contents size e.g. using QScrollView::resizeContents(). */ virtual void updateWidgetContentsSize() = 0; /*! @internal Updates record appearance after canceling record edit. Used by cancelRecordEdit(). By default just calls updateRecord(m_curRecord). Reimplemented by KexiFormScrollView. */ virtual void updateAfterCancelRecordEditing(); /*! @internal Updates record appearance after accepting record edit. Used by acceptRecordEditing(). By default just calls updateRecord(m_curRecord). Reimplemented by KexiFormScrollView. */ virtual void updateAfterAcceptRecordEditing(); //! Handles TableViewData::recordRepaintRequested() signal virtual void slotRecordRepaintRequested(KDbRecordData* data) { Q_UNUSED(data); } //! Handles TableViewData::aboutToDeleteRecord() signal. Prepares info for slotRecordDeleted(). virtual void slotAboutToDeleteRecord(KDbRecordData* data, KDbResultInfo* result, bool repaint); //! Handles TableViewData::recordDeleted() signal to repaint when needed. virtual void slotRecordDeleted(); //! Handles TableViewData::recordInserted() signal to repaint when needed. virtual void slotRecordInserted(KDbRecordData *data, bool repaint); virtual void beginInsertItem(KDbRecordData *data, int pos); virtual void endInsertItem(KDbRecordData *data, int pos); virtual void beginRemoveItem(KDbRecordData *data, int pos); virtual void endRemoveItem(int pos); //! Like above, not db-aware version virtual void slotRecordInserted(KDbRecordData *data, int record, bool repaint); virtual void slotRecordsDeleted(const QList &) {} //! for sanity checks (return true if m_data is present; else: outputs warning) inline bool hasData() const; /*! Used by setCursorPosition() if cursor's position changed. */ virtual void selectCellInternal(int previousRecord, int previousColumn); /*! Used in KexiDataAwareObjectInterface::slotRecordDeleted() to repaint tow \a record and all visible below. Implemented if there is more than one record displayed, i.e. currently for KexiTableView. */ virtual void updateAllVisibleRecordsBelow(int record) { Q_UNUSED(record); } /*! @return geometry of the viewport, i.e. the scrollable area, minus any scrollbars, etc. */ virtual QRect viewportGeometry() const = 0; //! Call this from the subclass. */ virtual void focusOutEvent(QFocusEvent* e); /*! Handles verticalScrollBar()'s valueChanged(int) signal. Called when vscrollbar's value has been changed. Call this method from the subclass. */ virtual void verticalScrollBarValueChanged(int v); /*! Changes 'record editing' >=0 there's currently edited record, else -1. * Can be reimplemented with calling superclass setRecordEditing() * Sends recordEditingStarted(int) signal. * @see recordEditing() recordEditingStarted(). */ void setRecordEditing(int record); /*! Shows error message box suitable for \a resultInfo. This can be "sorry" or "detailedSorry" message box or "queryYesNo" if resultInfo->allowToDiscardChanges is true. \return code of button clicked: KMessageBox::Ok in case of "sorry" or "detailedSorry" messages and KMessageBox::Yes or KMessageBox::No in case of "queryYesNo" message. */ int showErrorMessageForResult(const KDbResultInfo& resultInfo); /*! Shows context message @a message for editor @a item. */ void showEditorContextMessage( KexiDataItemInterface *item, const QString &message, KMessageWidget::MessageType type, KMessageWidget::CalloutPointerDirection direction); /*! Shows context message about exceeded length for editor @a item. If @a exceeded is true, a new message is created, else the message will be removed. */ void showLengthExceededMessage(KexiDataItemInterface *item, bool exceeded); /*! Updates message about exceeded length for editor @a item. Useful only where message created with showLengthExceededMessage() is displayed. */ void showUpdateForLengthExceededMessage(KexiDataItemInterface *item); /*! Prepares array of indices of visible values to search within. This is per-interface global cache. Needed for faster lookup because there could be lookup values. Called whenever columns definition changes, i.e. in setData() and clearColumns(). @see find() */ void updateIndicesForVisibleValues(); //! @return horizontal header, 0 by default. virtual QHeaderView* horizontalHeader() const; //! @return vertical header, 0 by default. virtual QHeaderView* verticalHeader() const; //! Update section of vertical header virtual void updateVerticalHeaderSection(int section) = 0; //! data structure displayed for this object KDbTableViewData *m_data; //! current record (cursor) int m_curRecord; //! current column (cursor) int m_curColumn; //! current record's data KDbRecordData *m_currentRecord; //! data iterator KDbTableViewDataIterator m_itemIterator; //! record's data for inserting KDbRecordData *m_insertRecord; //! true if m_data member is owned by this object bool m_owner; /*! true if a new record is edited; implies: m_recorfEditing == true. */ bool m_newRecordEditing; /*! 'sorting by column' availability flag for widget */ bool m_isSortingEnabled; /*! true if filtering is enabled for the view. */ bool m_isFilteringEnabled; /*! Public version of 'acceptsRecordEditingAfterCellAcceptin' flag (available for a user). It's OR'es together with above flag. */ bool m_acceptsRecordEditAfterCellAccepting; /*! Used in acceptEditor() to avoid infinite recursion, eg. when we're calling acceptRecordEditing() during cell accepting phase. */ bool m_inside_acceptEditor; // no bit field allowed /*! Used in acceptRecordEditing() to avoid infinite recursion, eg. when we're calling acceptRecordEdit() during cell accepting phase. */ bool m_inside_acceptRecordEdit; // no bit field allowed /*! @internal if true, this object automatically accepts record editing (using acceptRecordEditing()) on accepting any cell's edit (i.e. after acceptEditor()). */ bool m_internal_acceptsRecordEditingAfterCellAccepting; /*! true, if inserting empty records are enabled (false by default) */ bool m_emptyRecordInsertingEnabled; /*! Contains 1 if the object is readOnly, 0 if not; otherwise (-1 means "do not know") the 'readOnly' flag from object's internal data structure (KDbTableViewData *KexiTableView::m_data) is reused. */ int m_readOnly; //! @todo really keep this here and not in KexiTableView? /*! true if currently double click action was is performed (so accept/cancel editor shouldn't be executed) */ bool m_contentsMousePressEvent_dblClick; /*! like for readOnly: 1 if inserting is enabled */ int m_insertingEnabled; /*! true, if initDataContents() should be called on show event. */ bool m_initDataContentsOnShow; /*! Set to true in setCursorPosition() to indicate that cursor position was set before show() and it shouldn't be changed on show(). Only used if initDataContentsOnShow is true. */ bool m_cursorPositionSetExplicityBeforeShow; /*! true if spreadSheetMode is enabled. False by default. @see KexiTableView::setSpreadSheetMode() */ bool m_spreadSheetMode; /*! true, if this table accepts dropping data on the records (false by default). */ bool m_dropsAtRecordEnabled; /*! true, if this entire (visible) record should be updated when moving to other record. False by default. For table view with 'record highlighting' flag enabled, it is true. */ bool m_updateEntireRecordWhenMovingToOtherRecord; DeletionPolicy m_deletionPolicy; KexiDataItemInterface *m_editor; /*! Navigation panel, used if navigationPanelEnabled is true. */ KexiRecordNavigatorIface *m_navPanel; //!< main navigation widget bool m_navPanelEnabled; /*! Record number that over which user drags a mouse pointer. Used to indicate dropping possibility for that record. Equal -1 if no indication is needed. */ int m_dragIndicatorLine; /*! Context menu widget. */ QMenu *m_contextMenu; /*! Text of context menu title. */ QString m_contextMenuTitleText; /*! Icon of context menu title. */ QIcon m_contextMenuTitleIcon; /*! True if context menu is enabled. */ bool m_contextMenuEnabled; //! Used by updateAfterCancelRecordEditing() bool m_alsoUpdateNextRecord; /*! Record number (>=0 or -1 == no record) that will be deleted in deleteRecord(). It is set in slotAboutToDeleteRecord(KDbRecordData*,KDbResultInfo*,bool)) slot received from KDbTableViewData member. This value will be used in slotRecordDeleted() after recordDeleted() signal is received from KDbTableViewData member and then cleared (set to -1). */ int m_recordWillBeDeleted; /*! Displays passive error popup label used when invalid data has been entered. */ QPointer m_errorMessagePopup; /*! Used to enable/disable execution of verticalScrollBarValueChanged() when users navigate through records using keyboard, so scrollbar tooltips are not visible. */ bool m_verticalScrollBarValueChanged_enabled; /*! True, if vscrollbar tooltips are enabled (true by default). */ bool m_scrollbarToolTipsEnabled; //! Used to mark recently found value class PositionOfValue { public: PositionOfValue() : firstCharacter(0), lastCharacter(0), exists(false) {} int firstCharacter; int lastCharacter; bool exists; }; /*! Used to mark recently found value. Updated on successful execution of find(). If the current cursor's position changes, or data in the current cell changes, positionOfRecentlyFoundValue.exists is set to false. */ PositionOfValue m_positionOfRecentlyFoundValue; /*! Used to compare whether we're looking for new value. */ QVariant m_recentlySearchedValue; /*! Used to compare whether the search direction has changed. */ KexiSearchAndReplaceViewInterface::Options::SearchDirection m_recentSearchDirection; //! Setup by updateIndicesForVisibleValues() and used by find() QVector m_indicesForVisibleValues; private: /*! >= 0 if a record is edited */ int m_recordEditing; bool m_lengthExceededMessageVisible; //! true if acceptRecordEditing() should be called in setCursorPosition() (true by default) bool m_acceptRecordEditing_in_setCursorPosition_enabled; }; Q_DECLARE_OPERATORS_FOR_FLAGS(KexiDataAwareObjectInterface::CreateEditorFlags) Q_DECLARE_OPERATORS_FOR_FLAGS(KexiDataAwareObjectInterface::CursorPositionFlags) inline bool KexiDataAwareObjectInterface::hasData() const { - if (!m_data) - qDebug() << "No data assigned!"; - return m_data != 0; + return m_data; } inline KDbRecordData *KexiDataAwareObjectInterface::recordAt(int pos) const { KDbRecordData *data = m_data->at(pos); if (!data) qDebug() << "pos:" << pos << "- NO ITEM!!"; else { /* qDebug() << "record:" << record; int i=1; for (KexiTableItem::Iterator it = item->begin();it!=item->end();++it,i++) qDebug() << i<<":" << (*it).toString();*/ } return data; } //! Convenience macro used for KexiDataAwareObjectInterface implementations. #define KEXI_DATAAWAREOBJECTINTERFACE \ public: \ void connectCellSelectedSignal(const QObject* receiver, const char* intIntMember) override { \ connect(this, SIGNAL(cellSelected(int,int)), receiver, intIntMember); \ } \ void connectRecordEditingStartedSignal(const QObject* receiver, const char* intMember) override { \ connect(this, SIGNAL(recordEditingStarted(int)), receiver, intMember); \ } \ void connectRecordEditingTerminatedSignal(const QObject* receiver, const char* voidMember) override { \ connect(this, SIGNAL(recordEditingTerminated(int)), receiver, voidMember); \ } \ void connectUpdateSaveCancelActionsSignal(const QObject* receiver, \ const char* voidMember) override { \ connect(this, SIGNAL(updateSaveCancelActions()), receiver, voidMember); \ } \ void connectReloadActionsSignal(const QObject* receiver, const char* voidMember) override { \ connect(this, SIGNAL(reloadActions()), receiver, voidMember); \ } \ void connectDataSetSignal(const QObject* receiver, \ const char* kexiTableViewDataMember) override { \ connect(this, SIGNAL(dataSet(KDbTableViewData*)), receiver, kexiTableViewDataMember); \ } \ void connectToReloadDataSlot(const QObject* sender, const char* voidSignal) override { \ connect(sender, voidSignal, this, SLOT(reloadData())); \ } #endif diff --git a/src/widget/navigator/KexiProjectNavigator.cpp b/src/widget/navigator/KexiProjectNavigator.cpp index 35c36439f..9dc4dde42 100644 --- a/src/widget/navigator/KexiProjectNavigator.cpp +++ b/src/widget/navigator/KexiProjectNavigator.cpp @@ -1,769 +1,769 @@ /* This file is part of the KDE project Copyright (C) 2002, 2003 Lucijan Busch Copyright (C) 2003-2016 Jarosław Staniek Copyright (C) 2010 Adam Pigg 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 "KexiProjectNavigator.h" #include "KexiProjectTreeView.h" #include "KexiProjectModel.h" #include "KexiProjectModelItem.h" #include "KexiProjectItemDelegate.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 class Q_DECL_HIDDEN KexiProjectNavigator::Private { public: Private(Features features_, KexiProjectNavigator *qq) : features(features_) , q(qq) , emptyStateLabel(0) , prevSelectedPartInfo(0) , readOnly(false) { } ~Private() { delete model; } void clearSelectionIfNeeded() { if (features & ClearSelectionAfterAction) { list->selectionModel()->clear(); } } Features features; KexiProjectNavigator *q; QVBoxLayout *lyr; KexiProjectTreeView *list; QLabel *emptyStateLabel; KActionCollection *actions; KexiItemMenu *itemMenu; KexiGroupMenu *partMenu; QAction *deleteAction, *renameAction, *newObjectAction, *openAction, *designAction, *editTextAction, *executeAction, *dataExportToClipboardAction, *dataExportToFileAction; #ifdef KEXI_QUICK_PRINTING_SUPPORT QAction *printAction, *pageSetupAction; #endif KActionMenu* exportActionMenu; QAction *itemMenuTitle, *partMenuTitle, *exportActionMenu_sep, *pageSetupAction_sep; KexiPart::Info *prevSelectedPartInfo; bool readOnly; KexiProjectModel *model; QString itemsPluginId; }; KexiProjectNavigator::KexiProjectNavigator(QWidget* parent, Features features) : QWidget(parent) , d(new Private(features, this)) { d->actions = new KActionCollection(this); setObjectName("KexiProjectNavigator"); setWindowTitle(xi18nc("@title:window", "Project Navigator")); setWindowIcon(KexiMainWindowIface::global()->thisWidget()->windowIcon()); d->lyr = new QVBoxLayout(this); d->lyr->setContentsMargins(0, 0, 0, 0); d->list = new KexiProjectTreeView(this); if (d->features & Borders) { d->list->setAlternatingRowColors(true); } else { d->list->setFrameStyle(QFrame::NoFrame); QPalette pal(d->list->palette()); pal.setColor(QPalette::Base, Qt::transparent); d->list->setPalette(pal); d->list->setIndentation(0); } d->model = new KexiProjectModel(); connect(d->model, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(slotUpdateEmptyStateLabel())); connect(d->model, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(slotUpdateEmptyStateLabel())); d->list->setModel(d->model); KexiProjectItemDelegate *delegate = new KexiProjectItemDelegate(d->list); d->list->setItemDelegate(delegate); d->lyr->addWidget(d->list); //! @todo KEXI3 port from KGlobalSettings::Private::_k_slotNotifyChange: // connect(KGlobalSettings::self(), SIGNAL(settingsChanged(int)), SLOT(slotSettingsChanged(int))); // slotSettingsChanged(0); connect(d->list->selectionModel(), &QItemSelectionModel::currentChanged, this, &KexiProjectNavigator::slotSelectionChanged); if ((d->features & AllowSingleClickForOpeningItems) && KexiUtils::activateItemsOnSingleClick(d->list)) { connect(d->list, SIGNAL(clicked(QModelIndex)), this, SLOT(slotExecuteItem(QModelIndex))); } else { connect(d->list, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(slotExecuteItem(QModelIndex))); } // actions d->openAction = addAction("open_object", koIcon("document-open"), xi18n("&Open"), xi18n("Open object"), xi18n("Opens object selected in the list."), SLOT(slotOpenObject())); if (KexiMainWindowIface::global() && KexiMainWindowIface::global()->userMode()) { //! @todo some of these actions can be supported once we deliver ACLs... d->deleteAction = 0; d->renameAction = 0; d->designAction = 0; d->editTextAction = 0; d->newObjectAction = 0; } else { d->deleteAction = addAction("edit_delete", koIcon("edit-delete"), xi18n("&Delete..."), xi18n("Delete object"), xi18n("Deletes the object selected in the list."), SLOT(slotRemove())); d->renameAction = addAction("edit_rename", koIcon("edit-rename"), xi18n("&Rename..."), xi18n("Rename object"), xi18n("Renames the object selected in the list."), SLOT(slotRename())); //! @todo enable, doesn't work now: d->renameAction->setShortcut(QKeySequence(Qt::Key_F2)); #ifdef KEXI_SHOW_UNIMPLEMENTED //! @todo plugSharedAction("edit_cut",SLOT(slotCut())); //! @todo plugSharedAction("edit_copy",SLOT(slotCopy())); //! @todo plugSharedAction("edit_paste",SLOT(slotPaste())); #endif d->designAction = addAction("design_object", KexiIcon("mode-selector-design"), xi18n("&Design"), xi18n("Design object"), xi18n("Starts designing of the object selected in the list."), SLOT(slotDesignObject())); d->editTextAction = addAction("editText_object", QIcon(), "Design in Text View", // <- no icon, no i18n, will be updated before use xi18n("Design object in text view"), xi18n("Starts designing of the object in the list in text view."), SLOT(slotEditTextObject())); d->newObjectAction = addAction("new_object", koIcon("document-new"), QString(),QString(), QString(), SLOT(slotNewObject())); } d->executeAction = addAction("data_execute", koIcon("system-run"), xi18n("Execute"), //! @todo tooltip, what's this QString(), QString(), SLOT(slotExecuteObject())); d->actions->addAction("export_object", d->exportActionMenu = new KActionMenu(xi18n("Export"), this)); d->dataExportToClipboardAction = addAction("exportToClipboardAsDataTable", koIcon("edit-copy"), xi18nc("Export->To Clipboard as Data... ", "To &Clipboard..."), xi18n("Export data to clipboard"), xi18n("Exports data from the currently selected table or query to clipboard."), SLOT(slotExportToClipboardAsDataTable())); d->exportActionMenu->addAction(d->dataExportToClipboardAction); d->dataExportToFileAction = addAction("exportToFileAsDataTable", KexiIcon("table"), xi18nc("Export->To File As Data &Table... ", "To &File As Data Table..."), xi18n("Export data to a file"), xi18n("Exports data from the currently selected table or query to a file."), SLOT(slotExportToFileAsDataTable())); d->exportActionMenu->addAction(d->dataExportToFileAction); #ifdef KEXI_QUICK_PRINTING_SUPPORT d->printAction = addAction("print_object", koIcon("document-print"), futureI18n("&Print..."), futureI18n("Print data"), futureI18n("Prints data from the currently selected table or query."), SLOT(slotPrintObject())); //! @todo document-page-setup could be a better icon d->pageSetupAction = addAction("pageSetupForObject", koIcon("configure"), futureI18n("Page Setup..."), futureI18n("Page setup for data"), futureI18n("Shows page setup for printing the active table or query."), SLOT(slotPageSetupForObject())); #endif if (KexiMainWindowIface::global() && KexiMainWindowIface::global()->userMode()) { //! @todo some of these actions can be supported once we deliver ACLs... d->partMenu = 0; } else { d->partMenu = new KexiGroupMenu(this, d->actions); } if (d->features & ContextMenus) { d->itemMenu = new KexiItemMenu(this, d->actions); } else { d->itemMenu = 0; } if (!(d->features & Writable)) { setReadOnly(true); } slotSelectionChanged(QModelIndex()); } void KexiProjectNavigator::setProject(KexiProject* prj, const QString& itemsPartClass, QString* partManagerErrorMessages, bool addAsSearchableModel) { d->itemsPluginId = itemsPartClass; KexiMainWindowIface::global()->removeSearchableModel(d->model); // before model changes d->model->setProject(prj, itemsPartClass, partManagerErrorMessages); if (addAsSearchableModel) { KexiMainWindowIface::global()->addSearchableModel(d->model); } d->list->expandAll(); d->list->setRootIsDecorated(false); slotUpdateEmptyStateLabel(); // Select and set current to first item d->list->setCurrentIndex(d->model->firstPartItem()); d->list->selectionModel()->select(d->list->currentIndex(), QItemSelectionModel::Rows); } QString KexiProjectNavigator::itemsPluginId() const { return d->itemsPluginId; } KexiProjectNavigator::~KexiProjectNavigator() { delete d; } QAction * KexiProjectNavigator::addAction(const QString& name, const QIcon& icon, const QString& text, const QString& toolTip, const QString& whatsThis, const char* slot) { QAction *action = new QAction(icon, text, this); d->actions->addAction(name, action); action->setToolTip(toolTip); action->setWhatsThis(whatsThis); connect(action, SIGNAL(triggered()), this, slot); return action; } void KexiProjectNavigator::contextMenuEvent(QContextMenuEvent* event) { if (!d->list->currentIndex().isValid() || !(d->features & ContextMenus)) return; QModelIndex pointedIndex = d->list->indexAt(d->list->mapFromGlobal(event->globalPos())); KexiProjectModelItem *bit = static_cast(pointedIndex.internalPointer()); if (!bit || !bit->partItem() /*no menu for group*/) { return; } QMenu *pm = 0; if (bit->partItem()) { pm = d->itemMenu; KexiProjectModelItem *par_it = static_cast(bit->parent()); if (par_it->partInfo() && bit->partItem()) { d->itemMenu->update(*par_it->partInfo(), *bit->partItem()); } } if (pm) { pm->exec(event->globalPos()); } event->setAccepted(true); d->clearSelectionIfNeeded(); } void KexiProjectNavigator::slotExecuteItem(const QModelIndex& vitem) { KexiProjectModelItem *it = static_cast(vitem.internalPointer()); if (!it) { qWarning() << "No internal pointer"; return; } if (it->partInfo()->isExecuteSupported()) emit executeItem(it->partItem()); else emit openOrActivateItem(it->partItem(), Kexi::DataViewMode); d->clearSelectionIfNeeded(); } void KexiProjectNavigator::slotSelectionChanged(const QModelIndex& i) { KexiProjectModelItem *it = static_cast(i.internalPointer()); if (!it) { if (KexiMainWindowIface::global() && !KexiMainWindowIface::global()->userMode()) { d->openAction->setEnabled(false); d->designAction->setEnabled(false); d->deleteAction->setEnabled(false); } return; } const bool gotitem = it->partItem(); //! @todo also check if the item is not read only if (d->deleteAction) { d->deleteAction->setEnabled(gotitem && !d->readOnly); } #ifdef KEXI_SHOW_UNIMPLEMENTED //! @todo setAvailable("edit_cut",gotitem); //! @todo setAvailable("edit_copy",gotitem); //! @todo setAvailable("edit_edititem",gotitem); #endif if ( KexiMainWindowIface::global() && !KexiMainWindowIface::global()->userMode() ) { d->openAction->setEnabled(gotitem && (it->partInfo()->supportedViewModes() & Kexi::DataViewMode)); if (d->designAction) { d->designAction->setEnabled(gotitem && (it->partInfo()->supportedViewModes() & Kexi::DesignViewMode)); } if (d->editTextAction) d->editTextAction->setEnabled(gotitem && (it->partInfo()->supportedViewModes() & Kexi::TextViewMode)); if (d->prevSelectedPartInfo != it->partInfo()) { d->prevSelectedPartInfo = it->partInfo(); if (d->newObjectAction) { d->newObjectAction->setText( xi18n("&Create Object: %1...", it->partInfo()->name() )); d->newObjectAction->setIcon(QIcon::fromTheme(it->partInfo()->iconName())); } #if 0 } else { if (d->newObjectAction) { d->newObjectAction->setText(xi18n("&Create Object...")); } #endif } } emit selectionChanged(it->partItem()); } void KexiProjectNavigator::slotRemove() { if (!d->deleteAction || !d->deleteAction->isEnabled() || !(d->features & Writable)) return; KexiProjectModelItem *it = static_cast(d->list->currentIndex().internalPointer()); if (!it || !it->partItem()) return; emit removeItem(it->partItem()); } void KexiProjectNavigator::slotNewObject() { if (!d->newObjectAction || !(d->features & Writable)) return; KexiProjectModelItem *it = static_cast(d->list->currentIndex().internalPointer()); if (!it || !it->partInfo()) return; emit newItem(it->partInfo()); } void KexiProjectNavigator::slotOpenObject() { KexiProjectModelItem *it = static_cast(d->list->currentIndex().internalPointer()); if (!it || !it->partItem()) return; emit openItem(it->partItem(), Kexi::DataViewMode); } void KexiProjectNavigator::slotDesignObject() { if (!d->designAction) return; KexiProjectModelItem *it = static_cast(d->list->currentIndex().internalPointer()); if (!it || !it->partItem()) return; emit openItem(it->partItem(), Kexi::DesignViewMode); } void KexiProjectNavigator::slotEditTextObject() { if (!d->editTextAction) return; KexiProjectModelItem *it = static_cast(d->list->currentIndex().internalPointer()); if (!it || !it->partItem()) return; emit openItem(it->partItem(), Kexi::TextViewMode); } void KexiProjectNavigator::slotCut() { if (!(d->features & Writable)) return; //! @todo } void KexiProjectNavigator::slotCopy() { //! @todo } void KexiProjectNavigator::slotPaste() { if (!(d->features & Writable)) return; //! @todo } void KexiProjectNavigator::slotRename() { if (!d->renameAction || !(d->features & Writable)) return; KexiPart::Item* partItem = selectedPartItem(); if (!partItem) { return; } KexiProjectModelItem *partModelItem = d->model->modelItemFromItem(*partItem); if (!partModelItem) { return; } KexiPart::Info *info = partModelItem->partInfo(); KexiPart::Part *part = Kexi::partManager().partForPluginId(partItem->pluginId()); if (!info || !part) { return; } KexiNameDialog dialog( xi18nc("@info Rename object %1:", "Rename %1:", partItem->name()), this); dialog.buttonBox()->button(QDialogButtonBox::Ok)->setText(xi18nc("@action:button Rename object", "Rename")); if (!d->model->project()) { qWarning() << "No KexiProject assigned!"; return; } dialog.widget()->addNameSubvalidator( //check if new name is allowed new KDbObjectNameValidator(d->model->project()->dbConnection()->driver())); dialog.widget()->setCaptionText(partItem->caption()); dialog.widget()->setNameText(partItem->name()); dialog.setWindowTitle( xi18nc("@title:window Rename Object %1.", "Rename %1", partItem->name())); dialog.setDialogIcon(info->iconName()); dialog.setAllowOverwriting(true); bool overwriteNeeded; if (dialog.execAndCheckIfObjectExists(*d->model->project(), *part, &overwriteNeeded) != QDialog::Accepted) { return; } if (dialog.widget()->nameText() != dialog.widget()->originalNameText() && !d->model->renameItem(partItem, dialog.widget()->nameText())) { return; } d->model->setItemCaption(partItem, dialog.widget()->captionText()); } void KexiProjectNavigator::setFocus() { d->list->setFocus(); } void KexiProjectNavigator::updateItemName(KexiPart::Item& item, bool dirty) { if (!(d->features & Writable)) return; d->model->updateItemName(item, dirty); } void KexiProjectNavigator::selectItem(KexiPart::Item& item) { KexiProjectModelItem *bitem = d->model->modelItemFromItem(item); if (!bitem) return; QModelIndex idx = d->model->indexFromItem(bitem); d->list->setCurrentIndex(idx); d->list->scrollTo(idx); } void KexiProjectNavigator::clearSelection() { d->list->clearSelection(); d->list->scrollToTop(); } void KexiProjectNavigator::slotExecuteObject() { if (!d->executeAction) return; KexiPart::Item* item = selectedPartItem(); if (item) { emit executeItem(item); d->clearSelectionIfNeeded(); } } void KexiProjectNavigator::slotExportToClipboardAsDataTable() { if (!d->dataExportToClipboardAction) return; KexiPart::Item* item = selectedPartItem(); if (item) emit exportItemToClipboardAsDataTable(item); } void KexiProjectNavigator::slotExportToFileAsDataTable() { if (!d->dataExportToFileAction) return; KexiPart::Item* item = selectedPartItem(); if (item) emit exportItemToFileAsDataTable(item); } KexiPart::Item* KexiProjectNavigator::selectedPartItem() const { KexiProjectModelItem *it = static_cast(d->list->currentIndex().internalPointer()); return it ? it->partItem() : 0; } KexiPart::Item* KexiProjectNavigator::partItemWithSearchHighlight() const { KexiProjectModelItem *it = static_cast(d->model->itemWithSearchHighlight().internalPointer()); return it ? it->partItem() : 0; } bool KexiProjectNavigator::actionEnabled(const QString& actionName) const { if (actionName == "project_export_data_table" && (d->features & ContextMenus)) return d->exportActionMenu->isVisible(); qWarning() << "no such action:" << actionName; return false; } void KexiProjectNavigator::slotPrintObject() { #ifdef KEXI_QUICK_PRINTING_SUPPORT if (!d->printAction) return; KexiPart::Item* item = selectedPartItem(); if (item) emit printItem(item); #endif } void KexiProjectNavigator::slotPageSetupForObject() { #ifdef KEXI_QUICK_PRINTING_SUPPORT if (!d->pageSetupAction) return; KexiPart::Item* item = selectedPartItem(); if (item) emit pageSetupForItem(item); #endif } void KexiProjectNavigator::setReadOnly(bool set) { d->readOnly = set; if (d->deleteAction) d->deleteAction->setEnabled(!d->readOnly); if (d->renameAction) d->renameAction->setEnabled(!d->readOnly); if (d->newObjectAction) { d->newObjectAction->setEnabled(!d->readOnly); } } bool KexiProjectNavigator::isReadOnly() const { return d->readOnly; } void KexiProjectNavigator::clear() { d->model->clear(); } KexiProjectModel* KexiProjectNavigator::model() const { return d->model; } void KexiProjectNavigator::slotUpdateEmptyStateLabel() { if (d->model->objectsCount() == 0) { - // handle the empty state with care... http://www.pinterest.com/romanyakimovich/ui-empty-states/ + // handle the empty state with care... https://www.pinterest.com/romanyakimovich/ui-empty-states/ if (!d->emptyStateLabel) { QString imgPath = KIconLoader::global()->iconPath(KexiIconName("document-empty"), - KIconLoader::SizeLarge); //qDebug() << imgPath; d->emptyStateLabel = new QLabel( xi18nc("@info Message for empty state in project navigator", "" "" "" "Your project is empty..." "" "Why not create something?", imgPath), this); d->emptyStateLabel->setPalette( KexiUtils::paletteWithDimmedColor(d->emptyStateLabel->palette(), QPalette::WindowText)); d->emptyStateLabel->setAlignment(Qt::AlignCenter); d->emptyStateLabel->setTextFormat(Qt::RichText); d->emptyStateLabel->setWordWrap(true); QFont f(d->emptyStateLabel->font()); f.setItalic(true); f.setFamily("Times"); f.setPointSize(f.pointSize() * 4 / 3); //d->emptyStateLabel->setFont(f); d->lyr->insertWidget(0, d->emptyStateLabel); } d->emptyStateLabel->show(); } else { delete d->emptyStateLabel; d->emptyStateLabel = 0; } } //-------------------------------------------- KexiMenuBase::KexiMenuBase(QWidget* parent, KActionCollection *col) : QMenu(parent) , m_actionCollection(col) { } KexiMenuBase::~KexiMenuBase() { } QAction* KexiMenuBase::addAction(const QString& actionName) { QAction* action = m_actionCollection->action(actionName); if (action) QMenu::addAction(action); return action; } //-------------------------------------------- KexiItemMenu::KexiItemMenu(QWidget* parent, KActionCollection *col) : KexiMenuBase(parent, col) { } KexiItemMenu::~KexiItemMenu() { } void KexiItemMenu::update(const KexiPart::Info& partInfo, const KexiPart::Item& partItem) { clear(); addSection(QString()); KexiContextMenuUtils::updateTitle(this, partItem.name(), partInfo.name(), partInfo.iconName()); if (m_actionCollection->action("open_object") && m_actionCollection->action("open_object")->isEnabled() && (partInfo.supportedViewModes() & Kexi::DataViewMode)) { addAction("open_object"); } if (m_actionCollection->action("design_object") && m_actionCollection->action("design_object")->isEnabled() && (partInfo.supportedViewModes() & Kexi::DesignViewMode)) { addAction("design_object"); } QAction *a = m_actionCollection->action("editText_object"); if (a && a->isEnabled() && (partInfo.supportedViewModes() & Kexi::TextViewMode)) { QString actionText; QString iconName; KexiPart::getTextViewAction(partInfo.id(), &actionText, &iconName); a->setText(actionText); a->setIcon(QIcon::fromTheme(iconName)); QMenu::addAction(a); } addSeparator(); #ifdef KEXI_SHOW_UNIMPLEMENTED //! @todo plugSharedAction("edit_cut", m_itemMenu); //! @todo plugSharedAction("edit_copy", m_itemMenu); //! @todo addSeparator(); #endif bool addSep = false; if (partInfo.isExecuteSupported()) { addAction("data_execute"); addSep = true; } if (partInfo.isDataExportSupported()) { addAction("export_object"); addSep = true; } if (addSep) addSeparator(); #ifdef KEXI_QUICK_PRINTING_SUPPORT if (partInfo.isPrintingSupported()) addAction("print_object"); if (partInfo.isPrintingSupported()) addAction("pageSetupForObject"); if (m_actionCollection->action("edit_rename") || m_actionCollection->action("edit_delete")) addSeparator(); #endif addAction("edit_rename"); addAction("edit_delete"); } //-------------------------------------------- KexiGroupMenu::KexiGroupMenu(QWidget* parent, KActionCollection *col) : KexiMenuBase(parent, col) { } KexiGroupMenu::~KexiGroupMenu() { } void KexiGroupMenu::update(KexiPart::Info* partInfo) { Q_UNUSED(partInfo); clear(); addAction("new_object"); } diff --git a/src/widget/undo/kundo2group.cpp b/src/widget/undo/kundo2group.cpp index c60d01dc8..57e557dd6 100644 --- a/src/widget/undo/kundo2group.cpp +++ b/src/widget/undo/kundo2group.cpp @@ -1,468 +1,468 @@ /**************************************************************************** ** ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of the QtGui module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** No Commercial Usage ** This file contains pre-release code and may not be distributed. ** You may use this file in accordance with the terms and conditions ** contained in the Technology Preview License Agreement accompanying ** this package. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements -** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** will be met: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** If you have questions regarding the use of this file, please contact ** Nokia at qt-info@nokia.com. ** ** ** ** ** ** ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "kundo2group.h" #include "kundo2stack.h" #include "kundo2stack_p.h" #include #ifndef QT_NO_UNDOGROUP /*! \class KUndo2Group \brief The KUndo2Group class is a group of KUndo2QStack objects. For an overview of the Qt's undo framework, see the \link qundo.html overview\endlink. An application often has multiple undo stacks, one for each opened document. At the same time, an application usually has one undo action and one redo action, which triggers undo or redo in the active document. KUndo2Group is a group of KUndo2QStack objects, one of which may be active. It has an undo() and redo() slot, which calls KUndo2QStack::undo() and KUndo2QStack::redo() for the active stack. It also has the functions createUndoAction() and createRedoAction(). The actions returned by these functions behave in the same way as those returned by KUndo2QStack::createUndoAction() and KUndo2QStack::createRedoAction() of the active stack. Stacks are added to a group with addStack() and removed with removeStack(). A stack is implicitly added to a group when it is created with the group as its parent QObject. It is the programmer's responsibility to specify which stack is active by calling KUndo2QStack::setActive(), usually when the associated document window receives focus. The active stack may also be set with setActiveStack(), and is returned by activeStack(). When a stack is added to a group using addStack(), the group does not take ownership of the stack. This means the stack has to be deleted separately from the group. When a stack is deleted, it is automatically removed from a group. A stack may belong to only one group. Adding it to another group will cause it to be removed from the previous group. A KUndo2Group is also useful in conjunction with KUndo2View. If a KUndo2View is set to watch a group using KUndo2View::setGroup(), it will update itself to display the active stack. */ /*! Creates an empty KUndo2Group object with parent \a parent. \sa addStack() */ KUndo2Group::KUndo2Group(QObject *parent) : QObject(parent), m_active(0) { } /*! Destroys the KUndo2Group. */ KUndo2Group::~KUndo2Group() { // Ensure all KUndo2Stacks no longer refer to this group. QList::iterator it = m_stack_list.begin(); QList::iterator end = m_stack_list.end(); while (it != end) { (*it)->m_group = 0; ++it; } } /*! Adds \a stack to this group. The group does not take ownership of the stack. Another way of adding a stack to a group is by specifying the group as the stack's parent QObject in KUndo2QStack::KUndo2QStack(). In this case, the stack is deleted when the group is deleted, in the usual manner of QObjects. \sa removeStack() stacks() KUndo2QStack::KUndo2QStack() */ void KUndo2Group::addStack(KUndo2QStack *stack) { if (m_stack_list.contains(stack)) return; m_stack_list.append(stack); if (KUndo2Group *other = stack->m_group) other->removeStack(stack); stack->m_group = this; } /*! Removes \a stack from this group. If the stack was the active stack in the group, the active stack becomes 0. \sa addStack() stacks() KUndo2QStack::~KUndo2QStack() */ void KUndo2Group::removeStack(KUndo2QStack *stack) { if (m_stack_list.removeAll(stack) == 0) return; if (stack == m_active) setActiveStack(0); stack->m_group = 0; } /*! Returns a list of stacks in this group. \sa addStack() removeStack() */ QList KUndo2Group::stacks() const { return m_stack_list; } /*! Sets the active stack of this group to \a stack. If the stack is not a member of this group, this function does nothing. Synonymous with calling KUndo2QStack::setActive() on \a stack. The actions returned by createUndoAction() and createRedoAction() will now behave in the same way as those returned by \a stack's KUndo2QStack::createUndoAction() and KUndo2QStack::createRedoAction(). \sa KUndo2QStack::setActive() activeStack() */ void KUndo2Group::setActiveStack(KUndo2QStack *stack) { if (m_active == stack) return; if (m_active != 0) { disconnect(m_active, SIGNAL(canUndoChanged(bool)), this, SIGNAL(canUndoChanged(bool))); disconnect(m_active, SIGNAL(undoTextChanged(QString)), this, SIGNAL(undoTextChanged(QString))); disconnect(m_active, SIGNAL(canRedoChanged(bool)), this, SIGNAL(canRedoChanged(bool))); disconnect(m_active, SIGNAL(redoTextChanged(QString)), this, SIGNAL(redoTextChanged(QString))); disconnect(m_active, SIGNAL(indexChanged(int)), this, SIGNAL(indexChanged(int))); disconnect(m_active, SIGNAL(cleanChanged(bool)), this, SIGNAL(cleanChanged(bool))); } m_active = stack; if (m_active == 0) { emit canUndoChanged(false); emit undoTextChanged(QString()); emit canRedoChanged(false); emit redoTextChanged(QString()); emit cleanChanged(true); emit indexChanged(0); } else { connect(m_active, SIGNAL(canUndoChanged(bool)), this, SIGNAL(canUndoChanged(bool))); connect(m_active, SIGNAL(undoTextChanged(QString)), this, SIGNAL(undoTextChanged(QString))); connect(m_active, SIGNAL(canRedoChanged(bool)), this, SIGNAL(canRedoChanged(bool))); connect(m_active, SIGNAL(redoTextChanged(QString)), this, SIGNAL(redoTextChanged(QString))); connect(m_active, SIGNAL(indexChanged(int)), this, SIGNAL(indexChanged(int))); connect(m_active, SIGNAL(cleanChanged(bool)), this, SIGNAL(cleanChanged(bool))); emit canUndoChanged(m_active->canUndo()); emit undoTextChanged(m_active->undoText()); emit canRedoChanged(m_active->canRedo()); emit redoTextChanged(m_active->redoText()); emit cleanChanged(m_active->isClean()); emit indexChanged(m_active->index()); } emit activeStackChanged(m_active); } /*! Returns the active stack of this group. If none of the stacks are active, or if the group is empty, this function returns 0. \sa setActiveStack() KUndo2QStack::setActive() */ KUndo2QStack *KUndo2Group::activeStack() const { return m_active; } /*! Calls KUndo2QStack::undo() on the active stack. If none of the stacks are active, or if the group is empty, this function does nothing. \sa redo() canUndo() setActiveStack() */ void KUndo2Group::undo() { if (m_active != 0) m_active->undo(); } /*! Calls KUndo2QStack::redo() on the active stack. If none of the stacks are active, or if the group is empty, this function does nothing. \sa undo() canRedo() setActiveStack() */ void KUndo2Group::redo() { if (m_active != 0) m_active->redo(); } /*! Returns the value of the active stack's KUndo2QStack::canUndo(). If none of the stacks are active, or if the group is empty, this function returns false. \sa canRedo() setActiveStack() */ bool KUndo2Group::canUndo() const { return m_active != 0 && m_active->canUndo(); } /*! Returns the value of the active stack's KUndo2QStack::canRedo(). If none of the stacks are active, or if the group is empty, this function returns false. \sa canUndo() setActiveStack() */ bool KUndo2Group::canRedo() const { return m_active != 0 && m_active->canRedo(); } /*! Returns the value of the active stack's KUndo2QStack::undoActionText(). If none of the stacks are active, or if the group is empty, this function returns an empty string. \sa undoItemText() redoActionText() setActiveStack() */ QString KUndo2Group::undoText() const { return m_active == 0 ? QString() : m_active->undoText(); } /*! Returns the value of the active stack's KUndo2QStack::redoActionText(). If none of the stacks are active, or if the group is empty, this function returns an empty string. \sa redoItemText() undoActionText() setActiveStack() */ QString KUndo2Group::redoText() const { return m_active == 0 ? QString() : m_active->redoText(); } /*! Returns the value of the active stack's KUndo2QStack::isClean(). If none of the stacks are active, or if the group is empty, this function returns true. \sa setActiveStack() */ bool KUndo2Group::isClean() const { return m_active == 0 || m_active->isClean(); } #ifndef QT_NO_ACTION /*! Creates an undo QAction object with parent \a parent. Triggering this action will cause a call to KUndo2QStack::undo() on the active stack. The text of this action will always be the text of the command which will be undone in the next call to undo(), prefixed by \a prefix. If there is no command available for undo, if the group is empty or if none of the stacks are active, this action will be disabled. If \a prefix is empty, the default prefix "Undo" is used. \sa createRedoAction() canUndo() KUndo2Command::text() */ QAction *KUndo2Group::createUndoAction(QObject *parent) const { KUndo2Action *result = new KUndo2Action(i18n("Undo %1"), i18nc("Default text for undo action", "Undo"), parent); result->setEnabled(canUndo()); result->setPrefixedText(undoText()); connect(this, SIGNAL(canUndoChanged(bool)), result, SLOT(setEnabled(bool))); connect(this, SIGNAL(undoTextChanged(QString)), result, SLOT(setPrefixedText(QString))); connect(result, SIGNAL(triggered()), this, SLOT(undo())); return result; } /*! Creates an redo QAction object with parent \a parent. Triggering this action will cause a call to KUndo2QStack::redo() on the active stack. The text of this action will always be the text of the command which will be redone in the next call to redo(), prefixed by \a prefix. If there is no command available for redo, if the group is empty or if none of the stacks are active, this action will be disabled. If \a prefix is empty, the default prefix "Undo" is used. \sa createUndoAction() canRedo() KUndo2Command::text() */ QAction *KUndo2Group::createRedoAction(QObject *parent) const { KUndo2Action *result = new KUndo2Action(i18n("Redo %1"), i18nc("Default text for redo action", "Redo"), parent); result->setEnabled(canRedo()); result->setPrefixedText(redoText()); connect(this, SIGNAL(canRedoChanged(bool)), result, SLOT(setEnabled(bool))); connect(this, SIGNAL(redoTextChanged(QString)), result, SLOT(setPrefixedText(QString))); connect(result, SIGNAL(triggered()), this, SLOT(redo())); return result; } #endif // QT_NO_ACTION /*! \fn void KUndo2Group::activeStackChanged(KUndo2QStack *stack) This signal is emitted whenever the active stack of the group changes. This can happen when setActiveStack() or KUndo2QStack::setActive() is called, or when the active stack is removed form the group. \a stack is the new active stack. If no stack is active, \a stack is 0. \sa setActiveStack() KUndo2QStack::setActive() */ /*! \fn void KUndo2Group::indexChanged(int idx) This signal is emitted whenever the active stack emits KUndo2QStack::indexChanged() or the active stack changes. \a idx is the new current index, or 0 if the active stack is 0. \sa KUndo2QStack::indexChanged() setActiveStack() */ /*! \fn void KUndo2Group::cleanChanged(bool clean) This signal is emitted whenever the active stack emits KUndo2QStack::cleanChanged() or the active stack changes. \a clean is the new state, or true if the active stack is 0. \sa KUndo2QStack::cleanChanged() setActiveStack() */ /*! \fn void KUndo2Group::canUndoChanged(bool canUndo) This signal is emitted whenever the active stack emits KUndo2QStack::canUndoChanged() or the active stack changes. \a canUndo is the new state, or false if the active stack is 0. \sa KUndo2QStack::canUndoChanged() setActiveStack() */ /*! \fn void KUndo2Group::canRedoChanged(bool canRedo) This signal is emitted whenever the active stack emits KUndo2QStack::canRedoChanged() or the active stack changes. \a canRedo is the new state, or false if the active stack is 0. \sa KUndo2QStack::canRedoChanged() setActiveStack() */ /*! \fn void KUndo2Group::undoTextChanged(const QString &undoText) This signal is emitted whenever the active stack emits KUndo2QStack::undoTextChanged() or the active stack changes. \a undoText is the new state, or an empty string if the active stack is 0. \sa KUndo2QStack::undoTextChanged() setActiveStack() */ /*! \fn void KUndo2Group::redoTextChanged(const QString &redoText) This signal is emitted whenever the active stack emits KUndo2QStack::redoTextChanged() or the active stack changes. \a redoText is the new state, or an empty string if the active stack is 0. \sa KUndo2QStack::redoTextChanged() setActiveStack() */ #endif // QT_NO_UNDOGROUP diff --git a/src/widget/undo/kundo2group.h b/src/widget/undo/kundo2group.h index e240c8450..abaedc11a 100644 --- a/src/widget/undo/kundo2group.h +++ b/src/widget/undo/kundo2group.h @@ -1,104 +1,104 @@ /**************************************************************************** ** ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of the QtGui module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** No Commercial Usage ** This file contains pre-release code and may not be distributed. ** You may use this file in accordance with the terms and conditions ** contained in the Technology Preview License Agreement accompanying ** this package. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements -** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** will be met: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** If you have questions regarding the use of this file, please contact ** Nokia at qt-info@nokia.com. ** ** ** ** ** ** ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #ifndef KUNDO2GROUP_H #define KUNDO2GROUP_H #include #include #include "kexiundo_export.h" class KUndo2GroupPrivate; class KUndo2QStack; class QAction; #ifndef QT_NO_UNDOGROUP class KEXIUNDO_EXPORT KUndo2Group : public QObject { Q_OBJECT Q_DECLARE_PRIVATE(KUndo2Group) public: explicit KUndo2Group(QObject *parent = 0); ~KUndo2Group(); void addStack(KUndo2QStack *stack); void removeStack(KUndo2QStack *stack); QList stacks() const; KUndo2QStack *activeStack() const; #ifndef QT_NO_ACTION QAction *createUndoAction(QObject *parent) const; QAction *createRedoAction(QObject *parent) const; #endif // QT_NO_ACTION bool canUndo() const; bool canRedo() const; QString undoText() const; QString redoText() const; bool isClean() const; public Q_SLOTS: void undo(); void redo(); void setActiveStack(KUndo2QStack *stack); Q_SIGNALS: void activeStackChanged(KUndo2QStack *stack); void indexChanged(int idx); void cleanChanged(bool clean); void canUndoChanged(bool canUndo); void canRedoChanged(bool canRedo); void undoTextChanged(const QString &undoActionText); void redoTextChanged(const QString &redoActionText); private: // from QUndoGroupPrivate KUndo2QStack *m_active; QList m_stack_list; Q_DISABLE_COPY(KUndo2Group) }; #endif // QT_NO_UNDOGROUP #endif // KUNDO2GROUP_H diff --git a/src/widget/undo/kundo2magicstring.h b/src/widget/undo/kundo2magicstring.h index 48eddf336..48229d87d 100644 --- a/src/widget/undo/kundo2magicstring.h +++ b/src/widget/undo/kundo2magicstring.h @@ -1,319 +1,319 @@ /* * Copyright (c) 2014 Dmitry Kazakov * Copyright (c) 2014 Alexander Potashev * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KUNDO2MAGICSTRING_H #define KUNDO2MAGICSTRING_H #include #include #include #include "kexiundo_export.h" /** * \class KUndo2MagicString * The class is a special wrapper for a string that is * going to passed to a KUndo2Command and be later shown in the undo * history and undo action in menu. The strings like that must have * (qtundo-format) context to let translators know that they are * allowed to use magic split in them. * * Magic split is used in some languages to split the message in the * undo history docker (which is either verb or noun in + * href="https://en.wikipedia.org/wiki/Nominative_case">noun in * nominative) and the message in undo/redo actions (which is - * usually a noun + * usually a noun * in accusative). When the translator needs it he, splits two * translations with '\n' symbol and the magic string will recognize * it. * * \note KUndo2MagicString will never support concatenation operators, * because in many languages you cannot combine words without * knowing the proper case. */ class KEXIUNDO_EXPORT KUndo2MagicString { public: /** * Construct an empty string. Note that you cannot create a * non-empy string without special functions, all the calls to which * are processed by xgettext. */ KUndo2MagicString(); /** * Fetch the main translated string. That is the one that goes to * undo history and resembles the action name in verb/nominative */ QString toString() const; /** * Fetch the secondary string which will go to the undo/redo * action. This is usually a noun in accusative. If the * translator didn't provide a secondary string, toString() and * toSecondaryString() return the same values. */ QString toSecondaryString() const; /** * \return true if the contained string is empty */ bool isEmpty() const; bool operator==(const KUndo2MagicString &rhs) const; bool operator!=(const KUndo2MagicString &rhs) const; private: /** * Construction of a magic string is allowed only with the means * of speacial macros which resemble their kde-wide counterparts */ explicit KUndo2MagicString(const QString &text); friend KUndo2MagicString kundo2_noi18n(const QString &text); template friend KUndo2MagicString kundo2_noi18n(const char *text, const A1 &a1); template friend KUndo2MagicString kundo2_noi18n(const char *text, const A1 &a1, const A2 &a2); template friend KUndo2MagicString kundo2_noi18n(const char *text, const A1 &a1, const A2 &a2, const A3 &a3); template friend KUndo2MagicString kundo2_noi18n(const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4); friend KUndo2MagicString kundo2_i18n(const char *text); template friend KUndo2MagicString kundo2_i18n(const char *text, const A1 &a1); template friend KUndo2MagicString kundo2_i18n(const char *text, const A1 &a1, const A2 &a2); template friend KUndo2MagicString kundo2_i18n(const char *text, const A1 &a1, const A2 &a2, const A3 &a3); template friend KUndo2MagicString kundo2_i18n(const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4); friend KUndo2MagicString kundo2_i18nc(const char *ctxt, const char *text); template friend KUndo2MagicString kundo2_i18nc(const char *ctxt, const char *text, const A1 &a1); template friend KUndo2MagicString kundo2_i18nc(const char *ctxt, const char *text, const A1 &a1, const A2 &a2); template friend KUndo2MagicString kundo2_i18nc(const char *ctxt, const char *text, const A1 &a1, const A2 &a2, const A3 &a3); template friend KUndo2MagicString kundo2_i18np(const char *sing, const char *plur, const A1 &a1); template friend KUndo2MagicString kundo2_i18np(const char *sing, const char *plur, const A1 &a1, const A2 &a2); template friend KUndo2MagicString kundo2_i18np(const char *sing, const char *plur, const A1 &a1, const A2 &a2, const A3 &a3); template friend KUndo2MagicString kundo2_i18ncp(const char *ctxt, const char *sing, const char *plur, const A1 &a1); template friend KUndo2MagicString kundo2_i18ncp(const char *ctxt, const char *sing, const char *plur, const A1 &a1, const A2 &a2); template friend KUndo2MagicString kundo2_i18ncp(const char *ctxt, const char *sing, const char *plur, const A1 &a1, const A2 &a2, const A3 &a3); private: QString m_text; }; inline QDebug operator<<(QDebug dbg, const KUndo2MagicString &v) { if (v.toString() != v.toSecondaryString()) { dbg.nospace() << v.toString() << "(" << v.toSecondaryString() << ")"; } else { dbg.nospace() << v.toString(); } return dbg.space(); } /** * This is a special wrapper to a string which tells explicitly * that we don't need a translation for a given string. It is used * either in testing or internal commands, which don't go to the * stack directly. */ inline KUndo2MagicString kundo2_noi18n(const QString &text) { return KUndo2MagicString(text); } template inline KUndo2MagicString kundo2_noi18n(const char *text, const A1 &a1) { return KUndo2MagicString(QString(text).arg(a1)); } template inline KUndo2MagicString kundo2_noi18n(const char *text, const A1 &a1, const A2 &a2) { return KUndo2MagicString(QString(text).arg(a1).arg(a2)); } template inline KUndo2MagicString kundo2_noi18n(const char *text, const A1 &a1, const A2 &a2, const A3 &a3) { return KUndo2MagicString(QString(text).arg(a1).arg(a2).arg(a3)); } template inline KUndo2MagicString kundo2_noi18n(const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4) { return KUndo2MagicString(QString(text).arg(a1).arg(a2).arg(a3).arg(a4)); } /** * Same as ki18n, but is supposed to work with strings going to * undo stack */ inline KUndo2MagicString kundo2_i18n(const char *text) { return KUndo2MagicString(xi18nc("@info (qtundo-format)", text)); } template inline KUndo2MagicString kundo2_i18n(const char *text, const A1 &a1) { return KUndo2MagicString(xi18nc("@info (qtundo-format)", text, a1)); } template inline KUndo2MagicString kundo2_i18n(const char *text, const A1 &a1, const A2 &a2) { return KUndo2MagicString(xi18nc("@info (qtundo-format)", text, a1, a2)); } template inline KUndo2MagicString kundo2_i18n(const char *text, const A1 &a1, const A2 &a2, const A3 &a3) { return KUndo2MagicString(xi18nc("@info (qtundo-format)", text, a1, a2, a3)); } template inline KUndo2MagicString kundo2_i18n(const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4) { return KUndo2MagicString(xi18nc("@info (qtundo-format)", text, a1, a2, a3, a4)); } inline QString prependContext(const char *ctxt) { return QString("@info (qtundo-format) %1").arg(ctxt); } /** * Same as ki18nc, but is supposed to work with strings going to * undo stack */ inline KUndo2MagicString kundo2_i18nc(const char *ctxt, const char *text) { return KUndo2MagicString(xi18nc(prependContext(ctxt).toLatin1().data(), text)); } template inline KUndo2MagicString kundo2_i18nc(const char *ctxt, const char *text, const A1 &a1) { return KUndo2MagicString(xi18nc(prependContext(ctxt).toLatin1().data(), text, a1)); } template inline KUndo2MagicString kundo2_i18nc(const char *ctxt, const char *text, const A1 &a1, const A2 &a2) { return KUndo2MagicString(xi18nc(prependContext(ctxt).toLatin1().data(), text, a1, a2)); } template inline KUndo2MagicString kundo2_i18nc(const char *ctxt, const char *text, const A1 &a1, const A2 &a2, const A3 &a3) { return KUndo2MagicString(xi18nc(prependContext(ctxt).toLatin1().data(), text, a1, a2, a3)); } template inline KUndo2MagicString kundo2_i18nc(const char *ctxt, const char *text, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4) { return KUndo2MagicString(xi18nc(prependContext(ctxt).toLatin1().data(), text, a1, a2, a3, a4)); } /** * Same as ki18np, but is supposed to work with strings going to * undo stack */ template inline KUndo2MagicString kundo2_i18np(const char *sing, const char *plur, const A1 &a1) { return KUndo2MagicString(xi18ncp("@info (qtundo-format)", sing, plur, a1)); } template inline KUndo2MagicString kundo2_i18np(const char *sing, const char *plur, const A1 &a1, const A2 &a2) { return xi18ncp("@info (qtundo-format)", sing, plur, a1, a2); } template inline KUndo2MagicString kundo2_i18np(const char *sing, const char *plur, const A1 &a1, const A2 &a2, const A3 &a3) { return xi18ncp("@info (qtundo-format)", sing, plur, a1, a2, a3); } template inline KUndo2MagicString kundo2_i18np(const char *sing, const char *plur, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4) { return xi18ncp("@info (qtundo-format)", sing, plur, a1, a2, a3, a4); } /** * Same as ki18ncp, but is supposed to work with strings going to * undo stack */ template inline KUndo2MagicString kundo2_i18ncp(const char *ctxt, const char *sing, const char *plur, const A1 &a1) { return KUndo2MagicString(xi18ncp(prependContext(ctxt).toLatin1().data(), sing, plur, a1)); } template inline KUndo2MagicString kundo2_i18ncp(const char *ctxt, const char *sing, const char *plur, const A1 &a1, const A2 &a2) { return xi18ncp(prependContext(ctxt).toLatin1().data(), sing, plur, a1, a2); } template inline KUndo2MagicString kundo2_i18ncp(const char *ctxt, const char *sing, const char *plur, const A1 &a1, const A2 &a2, const A3 &a3) { return xi18ncp(prependContext(ctxt).toLatin1().data(), sing, plur, a1, a2, a3); } template inline KUndo2MagicString kundo2_i18ncp(const char *ctxt, const char *sing, const char *plur, const A1 &a1, const A2 &a2, const A3 &a3, const A4 &a4) { return xi18ncp(prependContext(ctxt).toLatin1().data(), sing, plur, a1, a2, a3, a4); } #endif /* KUNDO2MAGICSTRING_H */ diff --git a/src/widget/undo/kundo2model.cpp b/src/widget/undo/kundo2model.cpp index 8c4f7e0b6..75a769c9b 100644 --- a/src/widget/undo/kundo2model.cpp +++ b/src/widget/undo/kundo2model.cpp @@ -1,219 +1,219 @@ /* This file is part of the KDE project * Copyright (C) 2010 Matus Talcik * * 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. */ /**************************************************************************** ** ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of the QtGui module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** No Commercial Usage ** This file contains pre-release code and may not be distributed. ** You may use this file in accordance with the terms and conditions ** contained in the Technology Preview License Agreement accompanying ** this package. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements -** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** will be met: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** If you have questions regarding the use of this file, please contact ** Nokia at qt-info@nokia.com. ** ** ** ** ** ** ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "kundo2model.h" #include KUndo2Model::KUndo2Model(QObject *parent) : QAbstractItemModel(parent) { m_stack = 0; m_sel_model = new QItemSelectionModel(this, this); connect(m_sel_model, SIGNAL(currentChanged(QModelIndex, QModelIndex)), this, SLOT(setStackCurrentIndex(QModelIndex))); m_emty_label = i18n(""); } QItemSelectionModel *KUndo2Model::selectionModel() const { return m_sel_model; } KUndo2QStack *KUndo2Model::stack() const { return m_stack; } void KUndo2Model::setStack(KUndo2QStack *stack) { if (m_stack == stack) return; if (m_stack != 0) { disconnect(m_stack, SIGNAL(cleanChanged(bool)), this, SLOT(stackChanged())); disconnect(m_stack, SIGNAL(indexChanged(int)), this, SLOT(stackChanged())); disconnect(m_stack, SIGNAL(destroyed(QObject*)), this, SLOT(stackDestroyed(QObject*))); disconnect(m_stack, SIGNAL(indexChanged(int)), this, SLOT(addImage(int))); } m_stack = stack; if (m_stack != 0) { connect(m_stack, SIGNAL(cleanChanged(bool)), this, SLOT(stackChanged())); connect(m_stack, SIGNAL(indexChanged(int)), this, SLOT(stackChanged())); connect(m_stack, SIGNAL(destroyed(QObject*)), this, SLOT(stackDestroyed(QObject*))); connect(m_stack, SIGNAL(indexChanged(int)), this, SLOT(addImage(int))); } stackChanged(); } void KUndo2Model::stackDestroyed(QObject *obj) { if (obj != m_stack) return; m_stack = 0; stackChanged(); } void KUndo2Model::stackChanged() { beginResetModel(); endResetModel(); m_sel_model->setCurrentIndex(selectedIndex(), QItemSelectionModel::ClearAndSelect); } void KUndo2Model::setStackCurrentIndex(const QModelIndex &index) { if (m_stack == 0) return; if (index == selectedIndex()) return; if (index.column() != 0) return; m_stack->setIndex(index.row()); } QModelIndex KUndo2Model::selectedIndex() const { return m_stack == 0 ? QModelIndex() : createIndex(m_stack->index(), 0); } QModelIndex KUndo2Model::index(int row, int column, const QModelIndex &parent) const { if (m_stack == 0) return QModelIndex(); if (parent.isValid()) return QModelIndex(); if (column != 0) return QModelIndex(); if (row < 0 || row > m_stack->count()) return QModelIndex(); return createIndex(row, column); } QModelIndex KUndo2Model::parent(const QModelIndex&) const { return QModelIndex(); } int KUndo2Model::rowCount(const QModelIndex &parent) const { if (m_stack == 0) return 0; if (parent.isValid()) return 0; return m_stack->count() + 1; } int KUndo2Model::columnCount(const QModelIndex&) const { return 1; } QVariant KUndo2Model::data(const QModelIndex &index, int role) const { if (m_stack == 0) return QVariant(); if (index.column() != 0) return QVariant(); if (index.row() < 0 || index.row() > m_stack->count()) return QVariant(); if (role == Qt::DisplayRole) { if (index.row() == 0) return m_emty_label; return m_stack->text(index.row() - 1); } else if (role == Qt::DecorationRole) { if (index.row() == m_stack->cleanIndex() && !m_clean_icon.isNull()) return m_clean_icon; } return QVariant(); } QString KUndo2Model::emptyLabel() const { return m_emty_label; } void KUndo2Model::setEmptyLabel(const QString &label) { m_emty_label = label; stackChanged(); } void KUndo2Model::setCleanIcon(const QIcon &icon) { m_clean_icon = icon; stackChanged(); } QIcon KUndo2Model::cleanIcon() const { return m_clean_icon; } diff --git a/src/widget/undo/kundo2model.h b/src/widget/undo/kundo2model.h index 2a0ffc72a..5319eccf7 100644 --- a/src/widget/undo/kundo2model.h +++ b/src/widget/undo/kundo2model.h @@ -1,105 +1,105 @@ /* This file is part of the KDE project * Copyright (C) 2010 Matus Talcik * * 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. */ /**************************************************************************** ** ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of the QtGui module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** No Commercial Usage ** This file contains pre-release code and may not be distributed. ** You may use this file in accordance with the terms and conditions ** contained in the Technology Preview License Agreement accompanying ** this package. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements -** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** will be met: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** If you have questions regarding the use of this file, please contact ** Nokia at qt-info@nokia.com. ** ** ** ** ** ** ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #ifndef K_UNDO_2_MODEL #define K_UNDO_2_MODEL #include #include "kundo2stack.h" #include #include class KUndo2Model : public QAbstractItemModel { Q_OBJECT public: explicit KUndo2Model(QObject *parent = 0); KUndo2QStack *stack() const; virtual QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; virtual QModelIndex parent(const QModelIndex &child) const override; virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override; virtual int columnCount(const QModelIndex &parent = QModelIndex()) const override; virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QModelIndex selectedIndex() const; QItemSelectionModel *selectionModel() const; QString emptyLabel() const; void setEmptyLabel(const QString &label); void setCleanIcon(const QIcon &icon); QIcon cleanIcon() const; public Q_SLOTS: void setStack(KUndo2QStack *stack); private Q_SLOTS: void stackChanged(); void stackDestroyed(QObject *obj); void setStackCurrentIndex(const QModelIndex &index); private: KUndo2QStack *m_stack; QItemSelectionModel *m_sel_model; QString m_emty_label; QIcon m_clean_icon; }; #endif diff --git a/src/widget/undo/kundo2stack.cpp b/src/widget/undo/kundo2stack.cpp index 859cf02c6..97802f41d 100644 --- a/src/widget/undo/kundo2stack.cpp +++ b/src/widget/undo/kundo2stack.cpp @@ -1,1434 +1,1434 @@ /* * Copyright (c) 2014 Dmitry Kazakov * Copyright (c) 2014 Mohit Goyal * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /**************************************************************************** ** ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of the QtGui module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** No Commercial Usage ** This file contains pre-release code and may not be distributed. ** You may use this file in accordance with the terms and conditions ** contained in the Technology Preview License Agreement accompanying ** this package. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements -** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** will be met: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** If you have questions regarding the use of this file, please contact ** Nokia at qt-info@nokia.com. ** ** ** ** ** ** ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #include #include #include #include "kundo2stack.h" #include "kundo2stack_p.h" #include "kundo2group.h" #include #include #include #ifndef QT_NO_UNDOCOMMAND /*! \class KUndo2Command \brief The KUndo2Command class is the base class of all commands stored on a KUndo2QStack. For an overview of Qt's Undo Framework, see the \l{Overview of Qt's Undo Framework}{overview document}. A KUndo2Command represents a single editing action on a document; for example, inserting or deleting a block of text in a text editor. KUndo2Command can apply a change to the document with redo() and undo the change with undo(). The implementations for these functions must be provided in a derived class. \snippet doc/src/snippets/code/src_gui_util_qundostack.cpp 0 A KUndo2Command has an associated text(). This is a short string describing what the command does. It is used to update the text properties of the stack's undo and redo actions; see KUndo2QStack::createUndoAction() and KUndo2QStack::createRedoAction(). KUndo2Command objects are owned by the stack they were pushed on. KUndo2QStack deletes a command if it has been undone and a new command is pushed. For example: \snippet doc/src/snippets/code/src_gui_util_qundostack.cpp 1 In effect, when a command is pushed, it becomes the top-most command on the stack. To support command compression, KUndo2Command has an id() and the virtual function mergeWith(). These functions are used by KUndo2QStack::push(). To support command macros, a KUndo2Command object can have any number of child commands. Undoing or redoing the parent command will cause the child commands to be undone or redone. A command can be assigned to a parent explicitly in the constructor. In this case, the command will be owned by the parent. The parent in this case is usually an empty command, in that it doesn't provide its own implementation of undo() and redo(). Instead, it uses the base implementations of these functions, which simply call undo() or redo() on all its children. The parent should, however, have a meaningful text(). \snippet doc/src/snippets/code/src_gui_util_qundostack.cpp 2 Another way to create macros is to use the convenience functions KUndo2QStack::beginMacro() and KUndo2QStack::endMacro(). \sa KUndo2QStack */ /*! Constructs a KUndo2Command object with the given \a parent and \a text. If \a parent is not 0, this command is appended to parent's child list. The parent command then owns this command and will delete it in its destructor. \sa ~KUndo2Command() */ KUndo2Command::KUndo2Command(const KUndo2MagicString &text, KUndo2Command *parent): m_hasParent(parent != 0), m_timedID(0), m_endOfCommand(QTime::currentTime()) { d = new KUndo2CommandPrivate; if (parent != 0) { parent->d->child_list.append(this); } setText(text); setTime(); } /*! Constructs a KUndo2Command object with parent \a parent. If \a parent is not 0, this command is appended to parent's child list. The parent command then owns this command and will delete it in its destructor. \sa ~KUndo2Command() */ KUndo2Command::KUndo2Command(KUndo2Command *parent): m_hasParent(parent != 0),m_timedID(0) { d = new KUndo2CommandPrivate; if (parent != 0) parent->d->child_list.append(this); setTime(); } /*! Destroys the KUndo2Command object and all child commands. \sa KUndo2Command() */ KUndo2Command::~KUndo2Command() { qDeleteAll(d->child_list); delete d; } /*! Returns the ID of this command. A command ID is used in command compression. It must be an integer unique to this command's class, or -1 if the command doesn't support compression. If the command supports compression this function must be overridden in the derived class to return the correct ID. The base implementation returns -1. KUndo2QStack::push() will only try to merge two commands if they have the same ID, and the ID is not -1. \sa mergeWith(), KUndo2QStack::push() */ int KUndo2Command::id() const { return -1; } /*! Attempts to merge this command with \a command. Returns true on success; otherwise returns false. If this function returns true, calling this command's redo() must have the same effect as redoing both this command and \a command. Similarly, calling this command's undo() must have the same effect as undoing \a command and this command. KUndo2QStack will only try to merge two commands if they have the same id, and the id is not -1. The default implementation returns false. \snippet doc/src/snippets/code/src_gui_util_qundostack.cpp 3 \sa id() KUndo2QStack::push() */ bool KUndo2Command::mergeWith(const KUndo2Command *command) { Q_UNUSED(command); return false; } /*! Applies a change to the document. This function must be implemented in the derived class. Calling KUndo2QStack::push(), KUndo2QStack::undo() or KUndo2QStack::redo() from this function leads to undefined beahavior. The default implementation calls redo() on all child commands. \sa undo() */ void KUndo2Command::redo() { for (int i = 0; i < d->child_list.size(); ++i) d->child_list.at(i)->redo(); } /*! Reverts a change to the document. After undo() is called, the state of the document should be the same as before redo() was called. This function must be implemented in the derived class. Calling KUndo2QStack::push(), KUndo2QStack::undo() or KUndo2QStack::redo() from this function leads to undefined beahavior. The default implementation calls undo() on all child commands in reverse order. \sa redo() */ void KUndo2Command::undo() { for (int i = d->child_list.size() - 1; i >= 0; --i) d->child_list.at(i)->undo(); } /*! Returns a short text string describing what this command does; for example, "insert text". The text is used when the text properties of the stack's undo and redo actions are updated. \sa setText(), KUndo2QStack::createUndoAction(), KUndo2QStack::createRedoAction() */ QString KUndo2Command::actionText() const { if(d->actionText!=NULL) return d->actionText; else return QString(); } /*! Returns a short text string describing what this command does; for example, "insert text". The text is used when the text properties of the stack's undo and redo actions are updated. \sa setText(), KUndo2QStack::createUndoAction(), KUndo2QStack::createRedoAction() */ KUndo2MagicString KUndo2Command::text() const { return d->text; } /*! Sets the command's text to be the \a text specified. The specified text should be a short user-readable string describing what this command does. \sa text() KUndo2QStack::createUndoAction() KUndo2QStack::createRedoAction() */ void KUndo2Command::setText(const KUndo2MagicString &undoText) { d->text = undoText; d->actionText = undoText.toSecondaryString(); } /*! Returns the number of child commands in this command. \sa child() */ int KUndo2Command::childCount() const { return d->child_list.count(); } /*! Returns the child command at \a index. \sa childCount(), KUndo2QStack::command() */ const KUndo2Command *KUndo2Command::child(int index) const { if (index < 0 || index >= d->child_list.count()) return 0; return d->child_list.at(index); } bool KUndo2Command::hasParent() { return m_hasParent; } int KUndo2Command::timedId() { return m_timedID; } void KUndo2Command::setTimedID(int value) { m_timedID = value; } bool KUndo2Command::timedMergeWith(KUndo2Command *other) { if(other->timedId() == this->timedId() && other->timedId()!=-1 ) m_mergeCommandsVector.append(other); else return false; return true; } void KUndo2Command::setTime() { m_timeOfCreation = QTime::currentTime(); } QTime KUndo2Command::time() { return m_timeOfCreation; } void KUndo2Command::setEndTime() { m_endOfCommand = QTime::currentTime(); } QTime KUndo2Command::endTime() { return m_endOfCommand; } void KUndo2Command::undoMergedCommands() { undo(); if (!mergeCommandsVector().isEmpty()) { QVectorIterator it(mergeCommandsVector()); it.toFront(); while (it.hasNext()) { KUndo2Command* cmd = it.next(); cmd->undoMergedCommands(); } } } void KUndo2Command::redoMergedCommands() { if (!mergeCommandsVector().isEmpty()) { QVectorIterator it(mergeCommandsVector()); it.toBack(); while (it.hasPrevious()) { KUndo2Command* cmd = it.previous(); cmd->redoMergedCommands(); } } redo(); } QVector KUndo2Command::mergeCommandsVector() { return m_mergeCommandsVector; } bool KUndo2Command::isMerged() { return !m_mergeCommandsVector.isEmpty(); } KUndo2CommandExtraData* KUndo2Command::extraData() const { return d->extraData.data(); } void KUndo2Command::setExtraData(KUndo2CommandExtraData *data) { d->extraData.reset(data); } #endif // QT_NO_UNDOCOMMAND #ifndef QT_NO_UNDOSTACK /*! \class KUndo2QStack \brief The KUndo2QStack class is a stack of KUndo2Command objects. For an overview of Qt's Undo Framework, see the \l{Overview of Qt's Undo Framework}{overview document}. An undo stack maintains a stack of commands that have been applied to a document. New commands are pushed on the stack using push(). Commands can be undone and redone using undo() and redo(), or by triggering the actions returned by createUndoAction() and createRedoAction(). KUndo2QStack keeps track of the \a current command. This is the command which will be executed by the next call to redo(). The index of this command is returned by index(). The state of the edited object can be rolled forward or back using setIndex(). If the top-most command on the stack has already been redone, index() is equal to count(). KUndo2QStack provides support for undo and redo actions, command compression, command macros, and supports the concept of a \e{clean state}. \section1 Undo and Redo Actions KUndo2QStack provides convenient undo and redo QAction objects, which can be inserted into a menu or a toolbar. When commands are undone or redone, KUndo2QStack updates the text properties of these actions to reflect what change they will trigger. The actions are also disabled when no command is available for undo or redo. These actions are returned by KUndo2QStack::createUndoAction() and KUndo2QStack::createRedoAction(). \section1 Command Compression and Macros Command compression is useful when several commands can be compressed into a single command that can be undone and redone in a single operation. For example, when a user types a character in a text editor, a new command is created. This command inserts the character into the document at the cursor position. However, it is more convenient for the user to be able to undo or redo typing of whole words, sentences, or paragraphs. Command compression allows these single-character commands to be merged into a single command which inserts or deletes sections of text. For more information, see KUndo2Command::mergeWith() and push(). A command macro is a sequence of commands, all of which are undone and redone in one go. Command macros are created by giving a command a list of child commands. Undoing or redoing the parent command will cause the child commands to be undone or redone. Command macros may be created explicitly by specifying a parent in the KUndo2Command constructor, or by using the convenience functions beginMacro() and endMacro(). Although command compression and macros appear to have the same effect to the user, they often have different uses in an application. Commands that perform small changes to a document may be usefully compressed if there is no need to individually record them, and if only larger changes are relevant to the user. However, for commands that need to be recorded individually, or those that cannot be compressed, it is useful to use macros to provide a more convenient user experience while maintaining a record of each command. \section1 Clean State KUndo2QStack supports the concept of a clean state. When the document is saved to disk, the stack can be marked as clean using setClean(). Whenever the stack returns to this state through undoing and redoing commands, it emits the signal cleanChanged(). This signal is also emitted when the stack leaves the clean state. This signal is usually used to enable and disable the save actions in the application, and to update the document's title to reflect that it contains unsaved changes. \sa KUndo2Command, KUndo2View */ #ifndef QT_NO_ACTION KUndo2Action::KUndo2Action(const QString &textTemplate, const QString &defaultText, QObject *parent) : QAction(parent) { m_textTemplate = textTemplate; m_defaultText = defaultText; } void KUndo2Action::setPrefixedText(const QString &text) { if (text.isEmpty()) setText(m_defaultText); else setText(m_textTemplate.arg(text)); } #endif // QT_NO_ACTION /*! \internal Sets the current index to \a idx, emitting appropriate signals. If \a clean is true, makes \a idx the clean index as well. */ void KUndo2QStack::setIndex(int idx, bool clean) { bool was_clean = m_index == m_clean_index; if (m_lastMergedIndex <= idx) { m_lastMergedSetCount = idx - m_lastMergedIndex; } else { m_lastMergedSetCount = 1; m_lastMergedIndex = idx-1; } if(idx == 0){ m_lastMergedSetCount = 0; m_lastMergedIndex = 0; } if (idx != m_index) { m_index = idx; emit indexChanged(m_index); emit canUndoChanged(canUndo()); emit undoTextChanged(undoText()); emit canRedoChanged(canRedo()); emit redoTextChanged(redoText()); } if (clean) m_clean_index = m_index; bool is_clean = m_index == m_clean_index; if (is_clean != was_clean) emit cleanChanged(is_clean); } void KUndo2QStack::purgeRedoState() { bool macro = !m_macro_stack.isEmpty(); if (macro) return; bool redoStateChanged = false; bool cleanStateChanged = false; while (m_index < m_command_list.size()) { delete m_command_list.takeLast(); redoStateChanged = true; } if (m_clean_index > m_index) { m_clean_index = -1; // we've deleted the clean state cleanStateChanged = true; } if (redoStateChanged) { emit canRedoChanged(canRedo()); emit redoTextChanged(redoText()); } if (cleanStateChanged) { emit cleanChanged(isClean()); } } /*! \internal If the number of commands on the stack exceedes the undo limit, deletes commands from the bottom of the stack. Returns true if commands were deleted. */ bool KUndo2QStack::checkUndoLimit() { if (m_undo_limit <= 0 || !m_macro_stack.isEmpty() || m_undo_limit >= m_command_list.count()) return false; int del_count = m_command_list.count() - m_undo_limit; for (int i = 0; i < del_count; ++i) delete m_command_list.takeFirst(); m_index -= del_count; if (m_clean_index != -1) { if (m_clean_index < del_count) m_clean_index = -1; // we've deleted the clean command else m_clean_index -= del_count; } return true; } /*! Constructs an empty undo stack with the parent \a parent. The stack will initially be in the clean state. If \a parent is a KUndo2Group object, the stack is automatically added to the group. \sa push() */ KUndo2QStack::KUndo2QStack(QObject *parent) : QObject(parent), m_index(0), m_clean_index(0), m_group(0), m_undo_limit(0), m_useCumulativeUndoRedo(false), m_lastMergedSetCount(0), m_lastMergedIndex(0) { setTimeT1(5); setTimeT2(1); setStrokesN(2); #ifndef QT_NO_UNDOGROUP if (KUndo2Group *group = qobject_cast(parent)) group->addStack(this); #endif } /*! Destroys the undo stack, deleting any commands that are on it. If the stack is in a KUndo2Group, the stack is automatically removed from the group. \sa KUndo2QStack() */ KUndo2QStack::~KUndo2QStack() { #ifndef QT_NO_UNDOGROUP if (m_group != 0) m_group->removeStack(this); #endif clear(); } /*! Clears the command stack by deleting all commands on it, and returns the stack to the clean state.{ } Commands are not undone or redone; the state of the edited object remains unchanged. This function is usually used when the contents of the document are abandoned. \sa KUndo2QStack() */ void KUndo2QStack::clear() { if (m_command_list.isEmpty()) return; bool was_clean = isClean(); m_macro_stack.clear(); qDeleteAll(m_command_list); m_command_list.clear(); m_index = 0; m_clean_index = 0; emit indexChanged(0); emit canUndoChanged(false); emit undoTextChanged(QString()); emit canRedoChanged(false); emit redoTextChanged(QString()); if (!was_clean) emit cleanChanged(true); } /*! Pushes \a cmd on the stack or merges it with the most recently executed command. In either case, executes \a cmd by calling its redo() function. If \a cmd's id is not -1, and if the id is the same as that of the most recently executed command, KUndo2QStack will attempt to merge the two commands by calling KUndo2Command::mergeWith() on the most recently executed command. If KUndo2Command::mergeWith() returns true, \a cmd is deleted and false is returned. In all other cases \a cmd is simply pushed on the stack and true is returned. If commands were undone before \a cmd was pushed, the current command and all commands above it are deleted. Hence \a cmd always ends up being the top-most on the stack. Once a command is pushed, the stack takes ownership of it. There are no getters to return the command, since modifying it after it has been executed will almost always lead to corruption of the document's state. \sa KUndo2Command::id() KUndo2Command::mergeWith() */ bool KUndo2QStack::push(KUndo2Command *cmd) { cmd->redoMergedCommands(); cmd->setEndTime(); bool macro = !m_macro_stack.isEmpty(); KUndo2Command *cur = 0; if (macro) { KUndo2Command *macro_cmd = m_macro_stack.last(); if (!macro_cmd->d->child_list.isEmpty()) cur = macro_cmd->d->child_list.last(); } else { if (m_index > 0) cur = m_command_list.at(m_index - 1); while (m_index < m_command_list.size()) delete m_command_list.takeLast(); if (m_clean_index > m_index) m_clean_index = -1; // we've deleted the clean state } bool try_merge = cur != 0 && cur->id() != -1 && cur->id() == cmd->id() && (macro || m_index != m_clean_index); /*! *Here we are going to try to merge several commands together using the QVector field in the commands using *3 parameters. N : Number of commands that should remain individual at the top of the stack. T1 : Time lapsed between current command and previously merged command -- signal to *merge throughout the stack. T2 : Time lapsed between two commands signalling both commands belong to the same set *Whenever a KUndo2Command is initialized -- it consists of a start-time and when it is pushed --an end time. *Every time a command is pushed -- it checks whether the command pushed was pushed after T1 seconds of the last merged command *Then the merging begins with each group depending on the time in between each command (T2). * *@TODO : Currently it is not able to merge two merged commands together. */ if (!macro && m_command_list.size() > 1 && cmd->timedId() != -1 && m_useCumulativeUndoRedo) { KUndo2Command* lastcmd = m_command_list.last(); if (qAbs(cmd->time().msecsTo(lastcmd->endTime())) < m_timeT2 * 1000) { m_lastMergedSetCount++; } else { m_lastMergedSetCount = 0; m_lastMergedIndex = m_index-1; } if (lastcmd->timedId() == -1){ m_lastMergedSetCount = 0; m_lastMergedIndex = m_index; } if (m_lastMergedSetCount > m_strokesN) { KUndo2Command* toMerge = m_command_list.at(m_lastMergedIndex); if (toMerge && m_command_list.size() >= m_lastMergedIndex + 1 && m_command_list.at(m_lastMergedIndex + 1)) { if(toMerge->timedMergeWith(m_command_list.at(m_lastMergedIndex + 1))){ m_command_list.removeAt(m_lastMergedIndex + 1); } m_lastMergedSetCount--; m_lastMergedIndex = m_command_list.indexOf(toMerge); } } m_index = m_command_list.size(); if(m_lastMergedIndextime().msecsTo(m_command_list.at(m_lastMergedIndex)->endTime()) < -m_timeT1 * 1000) { //T1 time elapsed QListIterator it(m_command_list); it.toBack(); m_lastMergedSetCount = 1; while (it.hasPrevious()) { KUndo2Command* curr = it.previous(); KUndo2Command* lastCmdInCurrent = curr; if (!lastcmd->mergeCommandsVector().isEmpty()) { if (qAbs(lastcmd->mergeCommandsVector().last()->time().msecsTo(lastCmdInCurrent->endTime())) < int(m_timeT2 * 1000) && lastcmd != lastCmdInCurrent && lastcmd != curr) { if(lastcmd->timedMergeWith(curr)){ if (m_command_list.contains(curr)) { m_command_list.removeOne(curr); } } } else { lastcmd = curr; //end of a merge set } } else { if (qAbs(lastcmd->time().msecsTo(lastCmdInCurrent->endTime())) < int(m_timeT2 * 1000) && lastcmd != lastCmdInCurrent &&lastcmd!=curr) { if(lastcmd->timedMergeWith(curr)){ if (m_command_list.contains(curr)){ m_command_list.removeOne(curr); } } } else { lastcmd = curr; //end of a merge set } } } m_lastMergedIndex = m_command_list.size()-1; } } m_index = m_command_list.size(); } if (try_merge && cur->mergeWith(cmd)) { delete cmd; cmd = 0; if (!macro) { emit indexChanged(m_index); emit canUndoChanged(canUndo()); emit undoTextChanged(undoText()); emit canRedoChanged(canRedo()); emit redoTextChanged(redoText()); } } else { if (macro) { m_macro_stack.last()->d->child_list.append(cmd); } else { m_command_list.append(cmd); if(checkUndoLimit()) { m_lastMergedIndex = m_index - m_strokesN; } setIndex(m_index + 1, false); } } return cmd; } /*! Marks the stack as clean and emits cleanChanged() if the stack was not already clean. Whenever the stack returns to this state through the use of undo/redo commands, it emits the signal cleanChanged(). This signal is also emitted when the stack leaves the clean state. \sa isClean(), cleanIndex() */ void KUndo2QStack::setClean() { if (!m_macro_stack.isEmpty()) { qWarning("KUndo2QStack::setClean(): cannot set clean in the middle of a macro"); return; } setIndex(m_index, true); } /*! If the stack is in the clean state, returns true; otherwise returns false. \sa setClean() cleanIndex() */ bool KUndo2QStack::isClean() const { if (!m_macro_stack.isEmpty()) return false; return m_clean_index == m_index; } /*! Returns the clean index. This is the index at which setClean() was called. A stack may not have a clean index. This happens if a document is saved, some commands are undone, then a new command is pushed. Since push() deletes all the undone commands before pushing the new command, the stack can't return to the clean state again. In this case, this function returns -1. \sa isClean() setClean() */ int KUndo2QStack::cleanIndex() const { return m_clean_index; } /*! Undoes the command below the current command by calling KUndo2Command::undo(). Decrements the current command index. If the stack is empty, or if the bottom command on the stack has already been undone, this function does nothing. \sa redo() index() */ void KUndo2QStack::undo() { if (m_index == 0) return; if (!m_macro_stack.isEmpty()) { qWarning("KUndo2QStack::undo(): cannot undo in the middle of a macro"); return; } int idx = m_index - 1; m_command_list.at(idx)->undoMergedCommands(); setIndex(idx, false); } /*! Redoes the current command by calling KUndo2Command::redo(). Increments the current command index. If the stack is empty, or if the top command on the stack has already been redone, this function does nothing. \sa undo() index() */ void KUndo2QStack::redo() { if (m_index == m_command_list.size()) return; if (!m_macro_stack.isEmpty()) { qWarning("KUndo2QStack::redo(): cannot redo in the middle of a macro"); return; } m_command_list.at(m_index)->redoMergedCommands(); setIndex(m_index + 1, false); } /*! Returns the number of commands on the stack. Macro commands are counted as one command. \sa index() setIndex() command() */ int KUndo2QStack::count() const { return m_command_list.size(); } /*! Returns the index of the current command. This is the command that will be executed on the next call to redo(). It is not always the top-most command on the stack, since a number of commands may have been undone. \sa undo() redo() count() */ int KUndo2QStack::index() const { return m_index; } /*! Repeatedly calls undo() or redo() until the current command index reaches \a idx. This function can be used to roll the state of the document forwards of backwards. indexChanged() is emitted only once. \sa index() count() undo() redo() */ void KUndo2QStack::setIndex(int idx) { if (!m_macro_stack.isEmpty()) { qWarning("KUndo2QStack::setIndex(): cannot set index in the middle of a macro"); return; } if (idx < 0) idx = 0; else if (idx > m_command_list.size()) idx = m_command_list.size(); int i = m_index; while (i < idx) { m_command_list.at(i++)->redoMergedCommands(); notifySetIndexChangedOneCommand(); } while (i > idx) { m_command_list.at(--i)->undoMergedCommands(); notifySetIndexChangedOneCommand(); } setIndex(idx, false); } /** * Called by setIndex after every command execution. It is needed by * Krita to insert barriers between different kind of commands */ void KUndo2QStack::notifySetIndexChangedOneCommand() { } /*! Returns true if there is a command available for undo; otherwise returns false. This function returns false if the stack is empty, or if the bottom command on the stack has already been undone. Synonymous with index() == 0. \sa index() canRedo() */ bool KUndo2QStack::canUndo() const { if (!m_macro_stack.isEmpty()) return false; return m_index > 0; } /*! Returns true if there is a command available for redo; otherwise returns false. This function returns false if the stack is empty or if the top command on the stack has already been redone. Synonymous with index() == count(). \sa index() canUndo() */ bool KUndo2QStack::canRedo() const { if (!m_macro_stack.isEmpty()) return false; return m_index < m_command_list.size(); } /*! Returns the text of the command which will be undone in the next call to undo(). \sa KUndo2Command::text() redoActionText() undoItemText() */ QString KUndo2QStack::undoText() const { if (!m_macro_stack.isEmpty()) return QString(); if (m_index > 0 && m_command_list.at(m_index-1)!=NULL) return m_command_list.at(m_index - 1)->actionText(); return QString(); } /*! Returns the text of the command which will be redone in the next call to redo(). \sa KUndo2Command::text() undoActionText() redoItemText() */ QString KUndo2QStack::redoText() const { if (!m_macro_stack.isEmpty()) return QString(); if (m_index < m_command_list.size()) return m_command_list.at(m_index)->actionText(); return QString(); } #ifndef QT_NO_ACTION /*! Creates an undo QAction object with the given \a parent. Triggering this action will cause a call to undo(). The text of this action is the text of the command which will be undone in the next call to undo(), prefixed by the specified \a prefix. If there is no command available for undo, this action will be disabled. If \a prefix is empty, the default prefix "Undo" is used. \sa createRedoAction(), canUndo(), KUndo2Command::text() */ QAction *KUndo2QStack::createUndoAction(QObject *parent) const { KUndo2Action *result = new KUndo2Action(i18n("Undo %1"), i18nc("Default text for undo action", "Undo"), parent); result->setEnabled(canUndo()); result->setPrefixedText(undoText()); connect(this, SIGNAL(canUndoChanged(bool)), result, SLOT(setEnabled(bool))); connect(this, SIGNAL(undoTextChanged(QString)), result, SLOT(setPrefixedText(QString))); connect(result, SIGNAL(triggered()), this, SLOT(undo())); return result; } /*! Creates an redo QAction object with the given \a parent. Triggering this action will cause a call to redo(). The text of this action is the text of the command which will be redone in the next call to redo(), prefixed by the specified \a prefix. If there is no command available for redo, this action will be disabled. If \a prefix is empty, the default prefix "Redo" is used. \sa createUndoAction(), canRedo(), KUndo2Command::text() */ QAction *KUndo2QStack::createRedoAction(QObject *parent) const { KUndo2Action *result = new KUndo2Action(i18n("Redo %1"), i18nc("Default text for redo action", "Redo"), parent); result->setEnabled(canRedo()); result->setPrefixedText(redoText()); connect(this, SIGNAL(canRedoChanged(bool)), result, SLOT(setEnabled(bool))); connect(this, SIGNAL(redoTextChanged(QString)), result, SLOT(setPrefixedText(QString))); connect(result, SIGNAL(triggered()), this, SLOT(redo())); return result; } #endif // QT_NO_ACTION /*! Begins composition of a macro command with the given \a text description. An empty command described by the specified \a text is pushed on the stack. Any subsequent commands pushed on the stack will be appended to the empty command's children until endMacro() is called. Calls to beginMacro() and endMacro() may be nested, but every call to beginMacro() must have a matching call to endMacro(). While a macro is composed, the stack is disabled. This means that: \li indexChanged() and cleanChanged() are not emitted, \li canUndo() and canRedo() return false, \li calling undo() or redo() has no effect, \li the undo/redo actions are disabled. The stack becomes enabled and appropriate signals are emitted when endMacro() is called for the outermost macro. \snippet doc/src/snippets/code/src_gui_util_qundostack.cpp 4 This code is equivalent to: \snippet doc/src/snippets/code/src_gui_util_qundostack.cpp 5 \sa endMacro() */ void KUndo2QStack::beginMacro(const KUndo2MagicString &text) { KUndo2Command *cmd = new KUndo2Command(); cmd->setText(text); if (m_macro_stack.isEmpty()) { while (m_index < m_command_list.size()) delete m_command_list.takeLast(); if (m_clean_index > m_index) m_clean_index = -1; // we've deleted the clean state m_command_list.append(cmd); } else { m_macro_stack.last()->d->child_list.append(cmd); } m_macro_stack.append(cmd); if (m_macro_stack.count() == 1) { emit canUndoChanged(false); emit undoTextChanged(QString()); emit canRedoChanged(false); emit redoTextChanged(QString()); } } /*! Ends composition of a macro command. If this is the outermost macro in a set nested macros, this function emits indexChanged() once for the entire macro command. \sa beginMacro() */ void KUndo2QStack::endMacro() { if (m_macro_stack.isEmpty()) { qWarning("KUndo2QStack::endMacro(): no matching beginMacro()"); return; } m_macro_stack.removeLast(); if (m_macro_stack.isEmpty()) { checkUndoLimit(); setIndex(m_index + 1, false); } } /*! Returns a const pointer to the command at \a index. This function returns a const pointer, because modifying a command, once it has been pushed onto the stack and executed, almost always causes corruption of the state of the document, if the command is later undone or redone. \sa KUndo2Command::child() */ const KUndo2Command *KUndo2QStack::command(int index) const { if (index < 0 || index >= m_command_list.count()) return 0; return m_command_list.at(index); } /*! Returns the text of the command at index \a idx. \sa beginMacro() */ QString KUndo2QStack::text(int idx) const { if (idx < 0 || idx >= m_command_list.size()) return QString(); return m_command_list.at(idx)->text().toString(); } /*! \property KUndo2QStack::undoLimit \brief the maximum number of commands on this stack. When the number of commands on a stack exceedes the stack's undoLimit, commands are deleted from the bottom of the stack. Macro commands (commands with child commands) are treated as one command. The default value is 0, which means that there is no limit. This property may only be set when the undo stack is empty, since setting it on a non-empty stack might delete the command at the current index. Calling setUndoLimit() on a non-empty stack prints a warning and does nothing. */ void KUndo2QStack::setUndoLimit(int limit) { if (!m_command_list.isEmpty()) { qWarning("KUndo2QStack::setUndoLimit(): an undo limit can only be set when the stack is empty"); return; } if (limit == m_undo_limit) return; m_undo_limit = limit; checkUndoLimit(); } int KUndo2QStack::undoLimit() const { return m_undo_limit; } /*! \property KUndo2QStack::active \brief the active status of this stack. An application often has multiple undo stacks, one for each opened document. The active stack is the one associated with the currently active document. If the stack belongs to a KUndo2Group, calls to KUndo2Group::undo() or KUndo2Group::redo() will be forwarded to this stack when it is active. If the KUndo2Group is watched by a KUndo2View, the view will display the contents of this stack when it is active. If the stack does not belong to a KUndo2Group, making it active has no effect. It is the programmer's responsibility to specify which stack is active by calling setActive(), usually when the associated document window receives focus. \sa KUndo2Group */ void KUndo2QStack::setActive(bool active) { #ifdef QT_NO_UNDOGROUP Q_UNUSED(active); #else if (m_group != 0) { if (active) m_group->setActiveStack(this); else if (m_group->activeStack() == this) m_group->setActiveStack(0); } #endif } bool KUndo2QStack::isActive() const { #ifdef QT_NO_UNDOGROUP return true; #else return m_group == 0 || m_group->activeStack() == this; #endif } void KUndo2QStack::setUseCumulativeUndoRedo(bool value) { m_useCumulativeUndoRedo = value; } bool KUndo2QStack::useCumulativeUndoRedo() { return m_useCumulativeUndoRedo; } void KUndo2QStack::setTimeT1(double value) { m_timeT1 = value; } double KUndo2QStack::timeT1() { return m_timeT1; } void KUndo2QStack::setTimeT2(double value) { m_timeT2 = value; } double KUndo2QStack::timeT2() { return m_timeT2; } int KUndo2QStack::strokesN() { return m_strokesN; } void KUndo2QStack::setStrokesN(int value) { m_strokesN = value; } QAction* KUndo2Stack::createRedoAction(KActionCollection* actionCollection, const QString& actionName) { QAction* action = KUndo2QStack::createRedoAction(actionCollection); if (actionName.isEmpty()) { action->setObjectName(KStandardAction::name(KStandardAction::Redo)); } else { action->setObjectName(actionName); } action->setIcon(KexiIcon("edit-redo")); action->setIconText(i18n("Redo")); action->setShortcuts(KStandardShortcut::redo()); actionCollection->addAction(action->objectName(), action); return action; } QAction* KUndo2Stack::createUndoAction(KActionCollection* actionCollection, const QString& actionName) { QAction* action = KUndo2QStack::createUndoAction(actionCollection); if (actionName.isEmpty()) { action->setObjectName(KStandardAction::name(KStandardAction::Undo)); } else { action->setObjectName(actionName); } action->setIcon(koIcon("edit-undo")); action->setIconText(i18n("Undo")); action->setShortcuts(KStandardShortcut::undo()); actionCollection->addAction(action->objectName(), action); return action; } /*! \fn void KUndo2QStack::indexChanged(int idx) This signal is emitted whenever a command modifies the state of the document. This happens when a command is undone or redone. When a macro command is undone or redone, or setIndex() is called, this signal is emitted only once. \a idx specifies the index of the current command, ie. the command which will be executed on the next call to redo(). \sa index() setIndex() */ /*! \fn void KUndo2QStack::cleanChanged(bool clean) This signal is emitted whenever the stack enters or leaves the clean state. If \a clean is true, the stack is in a clean state; otherwise this signal indicates that it has left the clean state. \sa isClean() setClean() */ /*! \fn void KUndo2QStack::undoTextChanged(const QString &undoText) This signal is emitted whenever the value of undoText() changes. It is used to update the text property of the undo action returned by createUndoAction(). \a undoText specifies the new text. */ /*! \fn void KUndo2QStack::canUndoChanged(bool canUndo) This signal is emitted whenever the value of canUndo() changes. It is used to enable or disable the undo action returned by createUndoAction(). \a canUndo specifies the new value. */ /*! \fn void KUndo2QStack::redoTextChanged(const QString &redoText) This signal is emitted whenever the value of redoText() changes. It is used to update the text property of the redo action returned by createRedoAction(). \a redoText specifies the new text. */ /*! \fn void KUndo2QStack::canRedoChanged(bool canRedo) This signal is emitted whenever the value of canRedo() changes. It is used to enable or disable the redo action returned by createRedoAction(). \a canRedo specifies the new value. */ KUndo2Stack::KUndo2Stack(QObject *parent): KUndo2QStack(parent) { } #endif // QT_NO_UNDOSTACK diff --git a/src/widget/undo/kundo2stack.h b/src/widget/undo/kundo2stack.h index ebe1908d5..9f20755c2 100644 --- a/src/widget/undo/kundo2stack.h +++ b/src/widget/undo/kundo2stack.h @@ -1,258 +1,258 @@ /* * Copyright (c) 2014 Dmitry Kazakov * Copyright (c) 2014 Mohit Goyal * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /**************************************************************************** ** ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of the QtGui module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** No Commercial Usage ** This file contains pre-release code and may not be distributed. ** You may use this file in accordance with the terms and conditions ** contained in the Technology Preview License Agreement accompanying ** this package. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements -** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** will be met: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** If you have questions regarding the use of this file, please contact ** Nokia at qt-info@nokia.com. ** ** ** ** ** ** ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #ifndef KUNDO2STACK_H #define KUNDO2STACK_H #include #include #include #include #include #include #include "kexiundo_export.h" class QAction; class KUndo2CommandPrivate; class KUndo2Group; class KActionCollection; #ifndef QT_NO_UNDOCOMMAND #include "kundo2magicstring.h" #include "kundo2commandextradata.h" class KEXIUNDO_EXPORT KUndo2Command { KUndo2CommandPrivate *d; public: explicit KUndo2Command(KUndo2Command *parent = 0); explicit KUndo2Command(const KUndo2MagicString &text, KUndo2Command *parent = 0); virtual ~KUndo2Command(); virtual void undo(); virtual void redo(); QString actionText() const; KUndo2MagicString text() const; void setText(const KUndo2MagicString &text); virtual int id() const; virtual int timedId(); virtual void setTimedID(int timedID); virtual bool mergeWith(const KUndo2Command *other); virtual bool timedMergeWith(KUndo2Command *other); int childCount() const; const KUndo2Command *child(int index) const; bool hasParent(); virtual void setTime(); virtual QTime time(); virtual void setEndTime(); virtual QTime endTime(); virtual QVector mergeCommandsVector(); virtual bool isMerged(); virtual void undoMergedCommands(); virtual void redoMergedCommands(); /** * \return user-defined object associated with the command * * \see setExtraData() */ KUndo2CommandExtraData* extraData() const; /** * The user can assign an arbitrary object associated with the * command. The \p data object is owned by the command. If you assign * the object twice, the first one will be destroyed. */ void setExtraData(KUndo2CommandExtraData *data); private: Q_DISABLE_COPY(KUndo2Command) friend class KUndo2QStack; bool m_hasParent; int m_timedID; QTime m_timeOfCreation; QTime m_endOfCommand; QVector m_mergeCommandsVector; }; #endif // QT_NO_UNDOCOMMAND #ifndef QT_NO_UNDOSTACK class KEXIUNDO_EXPORT KUndo2QStack : public QObject { Q_OBJECT // Q_DECLARE_PRIVATE(KUndo2QStack) Q_PROPERTY(bool active READ isActive WRITE setActive) Q_PROPERTY(int undoLimit READ undoLimit WRITE setUndoLimit) public: explicit KUndo2QStack(QObject *parent = 0); virtual ~KUndo2QStack(); void clear(); bool push(KUndo2Command *cmd); bool canUndo() const; bool canRedo() const; QString undoText() const; QString redoText() const; int count() const; int index() const; QString actionText(int idx) const; QString text(int idx) const; #ifndef QT_NO_ACTION QAction *createUndoAction(QObject *parent) const; QAction *createRedoAction(QObject *parent) const; #endif // QT_NO_ACTION bool isActive() const; bool isClean() const; int cleanIndex() const; void beginMacro(const KUndo2MagicString &text); void endMacro(); void setUndoLimit(int limit); int undoLimit() const; const KUndo2Command *command(int index) const; void setUseCumulativeUndoRedo(bool value); bool useCumulativeUndoRedo(); void setTimeT1(double value); double timeT1(); void setTimeT2(double value); double timeT2(); int strokesN(); void setStrokesN(int value); public Q_SLOTS: void setClean(); virtual void setIndex(int idx); virtual void undo(); virtual void redo(); void setActive(bool active = true); void purgeRedoState(); Q_SIGNALS: void indexChanged(int idx); void cleanChanged(bool clean); void canUndoChanged(bool canUndo); void canRedoChanged(bool canRedo); void undoTextChanged(const QString &undoActionText); void redoTextChanged(const QString &redoActionText); protected: virtual void notifySetIndexChangedOneCommand(); private: // from QUndoStackPrivate QList m_command_list; QList m_macro_stack; int m_index; int m_clean_index; KUndo2Group *m_group; int m_undo_limit; bool m_useCumulativeUndoRedo; double m_timeT1; double m_timeT2; int m_strokesN; int m_lastMergedSetCount; int m_lastMergedIndex; // also from QUndoStackPrivate void setIndex(int idx, bool clean); bool checkUndoLimit(); Q_DISABLE_COPY(KUndo2QStack) friend class KUndo2Group; }; class KEXIUNDO_EXPORT KUndo2Stack : public KUndo2QStack { Q_OBJECT public: explicit KUndo2Stack(QObject *parent = 0); // functions from KUndoStack QAction* createRedoAction(KActionCollection* actionCollection, const QString& actionName = QString()); QAction* createUndoAction(KActionCollection* actionCollection, const QString& actionName = QString()); }; #endif // QT_NO_UNDOSTACK #endif // KUNDO2STACK_H diff --git a/src/widget/undo/kundo2stack_p.h b/src/widget/undo/kundo2stack_p.h index bc122fd61..5a19fe8f0 100644 --- a/src/widget/undo/kundo2stack_p.h +++ b/src/widget/undo/kundo2stack_p.h @@ -1,93 +1,93 @@ /**************************************************************************** ** ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of the QtGui module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** No Commercial Usage ** This file contains pre-release code and may not be distributed. ** You may use this file in accordance with the terms and conditions ** contained in the Technology Preview License Agreement accompanying ** this package. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements -** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** will be met: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** If you have questions regarding the use of this file, please contact ** Nokia at qt-info@nokia.com. ** ** ** ** ** ** ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #ifndef KUNDO2STACK_P_H #define KUNDO2STACK_P_H #include #include #include #include "kundo2stack.h" class KUndo2Command; // // W A R N I N G // ------------- // // This file is not part of the Qt API. It exists for the convenience // of qapplication_*.cpp, qwidget*.cpp and qfiledialog.cpp. This header // file may change from version to version without notice, or even be removed. // // We mean it. // class KUndo2CommandPrivate { public: KUndo2CommandPrivate() : id(-1) {} QList child_list; QString actionText; KUndo2MagicString text; int id; QScopedPointer extraData; }; #ifndef QT_NO_UNDOSTACK #ifndef QT_NO_ACTION class KUndo2Action : public QAction { Q_OBJECT public: KUndo2Action(const QString &textTemplate, const QString &defaultText, QObject *parent = 0); public Q_SLOTS: void setPrefixedText(const QString &text); private: QString m_textTemplate; QString m_defaultText; }; #endif // QT_NO_ACTION #endif // QT_NO_UNDOSTACK #endif // KUNDO2STACK_P_H diff --git a/src/widget/undo/kundo2view.cpp b/src/widget/undo/kundo2view.cpp index 9c37aa543..54571e113 100644 --- a/src/widget/undo/kundo2view.cpp +++ b/src/widget/undo/kundo2view.cpp @@ -1,276 +1,276 @@ /* This file is part of the KDE project * Copyright (C) 2010 Matus Talcik * * 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. */ /**************************************************************************** ** ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of the QtGui module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** No Commercial Usage ** This file contains pre-release code and may not be distributed. ** You may use this file in accordance with the terms and conditions ** contained in the Technology Preview License Agreement accompanying ** this package. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements -** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** will be met: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** If you have questions regarding the use of this file, please contact ** Nokia at qt-info@nokia.com. ** ** ** ** ** ** ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "kundo2stack.h" #include "kundo2view.h" #include "kundo2model.h" #include "kundo2group.h" #ifndef QT_NO_UNDOVIEW #include #include #include /*! \class KUndo2View \brief The KUndo2View class displays the contents of a KUndo2QStack. \ingroup advanced KUndo2View is a QListView which displays the list of commands pushed on an undo stack. The most recently executed command is always selected. Selecting a different command results in a call to KUndo2QStack::setIndex(), rolling the state of the document backwards or forward to the new command. The stack can be set explicitly with setStack(). Alternatively, a QUndoGroup object can be set with setGroup(). The view will then update itself automatically whenever the active stack of the group changes. \image KUndo2View.png */ class KUndo2ViewPrivate { public: KUndo2ViewPrivate(KUndo2View *view) : #ifndef QT_NO_UNDOGROUP group(0), #endif q(view) { model = new KUndo2Model(q); q->setModel(model); q->setSelectionModel(model->selectionModel()); } #ifndef QT_NO_UNDOGROUP QPointer group; #endif KUndo2Model *model; KUndo2View* const q; }; /*! Constructs a new view with parent \a parent. */ KUndo2View::KUndo2View(QWidget *parent) : QListView(parent), d(new KUndo2ViewPrivate(this)) { } /*! Constructs a new view with parent \a parent and sets the observed stack to \a stack. */ KUndo2View::KUndo2View(KUndo2QStack *stack, QWidget *parent) : QListView(parent), d(new KUndo2ViewPrivate(this)) { setStack(stack); } #ifndef QT_NO_UNDOGROUP /*! Constructs a new view with parent \a parent and sets the observed group to \a group. The view will update itself autmiatically whenever the active stack of the group changes. */ KUndo2View::KUndo2View(KUndo2Group *group, QWidget *parent) : QListView(parent), d(new KUndo2ViewPrivate(this)) { setGroup(group); } #endif // QT_NO_UNDOGROUP /*! Destroys this view. */ KUndo2View::~KUndo2View() { delete d; } /*! Returns the stack currently displayed by this view. If the view is looking at a QUndoGroup, this the group's active stack. \sa setStack() setGroup() */ KUndo2QStack *KUndo2View::stack() const { return d->model->stack(); } /*! Sets the stack displayed by this view to \a stack. If \a stack is 0, the view will be empty. If the view was previously looking at a QUndoGroup, the group is set to 0. \sa stack() setGroup() */ void KUndo2View::setStack(KUndo2QStack *stack) { #ifndef QT_NO_UNDOGROUP setGroup(0); #endif d->model->setStack(stack); } #ifndef QT_NO_UNDOGROUP /*! Sets the group displayed by this view to \a group. If \a group is 0, the view will be empty. The view will update itself autmiatically whenever the active stack of the group changes. \sa group() setStack() */ void KUndo2View::setGroup(KUndo2Group *group) { if (d->group == group) return; if (d->group != 0) { disconnect(d->group, SIGNAL(activeStackChanged(KUndo2QStack*)), d->model, SLOT(setStack(KUndo2QStack*))); } d->group = group; if (d->group != 0) { connect(d->group, SIGNAL(activeStackChanged(KUndo2QStack*)), d->model, SLOT(setStack(KUndo2QStack*))); d->model->setStack((KUndo2QStack *)d->group->activeStack()); } else { d->model->setStack(0); } } /*! Returns the group displayed by this view. If the view is not looking at group, this function returns 0. \sa setGroup() setStack() */ KUndo2Group *KUndo2View::group() const { return d->group; } #endif // QT_NO_UNDOGROUP /*! \property KUndo2View::emptyLabel \brief the label used for the empty state. The empty label is the topmost element in the list of commands, which represents the state of the document before any commands were pushed on the stack. The default is the string "". */ void KUndo2View::setEmptyLabel(const QString &label) { d->model->setEmptyLabel(label); } QString KUndo2View::emptyLabel() const { return d->model->emptyLabel(); } /*! \property KUndo2View::cleanIcon \brief the icon used to represent the clean state. A stack may have a clean state set with KUndo2QStack::setClean(). This is usually the state of the document at the point it was saved. KUndo2View can display an icon in the list of commands to show the clean state. If this property is a null icon, no icon is shown. The default value is the null icon. */ void KUndo2View::setCleanIcon(const QIcon &icon) { d->model->setCleanIcon(icon); } QIcon KUndo2View::cleanIcon() const { return d->model->cleanIcon(); } #endif // QT_NO_UNDOVIEW diff --git a/src/widget/undo/kundo2view.h b/src/widget/undo/kundo2view.h index 855d6a27c..24891d011 100644 --- a/src/widget/undo/kundo2view.h +++ b/src/widget/undo/kundo2view.h @@ -1,111 +1,111 @@ /* This file is part of the KDE project * Copyright (C) 2010 Matus Talcik * * 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. */ /**************************************************************************** ** ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of the QtGui module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** No Commercial Usage ** This file contains pre-release code and may not be distributed. ** You may use this file in accordance with the terms and conditions ** contained in the Technology Preview License Agreement accompanying ** this package. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements -** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** will be met: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** If you have questions regarding the use of this file, please contact ** Nokia at qt-info@nokia.com. ** ** ** ** ** ** ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #ifndef KUNDO2VIEW_H #define KUNDO2VIEW_H #include #include #include "kexiundo_export.h" #ifndef QT_NO_UNDOVIEW class KUndo2ViewPrivate; class KUndo2QStack; class KUndo2Group; class QIcon; class KEXIUNDO_EXPORT KUndo2View : public QListView { Q_OBJECT Q_PROPERTY(QString emptyLabel READ emptyLabel WRITE setEmptyLabel) Q_PROPERTY(QIcon cleanIcon READ cleanIcon WRITE setCleanIcon) public: explicit KUndo2View(QWidget *parent = 0); explicit KUndo2View(KUndo2QStack *stack, QWidget *parent = 0); #ifndef QT_NO_UNDOGROUP explicit KUndo2View(KUndo2Group *group, QWidget *parent = 0); #endif ~KUndo2View(); KUndo2QStack *stack() const; #ifndef QT_NO_UNDOGROUP KUndo2Group *group() const; #endif void setEmptyLabel(const QString &label); QString emptyLabel() const; void setCleanIcon(const QIcon &icon); QIcon cleanIcon() const; public Q_SLOTS: void setStack(KUndo2QStack *stack); #ifndef QT_NO_UNDOGROUP void setGroup(KUndo2Group *group); #endif private: KUndo2ViewPrivate* const d; Q_DISABLE_COPY(KUndo2View) }; #endif // QT_NO_UNDOVIEW #endif // KUNDO2VIEW_H