diff --git a/CMakeLists.txt b/CMakeLists.txt index 0cafc45c1..45075f790 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,284 +1,284 @@ 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 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_FAST_CONCATENATION -DQT_USE_FAST_OPERATOR_PLUS -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 876a190af..5d905d36e 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/CMakeLists.txt b/src/CMakeLists.txt index a95c2b8dd..e073e94b5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,124 +1,124 @@ set(CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS true) check_function_exists("uname" HAVE_UNAME) simple_option(KEXI_SHOW_UNFINISHED "Show unfinished features in Kexi. Thus is useful for testing but may confuse end-user." OFF) simple_option(KEXI_SHOW_UNIMPLEMENTED "Forces to show menu entries and dialogs just to give impression about development plans for Kexi. Only recommended for test/development versions." OFF) # Extra GUI features if(NOT KEXI_MOBILE) simple_option(KEXI_AUTORISE_TABBED_TOOLBAR "Experimental: Autorise the main tabbed toolbar in Kexi" OFF) if(WIN32 OR APPLE) set(_KEXI_USE_KFILEWIDGET_DEFAULT OFF) else() set(_KEXI_USE_KFILEWIDGET_DEFAULT ON) endif() simple_option(KEXI_USE_KFILEWIDGET "Use KFileWidget-based inline file browser in Kexi. If OFF, a simple \ replacement file widget with native file dialogs is used. ON by default on UNIX, OFF by default on Windows \ and macOS. \ Note: Non-plasma Linux desktops still default to the simple replacement at runtime." ${_KEXI_USE_KFILEWIDGET_DEFAULT} ) endif(NOT KEXI_MOBILE) # Experimental: simple_option(KEXI_SCRIPTS_SUPPORT "Experimental: Enable scripting in Kexi" OFF) if(KEXI_SCRIPTS_SUPPORT) set(REQUIRED_QTQML_VERSION 5.8.0) # >= 5.8 because of QJSEngine::newQMetaObject() find_package(Qt5Qml ${REQUIRED_QTQML_VERSION} REQUIRED) set_package_properties(Qt5Qml PROPERTIES DESCRIPTION "A framework for developing applications and libraries with the QML and JavaScript language." - URL "http://qt.io" + URL "https://qt.io" TYPE REQUIRED PURPOSE "Required by Kexi scripting (JavaScript)" ) endif() # Broken: simple_option(KEXI_FORM_CURSOR_PROPERTY_SUPPORT "Broken: Enable \"cursor\" property in the form designer" OFF) simple_option(KEXI_SHOW_CONTEXT_HELP "Broken: Enable context help in Kexi main window" OFF) simple_option(KEXI_QUICK_PRINTING_SUPPORT "Broken: Enable print/print preview/print setup for tables/queries in the project navigator" OFF) simple_option(KEXI_AUTOFIELD_FORM_WIDGET_SUPPORT "Broken: Enable \"auto field\" form widget in the form designer" OFF) # OFF because we need to replace it with QTreeWidget which uses very different API compared to Q3ListView. Re-add QTreeWidget? simple_option(KEXI_LIST_FORM_WIDGET_SUPPORT "Broken: Enable \"list\" form widget in the form designer" OFF) simple_option(KEXI_PIXMAP_COLLECTIONS_SUPPORT "Broken: Enable support for pixmap collections" OFF) # Not available: simple_option(KEXI_MACROS_SUPPORT "Experimental: Enable macros in Kexi" OFF) if(KEXI_MACROS_SUPPORT) # temp. message(FATAL_ERROR "Macros are not yet available.") endif() simple_option(KEXI_TABLE_PRINT_SUPPORT "Experimental: Enable printing of tabular view in Kexi" OFF) # broken since Kexi 2 if(KEXI_TABLE_PRINT_SUPPORT) # temp. message(FATAL_ERROR "Table printing is not yet available.") endif() simple_option(KEXI_PROJECT_TEMPLATES "Experimental: Enable support for project templates in Kexi" OFF) # broken since Kexi 2 if(KEXI_PROJECT_TEMPLATES) # temp. message(FATAL_ERROR "Project templates are not yet available.") endif() #See commit 1e433a54cd9, left here for reference #option(KEXI_SQLITE_MIGRATION "If defined, SQLite3 migration to some newer format is possible. Users can see a suitable question on app's startup." OFF) add_definitions(-DTRANSLATION_DOMAIN=\"kexi\") #no default: add_definitions(-DKDE_DEFAULT_DEBUG_AREA=44010) macro_bool_to_01(SHOULD_BUILD_KEXI_DESKTOP_APP KEXI_DESKTOP) macro_bool_to_01(SHOULD_BUILD_KEXI_MOBILE_APP KEXI_MOBILE) configure_file(config-kexi.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kexi.h ) configure_file(KexiVersion.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/KexiVersion.h) include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}/core ) add_subdirectory( kexiutils ) add_subdirectory( core ) if(SHOULD_BUILD_KEXI_DESKTOP_APP) add_subdirectory( widget ) add_subdirectory( main ) add_subdirectory( formeditor ) add_subdirectory( migration ) endif() add_subdirectory( data ) add_subdirectory( plugins ) if (BUILD_TESTING) #TODO KEXI3 add_subdirectory( tests ) endif() ########### next target ############### if(SHOULD_BUILD_KEXI_DESKTOP_APP) set(kexi_SRCS main.cpp Messages.sh # non-source: ${CMAKE_SOURCE_DIR}/kundo2_aware_xgettext.sh Mainpage.dox Messages.sh ) kexi_add_app_metadata_files(kexi_SRCS) kexi_add_executable(kexi ${kexi_SRCS}) kexi_add_app_icons() target_link_libraries(kexi PRIVATE keximain ) install(TARGETS kexi ${INSTALL_TARGETS_DEFAULT_ARGS}) add_subdirectory( pics ) endif() if(SHOULD_BUILD_KEXI_MOBILE_APP) # add_subdirectory( mobile ) endif() if(BUILD_EXAMPLES) add_subdirectory(examples) endif() 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 7e792f93c..ac790172a 100644 --- a/src/kexiutils/KexiFadeWidgetEffect.cpp +++ b/src/kexiutils/KexiFadeWidgetEffect.cpp @@ -1,151 +1,151 @@ /* 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 ? destWidget->parentWidget() : 0), 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; } 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->newPixmap = d->destWidget->grab(); d->timeLine.setDuration(duration); 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 d0971c073..90525c426 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 \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. \section1 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. \section1 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. \section1 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. \bold{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 09b8b614f..5299da435 100644 --- a/src/main/KexiMainWindow.cpp +++ b/src/main/KexiMainWindow.cpp @@ -1,4433 +1,4433 @@ /* 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 #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 #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(); } //------------------------------------------------- KexiMainWindowTabWidget::KexiMainWindowTabWidget(QWidget *parent, KexiMainWidget* mainWidget) : QTabWidget(parent) , m_mainWidget(mainWidget) , m_tabIndex(-1) { m_closeAction = new QAction(koIcon("tab-close"), xi18n("&Close Tab"), this); m_closeAction->setToolTip(xi18n("Close the current tab")); m_closeAction->setWhatsThis(xi18n("Closes the current tab.")); m_closeAllTabsAction = new QAction(xi18n("Cl&ose All Tabs"), this); m_closeAllTabsAction->setToolTip(xi18n("Close all tabs")); m_closeAllTabsAction->setWhatsThis(xi18n("Closes all tabs.")); connect(m_closeAction, SIGNAL(triggered()), this, SLOT(closeTab())); connect(m_closeAllTabsAction, SIGNAL(triggered()), this, SLOT(closeAllTabs())); //! @todo insert window list in the corner widget as in firefox #if 0 // close-tab button: QToolButton* rightWidget = new QToolButton(this); rightWidget->setDefaultAction(m_closeAction); rightWidget->setText(QString()); rightWidget->setAutoRaise(true); rightWidget->adjustSize(); setCornerWidget(rightWidget, Qt::TopRightCorner); #endif setMovable(true); setDocumentMode(true); tabBar()->setExpanding(false); } KexiMainWindowTabWidget::~KexiMainWindowTabWidget() { } void KexiMainWindowTabWidget::paintEvent(QPaintEvent * event) { if (count() > 0) QTabWidget::paintEvent(event); else QWidget::paintEvent(event); } void KexiMainWindowTabWidget::mousePressEvent(QMouseEvent *event) { //! @todo KEXI3 test KexiMainWindowTabWidget's contextMenu event port from KTabWidget if (event->button() == Qt::RightButton) { int tab = tabBar()->tabAt(event->pos()); const QPoint realPos(tabBar()->mapToGlobal(event->pos())); if (QRect(tabBar()->mapToGlobal(QPoint(0,0)), tabBar()->mapToGlobal(QPoint(tabBar()->width()-1, tabBar()->height()-1))).contains(realPos)) { showContextMenuForTab(tab, tabBar()->mapToGlobal(event->pos())); return; } } QTabWidget::mousePressEvent(event); } void KexiMainWindowTabWidget::closeTab() { KexiMainWindow *main = dynamic_cast(KexiMainWindowIface::global()); if (main) { main->closeWindowForTab(m_tabIndex); } } tristate KexiMainWindowTabWidget::closeAllTabs() { tristate alternateResult = true; QList windowList; KexiMainWindow *main = dynamic_cast(KexiMainWindowIface::global()); if (!main) { return alternateResult; } for (int i = 0; i < count(); i++) { KexiWindow *window = main->windowForTab(i); if (window) { windowList.append(window); } } foreach (KexiWindow *window, windowList) { tristate result = main->closeWindow(window); if (result != true && result != false) { return result; } if (result == false) { alternateResult = false; } } return alternateResult; } void KexiMainWindowTabWidget::showContextMenuForTab(int index, const QPoint& point) { QMenu menu; if (index >= 0) { menu.addAction(m_closeAction); } if (count() > 0) { menu.addAction(m_closeAllTabsAction); } //! @todo add "&Detach Tab" if (menu.actions().isEmpty()) { return; } setTabIndexFromContextMenu(index); menu.exec(point); } void KexiMainWindowTabWidget::setTabIndexFromContextMenu(int clickedIndex) { if (currentIndex() == -1) { m_tabIndex = -1; return; } m_tabIndex = clickedIndex; } //------------------------------------------------- static bool setupIconTheme(KLocalizedString *errorMessage, KLocalizedString *detailsErrorMessage) { // Register kexi resource first to have priority over the standard breeze theme. // For example "table" icon exists in both resources. if (!registerResource("icons/kexi_breeze.rcc", QStandardPaths::AppDataLocation, QString(), QString(), errorMessage, detailsErrorMessage) || !registerGlobalBreezeIconsResource(errorMessage, detailsErrorMessage)) { return false; } setupBreezeIconTheme(); // tell KIconLoader an co. about the theme KConfigGroup cg(KSharedConfig::openConfig(), "Icons"); cg.writeEntry("Theme", "breeze"); cg.sync(); return true; } //! @todo 3.1 replace with KexiStyle bool setupApplication() { #if defined Q_OS_WIN || defined Q_OS_MACOS // Only this style matches current Kexi theme and can be supported/tested const char *name = "breeze"; QScopedPointer style(QStyleFactory::create(name)); if (!style || style->objectName() != name) { qWarning() << qPrintable(QString("Could not find application style %1. " "Kexi will not start. Please check if Kexi is properly installed. ") .arg(name)); return false; } qApp->setStyle(style.take()); #endif return true; } //static int KexiMainWindow::create(const QStringList &arguments, const QString &componentName, const QList &extraOptions) { qApp->setQuitOnLastWindowClosed(false); KLocalizedString::setApplicationDomain("kexi"); //! @todo KEXI3 app->setAttribute(Qt::AA_UseHighDpiPixmaps, true); KexiAboutData aboutData; if (!componentName.isEmpty()) { aboutData.setComponentName(componentName); } KAboutData::setApplicationData(aboutData); if (!setupApplication()) { return 1; } #ifdef HAVE_KCRASH KCrash::initialize(); #endif KLocalizedString errorMessage; KLocalizedString detailsErrorMessage; if (!setupIconTheme(&errorMessage, &detailsErrorMessage)) { if (detailsErrorMessage.isEmpty()) { KMessageBox::error(nullptr, errorMessage.toString()); } else { KMessageBox::detailedError(nullptr, errorMessage.toString(), detailsErrorMessage.toString()); } qWarning() << qPrintable(errorMessage.toString(Kuit::PlainText)); return 1; } QApplication::setWindowIcon(koIcon("kexi")); const tristate res = KexiStartupHandler::global()->init(arguments, extraOptions); if (!res || ~res) { return (~res) ? 0 : 1; } //qDebug() << "startupActions OK"; /* Exit requested, e.g. after database removing. */ if (KexiStartupHandler::global()->action() == KexiStartupData::Exit) { return 0; } KexiMainWindow *win = new KexiMainWindow(); #ifdef KEXI_DEBUG_GUI QWidget* debugWindow = 0; KConfigGroup generalGroup = KSharedConfig::openConfig()->group("General"); if (generalGroup.readEntry("ShowInternalDebugger", false)) { debugWindow = KexiUtils::createDebugWindow(win); debugWindow->show(); } #endif if (true != win->startup()) { delete win; return 1; } win->restoreSettings(); win->show(); #ifdef KEXI_DEBUG_GUI win->raise(); static_cast(win)->activateWindow(); #endif /*foreach (QWidget *widget, QApplication::topLevelWidgets()) { qDebug() << widget; }*/ return 0; } //------------------------------------------------- KexiMainMenuActionShortcut::KexiMainMenuActionShortcut(const QKeySequence& key, QAction *action, QWidget *parent) : QShortcut(key, parent) , m_action(action) { connect(this, SIGNAL(activated()), this, SLOT(slotActivated())); } KexiMainMenuActionShortcut::~KexiMainMenuActionShortcut() { } void KexiMainMenuActionShortcut::slotActivated() { if (!m_action->isEnabled()) { return; } m_action->trigger(); } //------------------------------------------------- KexiMainWindow::KexiMainWindow(QWidget *parent) : KexiMainWindowSuper(parent) , KexiMainWindowIface() , KexiGUIMessageHandler(this) , d(new KexiMainWindow::Private(this)) { setObjectName("KexiMainWindow"); setAttribute(Qt::WA_DeleteOnClose); kexiTester() << KexiTestObject(this); if (d->userMode) qDebug() << "starting up in the User Mode"; setAsDefaultHost(); //this is default host now. //get informed connect(&Kexi::partManager(), SIGNAL(partLoaded(KexiPart::Part*)), this, SLOT(slotPartLoaded(KexiPart::Part*))); connect(&Kexi::partManager(), SIGNAL(newObjectRequested(KexiPart::Info*)), this, SLOT(newObject(KexiPart::Info*))); setAcceptDrops(true); setupActions(); setupMainWidget(); updateAppCaption(); if (!d->userMode) { setupContextHelp(); setupPropertyEditor(); } invalidateActions(); d->timer.singleShot(0, this, SLOT(slotLastActions())); if (KexiStartupHandler::global()->forcedFullScreen()) { toggleFullScreen(true); } // --- global config //! @todo move to specialized KexiConfig class KConfigGroup tablesGroup(d->config->group("Tables")); const int defaultMaxLengthForTextFields = tablesGroup.readEntry("DefaultMaxLengthForTextFields", int(-1)); if (defaultMaxLengthForTextFields >= 0) { KDbField::setDefaultMaxLength(defaultMaxLengthForTextFields); } // --- /global config } KexiMainWindow::~KexiMainWindow() { d->forceWindowClosing = true; closeProject(); delete d; Kexi::deleteGlobalObjects(); } KexiProject *KexiMainWindow::project() { return d->prj; } QList KexiMainWindow::allActions() const { return actionCollection()->actions(); } KActionCollection *KexiMainWindow::actionCollection() const { return d->actionCollection; } KexiWindow* KexiMainWindow::currentWindow() const { return windowForTab(d->mainWidget->tabWidget()->currentIndex()); } KexiWindow* KexiMainWindow::windowForTab(int tabIndex) const { if (!d->mainWidget->tabWidget()) return 0; KexiWindowContainer *windowContainer = dynamic_cast(d->mainWidget->tabWidget()->widget(tabIndex)); if (!windowContainer) return 0; return windowContainer->window; } void KexiMainWindow::setupMainMenuActionShortcut(QAction * action) { if (!action->shortcut().isEmpty()) { foreach(const QKeySequence &shortcut, action->shortcuts()) { (void)new KexiMainMenuActionShortcut(shortcut, action, this); } } } static void addThreeDotsToActionText(QAction* action) { action->setText(xi18nc("Action name with three dots...", "%1...", action->text())); } QAction * KexiMainWindow::addAction(const char *name, const QIcon &icon, const QString& text, const char *shortcut) { QAction *action = icon.isNull() ? new QAction(text, this) : new QAction(icon, text, this); actionCollection()->addAction(name, action); if (shortcut) { action->setShortcut(QKeySequence(shortcut)); QShortcut *s = new QShortcut(action->shortcut(), this); connect(s, SIGNAL(activated()), action, SLOT(trigger())); } return action; } QAction * KexiMainWindow::addAction(const char *name, const QString& text, const char *shortcut) { return addAction(name, QIcon(), text, shortcut); } void KexiMainWindow::setupActions() { KActionCollection *ac = actionCollection(); // PROJECT MENU QAction *action; ac->addAction("project_new", action = new KexiMenuWidgetAction(KStandardAction::New, this)); addThreeDotsToActionText(action); action->setShortcuts(KStandardShortcut::openNew()); action->setToolTip(xi18n("Create a new project")); action->setWhatsThis( xi18n("Creates a new project. Currently opened project is not affected.")); connect(action, SIGNAL(triggered()), this, SLOT(slotProjectNew())); setupMainMenuActionShortcut(action); ac->addAction("project_open", action = new KexiMenuWidgetAction(KStandardAction::Open, this)); action->setToolTip(xi18n("Open an existing project")); action->setWhatsThis( xi18n("Opens an existing project. Currently opened project is not affected.")); connect(action, SIGNAL(triggered()), this, SLOT(slotProjectOpen())); setupMainMenuActionShortcut(action); { ac->addAction("project_welcome", action = d->action_project_welcome = new KexiMenuWidgetAction( QIcon(), xi18n("Welcome"), this)); addThreeDotsToActionText(action); connect(action, SIGNAL(triggered()), this, SLOT(slotProjectWelcome())); setupMainMenuActionShortcut(action); action->setToolTip(xi18n("Show Welcome page")); action->setWhatsThis( xi18n("Shows Welcome page with list of recently opened projects and other information. ")); } ac->addAction("project_save", d->action_save = KStandardAction::save(this, SLOT(slotProjectSave()), this)); d->action_save->setToolTip(xi18n("Save object changes")); d->action_save->setWhatsThis(xi18n("Saves object changes from currently selected window.")); setupMainMenuActionShortcut(d->action_save); d->action_save_as = addAction("project_saveas", koIcon("document-save-as"), xi18n("Save &As...")); d->action_save_as->setToolTip(xi18n("Save object as")); d->action_save_as->setWhatsThis( xi18n("Saves object from currently selected window under a new name (within the same project).")); connect(d->action_save_as, SIGNAL(triggered()), this, SLOT(slotProjectSaveAs())); #ifdef KEXI_SHOW_UNIMPLEMENTED ac->addAction("project_properties", action = d->action_project_properties = new KexiMenuWidgetAction( koIcon("document-properties"), futureI18n("Project Properties"), this)); connect(action, SIGNAL(triggered()), this, SLOT(slotProjectProperties())); setupMainMenuActionShortcut(action); #else d->action_project_properties = d->dummy_action; #endif //! @todo replace document-import icon with something other ac->addAction("project_import_export_send", action = d->action_project_import_export_send = new KexiMenuWidgetAction( koIcon("document-import"), xi18n("&Import, Export or Send..."), this)); action->setToolTip(xi18n("Import, export or send project")); action->setWhatsThis( xi18n("Imports, exports or sends project.")); connect(action, SIGNAL(triggered()), this, SLOT(slotProjectImportExportOrSend())); setupMainMenuActionShortcut(action); ac->addAction("project_close", action = d->action_close = new KexiMenuWidgetAction( koIcon("window-close"), xi18nc("Close Project", "&Close"), this)); action->setToolTip(xi18n("Close the current project")); action->setWhatsThis(xi18n("Closes the current project.")); connect(action, SIGNAL(triggered()), this, SLOT(slotProjectClose())); setupMainMenuActionShortcut(action); ac->addAction("quit", action = new KexiMenuWidgetAction(KStandardAction::Quit, this)); connect(action, SIGNAL(triggered()), this, SLOT(slotProjectQuit())); action->setWhatsThis(xi18n("Quits Kexi application.")); setupMainMenuActionShortcut(action); #ifdef KEXI_SHOW_UNIMPLEMENTED d->action_project_relations = addAction("project_relations", KexiIcon("database-relations"), futureI18n("&Relationships..."), "Ctrl+R"); d->action_project_relations->setToolTip(futureI18n("Project relationships")); d->action_project_relations->setWhatsThis(futureI18n("Shows project relationships.")); connect(d->action_project_relations, SIGNAL(triggered()), this, SLOT(slotProjectRelations())); #else d->action_project_relations = d->dummy_action; #endif d->action_tools_import_project = addAction("tools_import_project", KexiIcon("database-import"), xi18n("&Import Database...")); d->action_tools_import_project->setToolTip(xi18n("Import entire database as a Kexi project")); d->action_tools_import_project->setWhatsThis( xi18n("Imports entire database as a Kexi project.")); connect(d->action_tools_import_project, SIGNAL(triggered()), this, SLOT(slotToolsImportProject())); d->action_tools_data_import = addAction("tools_import_tables", koIcon("document-import"), xi18n("Import Tables...")); d->action_tools_data_import->setToolTip(xi18n("Import data from an external source into this project")); d->action_tools_data_import->setWhatsThis(xi18n("Imports data from an external source into this project.")); connect(d->action_tools_data_import, SIGNAL(triggered()), this, SLOT(slotToolsImportTables())); d->action_tools_compact_database = addAction("tools_compact_database", //! @todo icon koIcon("application-x-compress"), xi18n("&Compact Database...")); d->action_tools_compact_database->setToolTip(xi18n("Compact the current database project")); d->action_tools_compact_database->setWhatsThis( xi18n("Compacts the current database project, so it will take less space and work faster.")); connect(d->action_tools_compact_database, SIGNAL(triggered()), this, SLOT(slotToolsCompactDatabase())); if (d->userMode) d->action_project_import_data_table = 0; else { d->action_project_import_data_table = addAction("project_import_data_table", KexiIcon("document-empty"), /*! @todo: change to "file_import" with a table or so */ xi18nc("Import->Table Data From File...", "Import Data From &File...")); d->action_project_import_data_table->setToolTip(xi18n("Import table data from a file")); d->action_project_import_data_table->setWhatsThis(xi18n("Imports table data from a file.")); connect(d->action_project_import_data_table, SIGNAL(triggered()), this, SLOT(slotProjectImportDataTable())); } d->action_project_export_data_table = addAction("project_export_data_table", KexiIcon("table"), /*! @todo: change to "file_export" with a table or so */ xi18nc("Export->Table or Query Data to File...", "Export Data to &File...")); d->action_project_export_data_table->setToolTip( xi18n("Export data from the active table or query to a file")); d->action_project_export_data_table->setWhatsThis( xi18n("Exports data from the active table or query to a file.")); connect(d->action_project_export_data_table, SIGNAL(triggered()), this, SLOT(slotProjectExportDataTable())); //! @todo new QAction(xi18n("From File..."), "document-open", 0, //! this, SLOT(slotImportFile()), actionCollection(), "project_import_file"); //! @todo new QAction(xi18n("From Server..."), "network-server-database", 0, //! this, SLOT(slotImportServer()), actionCollection(), "project_import_server"); #ifdef KEXI_QUICK_PRINTING_SUPPORT ac->addAction("project_print", d->action_project_print = KStandardAction::print(this, SLOT(slotProjectPrint()), this)); d->action_project_print->setToolTip(futureI18n("Print data from the active table or query")); d->action_project_print->setWhatsThis(futureI18n("Prints data from the active table or query.")); ac->addAction("project_print_preview", d->action_project_print_preview = KStandardAction::printPreview( this, SLOT(slotProjectPrintPreview()), this)); d->action_project_print_preview->setToolTip( futureI18n("Show print preview for the active table or query")); d->action_project_print_preview->setWhatsThis( futureI18n("Shows print preview for the active table or query.")); d->action_project_print_setup = addAction("project_print_setup", koIcon("configure"), futureI18n("Print Set&up...")); //!< @todo document-page-setup could be a better icon d->action_project_print_setup->setToolTip( futureI18n("Show print setup for the active table or query")); d->action_project_print_setup->setWhatsThis( futureI18n("Shows print setup for the active table or query.")); connect(d->action_project_print_setup, SIGNAL(triggered()), this, SLOT(slotProjectPageSetup())); #endif //EDIT MENU d->action_edit_cut = createSharedAction(KStandardAction::Cut); d->action_edit_copy = createSharedAction(KStandardAction::Copy); d->action_edit_paste = createSharedAction(KStandardAction::Paste); if (d->userMode) d->action_edit_paste_special_data_table = 0; else { d->action_edit_paste_special_data_table = addAction( "edit_paste_special_data_table", d->action_edit_paste->icon(), xi18nc("Paste Special->As Data &Table...", "Paste Special...")); d->action_edit_paste_special_data_table->setToolTip( xi18n("Paste clipboard data as a table")); d->action_edit_paste_special_data_table->setWhatsThis( xi18n("Pastes clipboard data as a table.")); connect(d->action_edit_paste_special_data_table, SIGNAL(triggered()), this, SLOT(slotEditPasteSpecialDataTable())); } d->action_edit_copy_special_data_table = addAction( "edit_copy_special_data_table", KexiIcon("table"), xi18nc("Copy Special->Table or Query Data...", "Copy Special...")); d->action_edit_copy_special_data_table->setToolTip( xi18n("Copy selected table or query data to clipboard")); d->action_edit_copy_special_data_table->setWhatsThis( xi18n("Copies selected table or query data to clipboard.")); connect(d->action_edit_copy_special_data_table, SIGNAL(triggered()), this, SLOT(slotEditCopySpecialDataTable())); d->action_edit_undo = createSharedAction(KStandardAction::Undo); d->action_edit_undo->setWhatsThis(xi18n("Reverts the most recent editing action.")); d->action_edit_redo = createSharedAction(KStandardAction::Redo); d->action_edit_redo->setWhatsThis(xi18n("Reverts the most recent undo action.")); ac->addAction("edit_find", d->action_edit_find = KStandardAction::find( this, SLOT(slotEditFind()), this)); d->action_edit_find->setToolTip(xi18n("Find text")); d->action_edit_find->setWhatsThis(xi18n("Looks up the first occurrence of a piece of text.")); ac->addAction("edit_findnext", d->action_edit_findnext = KStandardAction::findNext( this, SLOT(slotEditFindNext()), this)); ac->addAction("edit_findprevious", d->action_edit_findprev = KStandardAction::findPrev( this, SLOT(slotEditFindPrevious()), this)); d->action_edit_replace = 0; //! @todo d->action_edit_replace = KStandardAction::replace( //! this, SLOT(slotEditReplace()), actionCollection(), "project_print_preview" ); d->action_edit_replace_all = 0; //! @todo d->action_edit_replace_all = new QAction( xi18n("Replace All"), "", 0, //! this, SLOT(slotEditReplaceAll()), actionCollection(), "edit_replaceall"); d->action_edit_select_all = createSharedAction(KStandardAction::SelectAll); d->action_edit_delete = createSharedAction(xi18n("&Delete"), koIconName("edit-delete"), QKeySequence(), "edit_delete"); d->action_edit_delete->setToolTip(xi18n("Delete selected object")); d->action_edit_delete->setWhatsThis(xi18n("Deletes currently selected object.")); d->action_edit_delete_row = createSharedAction(xi18n("Delete Record"), KexiIconName("edit-table-delete-row"), QKeySequence(Qt::CTRL + Qt::Key_Delete), "edit_delete_row"); d->action_edit_delete_row->setToolTip(xi18n("Delete the current record")); d->action_edit_delete_row->setWhatsThis(xi18n("Deletes the current record.")); d->action_edit_clear_table = createSharedAction(xi18n("Clear Table Contents..."), KexiIconName("edit-table-clear"), QKeySequence(), "edit_clear_table"); d->action_edit_clear_table->setToolTip(xi18n("Clear table contents")); d->action_edit_clear_table->setWhatsThis(xi18n("Clears table contents.")); setActionVolatile(d->action_edit_clear_table, true); d->action_edit_edititem = createSharedAction(xi18n("Edit Item"), QString(), QKeySequence(), /* CONFLICT in TV: Qt::Key_F2, */ "edit_edititem"); d->action_edit_edititem->setToolTip(xi18n("Edit currently selected item")); d->action_edit_edititem->setWhatsThis(xi18n("Edits currently selected item.")); d->action_edit_insert_empty_row = createSharedAction(xi18n("&Insert Empty Row"), KexiIconName("edit-table-insert-row"), QKeySequence(Qt::SHIFT | Qt::CTRL | Qt::Key_Insert), "edit_insert_empty_row"); setActionVolatile(d->action_edit_insert_empty_row, true); d->action_edit_insert_empty_row->setToolTip(xi18n("Insert one empty row above")); d->action_edit_insert_empty_row->setWhatsThis( xi18n("Inserts one empty row above currently selected table row.")); //VIEW MENU /* UNUSED, see KexiToggleViewModeAction if (!d->userMode) { d->action_view_mode = new QActionGroup(this); ac->addAction( "view_data_mode", d->action_view_data_mode = new KToggleAction( KexiIcon("data-view"), xi18n("&Data View"), d->action_view_mode) ); // d->action_view_data_mode->setObjectName("view_data_mode"); d->action_view_data_mode->setShortcut(QKeySequence("F6")); //d->action_view_data_mode->setExclusiveGroup("view_mode"); d->action_view_data_mode->setToolTip(xi18n("Switch to data view")); d->action_view_data_mode->setWhatsThis(xi18n("Switches to data view.")); d->actions_for_view_modes.insert( Kexi::DataViewMode, d->action_view_data_mode ); connect(d->action_view_data_mode, SIGNAL(triggered()), this, SLOT(slotViewDataMode())); } else { d->action_view_mode = 0; d->action_view_data_mode = 0; } if (!d->userMode) { ac->addAction( "view_design_mode", d->action_view_design_mode = new KToggleAction( KexiIcon("design-view"), xi18n("D&esign View"), d->action_view_mode) ); // d->action_view_design_mode->setObjectName("view_design_mode"); d->action_view_design_mode->setShortcut(QKeySequence("F7")); //d->action_view_design_mode->setExclusiveGroup("view_mode"); d->action_view_design_mode->setToolTip(xi18n("Switch to design view")); d->action_view_design_mode->setWhatsThis(xi18n("Switches to design view.")); d->actions_for_view_modes.insert( Kexi::DesignViewMode, d->action_view_design_mode ); connect(d->action_view_design_mode, SIGNAL(triggered()), this, SLOT(slotViewDesignMode())); } else d->action_view_design_mode = 0; if (!d->userMode) { ac->addAction( "view_text_mode", d->action_view_text_mode = new KToggleAction( KexiIcon("sql-view"), xi18n("&Text View"), d->action_view_mode) ); d->action_view_text_mode->setObjectName("view_text_mode"); d->action_view_text_mode->setShortcut(QKeySequence("F8")); //d->action_view_text_mode->setExclusiveGroup("view_mode"); d->action_view_text_mode->setToolTip(xi18n("Switch to text view")); d->action_view_text_mode->setWhatsThis(xi18n("Switches to text view.")); d->actions_for_view_modes.insert( Kexi::TextViewMode, d->action_view_text_mode ); connect(d->action_view_text_mode, SIGNAL(triggered()), this, SLOT(slotViewTextMode())); } else d->action_view_text_mode = 0; */ if (d->isProjectNavigatorVisible) { d->action_show_nav = addAction("view_navigator", xi18n("Show Project Navigator"), "Alt+0"); d->action_show_nav->setToolTip(xi18n("Show the Project Navigator pane")); d->action_show_nav->setWhatsThis(xi18n("Shows the Project Navigator pane.")); connect(d->action_show_nav, SIGNAL(triggered()), this, SLOT(slotShowNavigator())); } else { d->action_show_nav = 0; } if (d->isProjectNavigatorVisible) { - // Shortcut taken from "Activate Projects pane" http://doc.qt.io/qtcreator/creator-keyboard-shortcuts.html + // 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) { d->action_show_propeditor = addAction("view_propeditor", xi18n("Show Property Editor"), "Alt+3"); d->action_show_propeditor->setToolTip(xi18n("Show the Property Editor pane")); d->action_show_propeditor->setWhatsThis(xi18n("Shows the Property Editor pane.")); connect(d->action_show_propeditor, SIGNAL(triggered()), this, SLOT(slotShowPropertyEditor())); } else { d->action_show_propeditor = 0; } if (!d->userMode) { d->action_activate_propeditor = addAction("activate_propeditor", xi18n("Activate Property Editor"), "Alt+-"); d->action_activate_propeditor->setToolTip(xi18n("Activate the Property Editor pane")); d->action_activate_propeditor->setWhatsThis(xi18n("Activates the Property Editor pane. If it is hidden, shows it first.")); connect(d->action_activate_propeditor, SIGNAL(triggered()), this, SLOT(slotActivatePropertyEditor())); } else { d->action_activate_propeditor = 0; } d->action_view_global_search = addAction("view_global_search", xi18n("Switch to Global Search"), "Ctrl+K"); d->action_view_global_search->setToolTip(xi18n("Switch to Global Search box")); d->action_view_global_search->setWhatsThis(xi18n("Switches to Global Search box.")); // (connection is added elsewhere) //DATA MENU d->action_data_save_row = createSharedAction(xi18n("&Save Record"), koIconName("dialog-ok"), QKeySequence(Qt::SHIFT + Qt::Key_Return), "data_save_row"); d->action_data_save_row->setToolTip(xi18n("Save changes made to the current record")); d->action_data_save_row->setWhatsThis(xi18n("Saves changes made to the current record.")); //temp. disable because of problems with volatile actions setActionVolatile( d->action_data_save_row, true ); d->action_data_cancel_row_changes = createSharedAction(xi18n("&Cancel Record Changes"), koIconName("dialog-cancel"), QKeySequence(Qt::Key_Escape), "data_cancel_row_changes"); d->action_data_cancel_row_changes->setToolTip( xi18n("Cancel changes made to the current record")); d->action_data_cancel_row_changes->setWhatsThis( xi18n("Cancels changes made to the current record.")); //temp. disable because of problems with volatile actions setActionVolatile( d->action_data_cancel_row_changes, true ); d->action_data_execute = createSharedAction( xi18n("&Execute"), koIconName("media-playback-start"), QKeySequence(), "data_execute"); //! @todo d->action_data_execute->setToolTip(xi18n("")); //! @todo d->action_data_execute->setWhatsThis(xi18n("")); #ifdef KEXI_SHOW_UNIMPLEMENTED action = createSharedAction(futureI18n("&Filter"), koIconName("view-filter"), QKeySequence(), "data_filter"); setActionVolatile(action, true); #endif //! @todo action->setToolTip(xi18n("")); //! @todo action->setWhatsThis(xi18n("")); // - record-navigation related actions createSharedAction(KexiRecordNavigator::Actions::moveToFirstRecord(), QKeySequence(), "data_go_to_first_record"); createSharedAction(KexiRecordNavigator::Actions::moveToPreviousRecord(), QKeySequence(), "data_go_to_previous_record"); createSharedAction(KexiRecordNavigator::Actions::moveToNextRecord(), QKeySequence(), "data_go_to_next_record"); createSharedAction(KexiRecordNavigator::Actions::moveToLastRecord(), QKeySequence(), "data_go_to_last_record"); createSharedAction(KexiRecordNavigator::Actions::moveToNewRecord(), QKeySequence(), "data_go_to_new_record"); //FORMAT MENU d->action_format_font = createSharedAction(xi18n("&Font..."), koIconName("fonts-package"), QKeySequence(), "format_font"); d->action_format_font->setToolTip(xi18n("Change font for selected object")); d->action_format_font->setWhatsThis(xi18n("Changes font for selected object.")); //TOOLS MENU // WINDOW MENU // additional 'Window' menu items d->action_window_next = addAction("window_next", xi18n("&Next Window"), "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_tab_next = addAction("tab_next", futureI18n("&Next Tab"), "Ctrl+Tab"); d->action_tab_next->setToolTip(futureI18n("Next tab")); d->action_tab_next->setWhatsThis(futureI18n("Switches to the next tab.")); connect(d->action_tab_next, &QAction::triggered, this, &KexiMainWindow::activateNextTab); d->action_tab_previous = addAction("tab_previous", futureI18n("&Previous Tab"), "Ctrl+Shift+Tab"); d->action_tab_previous->setToolTip(futureI18n("Previous tab")); d->action_tab_previous->setWhatsThis(futureI18n("Switches to the previous tab.")); 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); QList shortcuts; shortcuts << d->action_window_fullscreen->shortcut() << QKeySequence("F11"); d->action_window_fullscreen->setShortcuts(shortcuts); QShortcut *s = new QShortcut(d->action_window_fullscreen->shortcut(), this); connect(s, SIGNAL(activated()), d->action_window_fullscreen, SLOT(trigger())); if (d->action_window_fullscreen->shortcuts().count() > 1) { QShortcut *sa = new QShortcut(d->action_window_fullscreen->shortcuts().value(1), this); connect(sa, SIGNAL(activated()), d->action_window_fullscreen, SLOT(trigger())); } //SETTINGS MENU //! @todo put 'configure keys' into settings view #ifdef KEXI_SHOW_UNIMPLEMENTED //! @todo toolbars configuration will be handled in a special way #endif #ifdef KEXI_MACROS_SUPPORT Kexi::tempShowMacros() = true; #else Kexi::tempShowMacros() = false; #endif #ifdef KEXI_SCRIPTS_SUPPORT Kexi::tempShowScripts() = true; #else Kexi::tempShowScripts() = false; #endif #ifdef KEXI_SHOW_UNIMPLEMENTED //! @todo implement settings window in a specific way ac->addAction("settings", action = d->action_settings = new KexiMenuWidgetAction( KStandardAction::Preferences, this)); action->setObjectName("settings"); action->setText(futureI18n("Settings...")); action->setToolTip(futureI18n("Show Kexi settings")); action->setWhatsThis(futureI18n("Shows Kexi settings.")); connect(action, SIGNAL(triggered()), this, SLOT(slotSettings())); setupMainMenuActionShortcut(action); #else d->action_settings = d->dummy_action; #endif //! @todo reenable 'tip of the day' later #if 0 KStandardAction::tipOfDay(this, SLOT(slotTipOfTheDayAction()), actionCollection()) ->setWhatsThis(xi18n("This shows useful tips on the use of this application.")); #endif // GLOBAL d->action_show_help_menu = addAction("help_show_menu", xi18nc("Help Menu", "Help"), "Alt+H"); d->action_show_help_menu->setToolTip(xi18n("Show Help menu")); d->action_show_help_menu->setWhatsThis(xi18n("Shows Help menu.")); // (connection is added elsewhere) // ----- declare action categories, so form's "assign action to button" // (and macros in the future) will be able to recognize category // of actions and filter them ----------------------------------- //! @todo shouldn't we move this to core? Kexi::ActionCategories *acat = Kexi::actionCategories(); acat->addAction("data_execute", Kexi::PartItemActionCategory); //! @todo unused for now acat->addWindowAction("data_filter", KexiPart::TableObjectType, KexiPart::QueryObjectType, KexiPart::FormObjectType); acat->addWindowAction("data_save_row", KexiPart::TableObjectType, KexiPart::QueryObjectType, KexiPart::FormObjectType); acat->addWindowAction("data_cancel_row_changes", KexiPart::TableObjectType, KexiPart::QueryObjectType, KexiPart::FormObjectType); acat->addWindowAction("delete_table_row", KexiPart::TableObjectType, KexiPart::QueryObjectType, KexiPart::FormObjectType); //! @todo support this in KexiPart::FormObjectType as well acat->addWindowAction("data_sort_az", KexiPart::TableObjectType, KexiPart::QueryObjectType); //! @todo support this in KexiPart::FormObjectType as well acat->addWindowAction("data_sort_za", KexiPart::TableObjectType, KexiPart::QueryObjectType); //! @todo support this in KexiPart::FormObjectType as well acat->addWindowAction("edit_clear_table", KexiPart::TableObjectType, KexiPart::QueryObjectType); //! @todo support this in KexiPart::FormObjectType as well acat->addWindowAction("edit_copy_special_data_table", KexiPart::TableObjectType, KexiPart::QueryObjectType); //! @todo support this in FormObjectType as well acat->addWindowAction("project_export_data_table", KexiPart::TableObjectType, KexiPart::QueryObjectType); // GlobalActions, etc. acat->addAction("edit_copy", Kexi::GlobalActionCategory | Kexi::PartItemActionCategory); acat->addAction("edit_cut", Kexi::GlobalActionCategory | Kexi::PartItemActionCategory); acat->addAction("edit_paste", Kexi::GlobalActionCategory | Kexi::PartItemActionCategory); acat->addAction("edit_delete", Kexi::GlobalActionCategory | Kexi::PartItemActionCategory | Kexi::WindowActionCategory, KexiPart::TableObjectType, KexiPart::QueryObjectType, KexiPart::FormObjectType); acat->addAction("edit_delete_row", Kexi::GlobalActionCategory | Kexi::WindowActionCategory, KexiPart::TableObjectType, KexiPart::QueryObjectType, KexiPart::FormObjectType); acat->addAction("edit_edititem", Kexi::PartItemActionCategory | Kexi::WindowActionCategory, KexiPart::TableObjectType, KexiPart::QueryObjectType); acat->addAction("edit_find", Kexi::GlobalActionCategory | Kexi::WindowActionCategory, KexiPart::TableObjectType, KexiPart::QueryObjectType, KexiPart::FormObjectType); acat->addAction("edit_findnext", Kexi::GlobalActionCategory | Kexi::WindowActionCategory, KexiPart::TableObjectType, KexiPart::QueryObjectType, KexiPart::FormObjectType); acat->addAction("edit_findprevious", Kexi::GlobalActionCategory | Kexi::WindowActionCategory, KexiPart::TableObjectType, KexiPart::QueryObjectType, KexiPart::FormObjectType); acat->addAction("edit_replace", Kexi::GlobalActionCategory | Kexi::WindowActionCategory, KexiPart::TableObjectType, KexiPart::QueryObjectType, KexiPart::FormObjectType); acat->addAction("edit_paste_special_data_table", Kexi::GlobalActionCategory); acat->addAction("help_about_app", Kexi::GlobalActionCategory); acat->addAction("help_about_kde", Kexi::GlobalActionCategory); acat->addAction("help_contents", Kexi::GlobalActionCategory); acat->addAction("help_report_bug", Kexi::GlobalActionCategory); acat->addAction("help_whats_this", Kexi::GlobalActionCategory); acat->addAction("help_donate", Kexi::GlobalActionCategory); // disabled for now acat->addAction("switch_application_language", Kexi::GlobalActionCategory); acat->addAction("options_configure_keybinding", Kexi::GlobalActionCategory); acat->addAction("project_close", Kexi::GlobalActionCategory); acat->addAction("project_import_data_table", Kexi::GlobalActionCategory); acat->addAction("project_new", Kexi::GlobalActionCategory); acat->addAction("project_open", Kexi::GlobalActionCategory); #ifdef KEXI_QUICK_PRINTING_SUPPORT //! @todo support this in FormObjectType, ReportObjectType as well as others acat->addAction("project_print", Kexi::WindowActionCategory, KexiPart::TableObjectType, KexiPart::QueryObjectType); //! @todo support this in FormObjectType, ReportObjectType as well as others acat->addAction("project_print_preview", Kexi::WindowActionCategory, KexiPart::TableObjectType, KexiPart::QueryObjectType); //! @todo support this in FormObjectType, ReportObjectType as well as others acat->addAction("project_print_setup", Kexi::WindowActionCategory, KexiPart::TableObjectType, KexiPart::QueryObjectType); #endif acat->addAction("quit", Kexi::GlobalActionCategory); acat->addAction("tools_compact_database", Kexi::GlobalActionCategory); acat->addAction("tools_import_project", Kexi::GlobalActionCategory); acat->addAction("tools_import_tables", Kexi::GlobalActionCategory); acat->addAction("view_data_mode", Kexi::GlobalActionCategory); acat->addAction("view_design_mode", Kexi::GlobalActionCategory); acat->addAction("view_text_mode", Kexi::GlobalActionCategory); acat->addAction("view_mainarea", Kexi::GlobalActionCategory); acat->addAction("view_navigator", Kexi::GlobalActionCategory); acat->addAction("activate_navigator", Kexi::GlobalActionCategory); acat->addAction("view_propeditor", Kexi::GlobalActionCategory); acat->addAction("activate_mainarea", Kexi::GlobalActionCategory); acat->addAction("activate_propeditor", Kexi::GlobalActionCategory); acat->addAction("window_close", Kexi::GlobalActionCategory | Kexi::WindowActionCategory); acat->setAllObjectTypesSupported("window_close", true); acat->addAction("window_next", Kexi::GlobalActionCategory); acat->addAction("window_previous", Kexi::GlobalActionCategory); acat->addAction("full_screen", Kexi::GlobalActionCategory); //skipped - design view only acat->addAction("format_font", Kexi::NoActionCategory); acat->addAction("project_save", Kexi::NoActionCategory); acat->addAction("edit_insert_empty_row", Kexi::NoActionCategory); //! @todo support this in KexiPart::TableObjectType, KexiPart::QueryObjectType later acat->addAction("edit_select_all", Kexi::NoActionCategory); //! @todo support this in KexiPart::TableObjectType, KexiPart::QueryObjectType, KexiPart::FormObjectType later acat->addAction("edit_redo", Kexi::NoActionCategory); //! @todo support this in KexiPart::TableObjectType, KexiPart::QueryObjectType, KexiPart::FormObjectType later acat->addAction("edit_undo", Kexi::NoActionCategory); //record-navigation related actions acat->addAction("data_go_to_first_record", Kexi::WindowActionCategory, KexiPart::TableObjectType, KexiPart::QueryObjectType, KexiPart::FormObjectType); acat->addAction("data_go_to_previous_record", Kexi::WindowActionCategory, KexiPart::TableObjectType, KexiPart::QueryObjectType, KexiPart::FormObjectType); acat->addAction("data_go_to_next_record", Kexi::WindowActionCategory, KexiPart::TableObjectType, KexiPart::QueryObjectType, KexiPart::FormObjectType); acat->addAction("data_go_to_last_record", Kexi::WindowActionCategory, KexiPart::TableObjectType, KexiPart::QueryObjectType, KexiPart::FormObjectType); acat->addAction("data_go_to_new_record", Kexi::WindowActionCategory, KexiPart::TableObjectType, KexiPart::QueryObjectType, KexiPart::FormObjectType); //skipped - internal: acat->addAction("tablepart_create", Kexi::NoActionCategory); acat->addAction("querypart_create", Kexi::NoActionCategory); acat->addAction("formpart_create", Kexi::NoActionCategory); acat->addAction("reportpart_create", Kexi::NoActionCategory); acat->addAction("macropart_create", Kexi::NoActionCategory); acat->addAction("scriptpart_create", Kexi::NoActionCategory); } void KexiMainWindow::invalidateActions() { invalidateProjectWideActions(); invalidateSharedActions(); } void KexiMainWindow::invalidateSharedActions(QObject *o) { //! @todo enabling is more complex... /* d->action_edit_cut->setEnabled(true); d->action_edit_copy->setEnabled(true); d->action_edit_paste->setEnabled(true);*/ if (!o) o = focusWindow(); KexiSharedActionHost::invalidateSharedActions(o); } void KexiMainWindow::invalidateSharedActions() { invalidateSharedActions(0); } // unused, I think void KexiMainWindow::invalidateSharedActionsLater() { QTimer::singleShot(1, this, SLOT(invalidateSharedActions())); } void KexiMainWindow::invalidateProjectWideActions() { const bool has_window = currentWindow(); const bool window_dirty = currentWindow() && currentWindow()->isDirty(); const bool readOnly = d->prj && d->prj->dbConnection() && d->prj->dbConnection()->options()->isReadOnly(); //PROJECT MENU d->action_save->setEnabled(has_window && window_dirty && !readOnly); d->action_save_as->setEnabled(has_window && !readOnly); d->action_project_properties->setEnabled(d->prj); d->action_close->setEnabled(d->prj); d->action_project_relations->setEnabled(d->prj); //DATA MENU if (d->action_project_import_data_table) d->action_project_import_data_table->setEnabled(d->prj && !readOnly); if (d->action_tools_data_import) d->action_tools_data_import->setEnabled(d->prj && !readOnly); d->action_project_export_data_table->setEnabled( currentWindow() && currentWindow()->part()->info()->isDataExportSupported()); if (d->action_edit_paste_special_data_table) d->action_edit_paste_special_data_table->setEnabled(d->prj && !readOnly); #ifdef KEXI_QUICK_PRINTING_SUPPORT const bool printingActionsEnabled = currentWindow() && currentWindow()->part()->info()->isPrintingSupported() && !currentWindow()->neverSaved(); d->action_project_print->setEnabled(printingActionsEnabled); d->action_project_print_preview->setEnabled(printingActionsEnabled); d->action_project_print_setup->setEnabled(printingActionsEnabled); #endif //EDIT MENU //! @todo "copy special" is currently enabled only for data view mode; //! what about allowing it to enable in design view for "kexi/table" ? if (currentWindow() && currentWindow()->currentViewMode() == Kexi::DataViewMode) { KexiPart::Info *activePartInfo = currentWindow()->part()->info(); d->action_edit_copy_special_data_table->setEnabled( activePartInfo ? activePartInfo->isDataExportSupported() : false); } else { d->action_edit_copy_special_data_table->setEnabled(false); } d->action_edit_find->setEnabled(d->prj); //VIEW MENU if (d->action_show_nav) d->action_show_nav->setEnabled(d->prj); d->action_activate_mainarea->setEnabled(d->prj); if (d->action_show_propeditor) d->action_show_propeditor->setEnabled(d->prj); #ifdef KEXI_SHOW_CONTEXT_HELP d->action_show_helper->setEnabled(d->prj); #endif //CREATE MENU if (d->tabbedToolBar && d->tabbedToolBar->createWidgetToolBar()) d->tabbedToolBar->createWidgetToolBar()->setEnabled(d->prj); // DATA MENU //TOOLS MENU // "compact db" supported if there's no db or the current db supports compacting and is opened r/w: d->action_tools_compact_database->setEnabled( //! @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->navigator) { d->navigator->setEnabled(d->prj); } if (d->propEditor) d->propEditorTabWidget->setEnabled(d->prj); } tristate KexiMainWindow::startup() { tristate result = true; switch (KexiStartupHandler::global()->action()) { case KexiStartupHandler::CreateBlankProject: d->updatePropEditorVisibility(Kexi::NoViewMode); break; #ifdef KEXI_PROJECT_TEMPLATES case KexiStartupHandler::CreateFromTemplate: result = createProjectFromTemplate(*KexiStartupHandler::global()->projectData()); break; #endif case KexiStartupHandler::OpenProject: result = openProject(*KexiStartupHandler::global()->projectData()); break; case KexiStartupHandler::ImportProject: result = showProjectMigrationWizard( KexiStartupHandler::global()->importActionData().mimeType, KexiStartupHandler::global()->importActionData().fileName ); break; case KexiStartupHandler::ShowWelcomeScreen: //! @todo show welcome screen as soon as is available QTimer::singleShot(100, this, SLOT(slotProjectWelcome())); break; default: d->updatePropEditorVisibility(Kexi::NoViewMode); } return result; } static QString internalReason(const KDbResult &result) { const QString msg = result.message(); if (msg.isEmpty()) { return QString(); } return xi18n("
(reason: %1)", msg); } tristate KexiMainWindow::openProject(const KexiProjectData& projectData) { //qDebug() << projectData; QScopedPointer prj(createKexiProjectObject(projectData)); if (~KexiDBPasswordDialog::getPasswordIfNeeded(prj->data()->connectionData(), this)) { return cancelled; } bool incompatibleWithKexi; tristate res = prj->open(&incompatibleWithKexi); if (prj->data()->connectionData()->isPasswordNeeded()) { // password was supplied in this session, and shouldn't be stored or reused afterwards, // so let's remove it prj->data()->connectionData()->setPassword(QString()); } if (~res) { return cancelled; } else if (!res) { if (incompatibleWithKexi) { if (KMessageBox::Yes == KMessageBox::questionYesNo(this, xi18nc("@info (don't add tags around %1, it's done already)", "Database project %1 does not appear to have been created using Kexi." "Do you want to import it as a new Kexi project?", projectData.infoString()), QString(), KGuiItem(xi18nc("@action:button Import Database", "&Import..."), KexiIconName("database-import")), KStandardGuiItem::cancel())) { const bool anotherProjectAlreadyOpened = prj; tristate res = showProjectMigrationWizard("application/x-kexi-connectiondata", projectData.databaseName(), *projectData.connectionData()); if (!anotherProjectAlreadyOpened) //the project could have been opened within this instance return res; //always return cancelled because even if migration succeeded, new Kexi instance //will be started if user wanted to open the imported db return cancelled; } return cancelled; } return false; } // success d->prj = prj.take(); setupProjectNavigator(); d->prj->data()->setLastOpened(QDateTime::currentDateTime()); Kexi::recentProjects()->addProjectData(*d->prj->data()); updateReadOnlyState(); invalidateActions(); setMessagesEnabled(false); QTimer::singleShot(1, this, SLOT(slotAutoOpenObjectsLater())); if (d->tabbedToolBar) { d->tabbedToolBar->showTab("create");// not needed since create toolbar already shows toolbar! move when kexi starts d->tabbedToolBar->showTab("data"); d->tabbedToolBar->showTab("external"); d->tabbedToolBar->showTab("tools"); d->tabbedToolBar->hideTab("form");//temporalily until createToolbar is split d->tabbedToolBar->hideTab("report");//temporalily until createToolbar is split // make sure any tab is activated d->tabbedToolBar->setCurrentTab(0); } return true; } tristate KexiMainWindow::openProject(const KexiProjectData& data, const QString& shortcutPath, bool *opened) { if (!shortcutPath.isEmpty() && d->prj) { const tristate result = openProjectInExternalKexiInstance( shortcutPath, QString(), QString()); if (result == true) { *opened = true; } return result; } return openProject(data); } tristate KexiMainWindow::createProjectFromTemplate(const KexiProjectData& projectData) { Q_UNUSED(projectData); #ifdef KEXI_PROJECT_TEMPLATES QStringList mimetypes; mimetypes.append(KDb::defaultFileBasedDriverMimeType()); QString fname; //! @todo KEXI3 add equivalent of kfiledialog:/// const QString startDir("kfiledialog:///OpenExistingOrCreateNewProject"/*as in KexiNewProjectWizard*/); const QString caption(xi18nc("@window:title", "Select New Project's Location")); while (true) { if (fname.isEmpty() && !projectData.connectionData()->databaseName().isEmpty()) { //propose filename from db template name fname = projectData.connectionData()->databaseName(); } const bool specialDir = fname.isEmpty(); qDebug() << fname << "............."; QFileDialog dlg(specialDir ? QUrl(startDir) : QUrl(), QString(), this); dlg.setModal(true); dlg.setMimeFilter(mimetypes); if (!specialDir) dlg.selectUrl(QUrl::fromLocalFile(fname); // may also be a filename dlg.setFileMode(QFileDialog::ExistingFile); dlg.setFileMode(QFileDialog::AcceptOpen); dlg.setWindowTitle(caption); if (QDialog::Accepted != dlg.exec()) { return cancelled; } if (dlg.selectedFiles().isEmpty() { return cancelled; } fname = dlg.selectedFiles().first(); if (fname.isEmpty()) { return cancelled; } if (KexiUtils::askForFileOverwriting(fname, this)) { break; } } QFile sourceFile(projectData.connectionData()->fileName()); if (!sourceFile.copy(fname)) { //! @todo show error from with QFile::FileError return false; } return openProject(fname, 0, QString(), projectData.autoopenObjects/*copy*/); #else return false; #endif } void KexiMainWindow::updateReadOnlyState() { const bool readOnly = d->prj && d->prj->dbConnection() && d->prj->dbConnection()->options()->isReadOnly(); //! @todo KEXI3 show read-only flag in the GUI because we have no statusbar if (d->navigator) { d->navigator->setReadOnly(readOnly); } // update "insert ....." actions for every part KexiPart::PartInfoList *plist = Kexi::partManager().infoList(); if (plist) { foreach(KexiPart::Info *info, *plist) { QAction *a = info->newObjectAction(); if (a) a->setEnabled(!readOnly); } } } void KexiMainWindow::slotAutoOpenObjectsLater() { QString not_found_msg; bool openingCancelled; //ok, now open "autoopen: objects if (d->prj) { 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->navigator) { d->navWasVisibleBeforeProjectClosing = d->navDockWidget->isVisible(); d->navDockWidget->hide(); d->navigator->setProject(0); slotProjectNavigatorVisibilityChanged(true); // hide side tab } if (d->propEditorDockWidget) d->propEditorDockWidget->hide(); d->clearWindows(); //sanity! delete d->prj; d->prj = 0; updateReadOnlyState(); invalidateActions(); updateAppCaption(); emit projectClosed(); return true; } void KexiMainWindow::setupContextHelp() { #ifdef KEXI_SHOW_CONTEXT_HELP d->ctxHelp = new KexiContextHelp(d->mainWidget, this); //! @todo /* d->ctxHelp->setContextHelp(xi18n("Welcome"),xi18n("The KEXI team wishes you a lot of productive work, " "with this product.


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


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

    " + additionalMessageString + "

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

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

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

    " + additionalMessageString /*may be empty*/, QString(), saveChanges, discardChanges); if (questionRes == KMessageBox::Cancel) { #ifndef KEXI_NO_PENDING_DIALOGS d->removePendingWindow(window->id()); #endif d->insideCloseWindow = false; d->windowsToClose.clear(); //give up with 'close all' return cancelled; } if (questionRes == KMessageBox::Yes) { //save it tristate res = saveObject(window, QString(), DoNotAsk); if (!res || ~res) { //! @todo show error info; (retry/ignore/cancel) #ifndef KEXI_NO_PENDING_DIALOGS d->removePendingWindow(window->id()); #endif d->insideCloseWindow = false; d->windowsToClose.clear(); //give up with 'close all' return res; } remove_on_closing = false; } } const int window_id = window->id(); //remember now, because removeObject() can destruct partitem object if (remove_on_closing) { //we won't save this object, and it was never saved -remove it if (!removeObject(window->partItem(), true)) { #ifndef KEXI_NO_PENDING_DIALOGS d->removePendingWindow(window->id()); #endif //msg? //! @todo ask if we'd continue and return true/false d->insideCloseWindow = false; d->windowsToClose.clear(); //give up with 'close all' return false; } } else { //not dirty now if (d->navigator) { d->navigator->updateItemName(*window->partItem(), false); } } hideDesignTab(previousItemId, QString()); d->removeWindow(window_id); d->setWindowContainerExistsFor(window->partItem()->identifier(), false); QWidget *windowContainer = window->parentWidget(); d->mainWidget->tabWidget()->removeTab( d->mainWidget->tabWidget()->indexOf(windowContainer)); #ifdef KEXI_QUICK_PRINTING_SUPPORT //also remove from 'print setup dialogs' cache, if needed int printedObjectID = 0; if (d->pageSetupWindowItemID2dataItemID_map.contains(window_id)) printedObjectID = d->pageSetupWindowItemID2dataItemID_map[ window_id ]; d->pageSetupWindows.remove(printedObjectID); #endif delete windowContainer; //focus navigator if nothing else available if (d->openedWindowsCount() == 0) { if (d->navigator) { d->navigator->setFocus(); } d->updatePropEditorVisibility(Kexi::NoViewMode); } invalidateActions(); d->insideCloseWindow = false; if (!d->windowsToClose.isEmpty()) {//continue 'close all' KexiWindow* w = d->windowsToClose.takeAt(0); closeWindow(w, true); } #ifndef KEXI_NO_PENDING_DIALOGS d->removePendingWindow(window_id); //perform pending global action that was suspended: if (!d->pendingWindowsExist()) { d->executeActionWhenPendingJobsAreFinished(); } #endif d->mainWidget->slotCurrentTabIndexChanged(d->mainWidget->tabWidget()->currentIndex()); showDesignTabIfNeeded(0); if (currentWindow()) { restoreDesignTabIfNeeded(currentWindow()->partItem()->pluginId(), currentWindow()->currentViewMode(), 0); } d->tabsToActivateOnShow.remove(previousItemId); return true; } QWidget* KexiMainWindow::findWindow(QWidget *w) { while (w && !acceptsSharedActions(w)) { if (w == d->propEditorDockWidget) return currentWindow(); w = w->parentWidget(); } return w; } KexiWindow* KexiMainWindow::openedWindowFor(int identifier) { return d->openedWindowFor(identifier); } KexiWindow* KexiMainWindow::openedWindowFor(const KexiPart::Item* item) { return item ? openedWindowFor(item->identifier()) : 0; } KDbQuerySchema* KexiMainWindow::unsavedQuery(int queryId) { KexiWindow * queryWindow = openedWindowFor(queryId); if (!queryWindow || !queryWindow->isDirty()) { return 0; } return queryWindow->part()->currentQuery(queryWindow->viewForMode(Kexi::DataViewMode)); } QList KexiMainWindow::currentParametersForQuery(int queryId) const { KexiWindow *queryWindow = d->openedWindowFor(queryId); if (!queryWindow) { return QList(); } KexiView *view = queryWindow->viewForMode(Kexi::DataViewMode); if (!view) { return QList(); } return view->currentParameters(); } bool KexiMainWindow::acceptsSharedActions(QObject *w) { return w->inherits("KexiWindow") || w->inherits("KexiView"); } bool KexiMainWindow::openingAllowed(KexiPart::Item* item, Kexi::ViewMode viewMode, QString* errorMessage) { //qDebug() << viewMode; //! @todo this can be more complex once we deliver ACLs... if (!d->userMode) return true; KexiPart::Part * part = Kexi::partManager().partForPluginId(item->pluginId()); if (!part) { if (errorMessage) { *errorMessage = Kexi::partManager().result().message(); } } //qDebug() << part << item->pluginId(); //if (part) // qDebug() << item->pluginId() << part->info()->supportedUserViewModes(); return part && (part->info()->supportedUserViewModes() & viewMode); } KexiWindow * KexiMainWindow::openObject(const QString& pluginId, const QString& name, Kexi::ViewMode viewMode, bool *openingCancelled, QMap* staticObjectArgs) { KexiPart::Item *item = d->prj->itemForPluginId(pluginId, name); if (!item) return 0; return openObject(item, viewMode, openingCancelled, staticObjectArgs); } KexiWindow * KexiMainWindow::openObject(KexiPart::Item* item, Kexi::ViewMode viewMode, bool *openingCancelled, QMap* staticObjectArgs, QString* errorMessage) { Q_ASSERT(openingCancelled); if (!d->prj || !item) { return 0; } if (!openingAllowed(item, viewMode, errorMessage)) { if (errorMessage) *errorMessage = xi18nc( "opening is not allowed in \"data view/design view/text view\" mode", "opening is not allowed in \"%1\" mode", Kexi::nameForViewMode(viewMode)); *openingCancelled = true; return 0; } //qDebug() << d->prj << item; KexiWindow *prevWindow = currentWindow(); KexiUtils::WaitCursor wait; #ifndef KEXI_NO_PENDING_DIALOGS Private::PendingJobType pendingType; KexiWindow *window = d->openedWindowFor(item, pendingType); if (pendingType != Private::NoJob) { *openingCancelled = true; return 0; } #else KexiWindow *window = openedWindowFor(item); #endif int previousItemId = currentWindow() ? currentWindow()->partItem()->identifier() : 0; *openingCancelled = false; bool alreadyOpened = false; KexiWindowContainer *windowContainer = 0; if (window) { if (viewMode != window->currentViewMode()) { if (true != switchToViewMode(*window, viewMode)) return 0; } else activateWindow(*window); alreadyOpened = true; } else { if (d->windowContainerExistsFor(item->identifier())) { // window not yet present but window container exists: return 0 and wait return 0; } KexiPart::Part *part = Kexi::partManager().partForPluginId(item->pluginId()); d->updatePropEditorVisibility(viewMode, part ? part->info() : 0); //update tabs before opening updateCustomPropertyPanelTabs(currentWindow() ? currentWindow()->part() : 0, currentWindow() ? currentWindow()->currentViewMode() : Kexi::NoViewMode, part, viewMode); // open new tab earlier windowContainer = new KexiWindowContainer(d->mainWidget->tabWidget()); d->setWindowContainerExistsFor(item->identifier(), true); const int tabIndex = d->mainWidget->tabWidget()->addTab( windowContainer, QIcon::fromTheme(part ? part->info()->iconName() : QString()), KexiWindow::windowTitleForItem(*item)); d->mainWidget->tabWidget()->setTabToolTip(tabIndex, KexiPart::fullCaptionForItem(item, part)); QString whatsThisText; if (part) { whatsThisText = xi18nc("@info", "Tab for %1 (%2).", item->captionOrName(), part->info()->name()); } else { whatsThisText = xi18nc("@info", "Tab for %1.", item->captionOrName()); } d->mainWidget->tabWidget()->setTabWhatsThis(tabIndex, whatsThisText); d->mainWidget->tabWidget()->setCurrentWidget(windowContainer); #ifndef KEXI_NO_PENDING_DIALOGS d->addItemToPendingWindows(item, Private::WindowOpeningJob); #endif window = d->prj->openObject(windowContainer, item, viewMode, staticObjectArgs); if (window) { windowContainer->setWindow(window); // update text and icon d->mainWidget->tabWidget()->setTabText( d->mainWidget->tabWidget()->indexOf(windowContainer), window->windowTitle()); d->mainWidget->tabWidget()->setTabIcon( d->mainWidget->tabWidget()->indexOf(windowContainer), window->windowIcon()); } } if (!window || !activateWindow(*window)) { #ifndef KEXI_NO_PENDING_DIALOGS d->removePendingWindow(item->identifier()); #endif d->setWindowContainerExistsFor(item->identifier(), false); d->mainWidget->tabWidget()->removeTab( d->mainWidget->tabWidget()->indexOf(windowContainer)); delete windowContainer; updateCustomPropertyPanelTabs(0, Kexi::NoViewMode); //revert //! @todo add error msg... return 0; } if (viewMode != window->currentViewMode()) invalidateSharedActions(); #ifndef KEXI_NO_PENDING_DIALOGS d->removePendingWindow(window->id()); //perform pending global action that was suspended: if (!d->pendingWindowsExist()) { d->executeActionWhenPendingJobsAreFinished(); } #endif if (window && !alreadyOpened) { // Call switchToViewMode() and propertySetSwitched() again here because // this is the time when then new window is the current one - previous call did nothing. switchToViewMode(*window, window->currentViewMode()); currentWindow()->selectedView()->propertySetSwitched(); } invalidateProjectWideActions(); restoreDesignTabIfNeeded(item->pluginId(), viewMode, previousItemId); activateDesignTabIfNeeded(item->pluginId(), viewMode); QString origTabToActivate; if (prevWindow) { // Save the orig tab for prevWindow that was stored in the restoreDesignTabIfNeeded() call above origTabToActivate = d->tabsToActivateOnShow.value(prevWindow->partItem()->identifier()); } activeWindowChanged(window, prevWindow); if (prevWindow) { // Restore the orig tab d->tabsToActivateOnShow.insert(prevWindow->partItem()->identifier(), origTabToActivate); } return window; } KexiWindow * KexiMainWindow::openObjectFromNavigator(KexiPart::Item* item, Kexi::ViewMode viewMode) { bool openingCancelled; return openObjectFromNavigator(item, viewMode, &openingCancelled); } KexiWindow * KexiMainWindow::openObjectFromNavigator(KexiPart::Item* item, Kexi::ViewMode viewMode, bool *openingCancelled) { Q_ASSERT(openingCancelled); if (!openingAllowed(item, viewMode)) { *openingCancelled = true; return 0; } if (!d->prj || !item) return 0; #ifndef KEXI_NO_PENDING_DIALOGS Private::PendingJobType pendingType; KexiWindow *window = d->openedWindowFor(item, pendingType); if (pendingType != Private::NoJob) { *openingCancelled = true; return 0; } #else KexiWindow *window = openedWindowFor(item); #endif *openingCancelled = false; if (window) { if (activateWindow(*window)) { return window; } } //if DataViewMode is not supported, try Design, then Text mode (currently useful for script part) KexiPart::Part *part = Kexi::partManager().partForPluginId(item->pluginId()); if (!part) return 0; if (viewMode == Kexi::DataViewMode && !(part->info()->supportedViewModes() & Kexi::DataViewMode)) { if (part->info()->supportedViewModes() & Kexi::DesignViewMode) return openObjectFromNavigator(item, Kexi::DesignViewMode, openingCancelled); else if (part->info()->supportedViewModes() & Kexi::TextViewMode) return openObjectFromNavigator(item, Kexi::TextViewMode, openingCancelled); } //do the same as in openObject() return openObject(item, viewMode, openingCancelled); } tristate KexiMainWindow::closeObject(KexiPart::Item* item) { #ifndef KEXI_NO_PENDING_DIALOGS Private::PendingJobType pendingType; KexiWindow *window = d->openedWindowFor(item, pendingType); if (pendingType == Private::WindowClosingJob) return true; else if (pendingType == Private::WindowOpeningJob) return cancelled; #else KexiWindow *window = openedWindowFor(item); #endif if (!window) return cancelled; return closeWindow(window); } bool KexiMainWindow::newObject(KexiPart::Info *info, bool* openingCancelled) { Q_ASSERT(openingCancelled); if (d->userMode) { *openingCancelled = true; return false; } *openingCancelled = false; if (!d->prj || !info) return false; KexiPart::Part *part = Kexi::partManager().part(info); if (!part) return false; KexiPart::Item *it = d->prj->createPartItem(info); if (!it) { //! @todo error return false; } if (!it->neverSaved()) { //only add stored objects to the browser d->navigator->model()->slotAddItem(it); } return openObject(it, Kexi::DesignViewMode, openingCancelled); } tristate KexiMainWindow::removeObject(KexiPart::Item *item, bool dontAsk) { if (d->userMode) return cancelled; if (!d->prj || !item) return false; KexiPart::Part *part = Kexi::partManager().partForPluginId(item->pluginId()); if (!part) return false; if (!dontAsk) { if (KMessageBox::No == KMessageBox::questionYesNo(this, xi18nc("@info 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->propEditor) d->propEditor->editor()->acceptInput(); } void KexiMainWindow::propertySetSwitched(KexiWindow *window, bool force, bool preservePrevSelection, bool sortedProperties, const QByteArray& propertyToSelect) { KexiWindow* _currentWindow = currentWindow(); //qDebug() << "currentWindow(): " // << (_currentWindow ? _currentWindow->windowTitle() : QString("NULL")) // << " window: " << (window ? window->windowTitle() : QString("NULL")); if (_currentWindow && _currentWindow != window) { d->propertySet = 0; //we'll need to move to another prop. set return; } if (d->propEditor) { KPropertySet *newSet = _currentWindow ? _currentWindow->propertySet() : 0; if (!newSet || (force || static_cast(d->propertySet) != newSet)) { d->propertySet = newSet; if (preservePrevSelection || force) { KPropertyEditorView::SetOptions options; if (preservePrevSelection) { options |= KPropertyEditorView::SetOption::PreservePreviousSelection; } if (sortedProperties) { options |= KPropertyEditorView::SetOption::AlphabeticalOrder; } if (propertyToSelect.isEmpty()) { d->propEditor->editor()->changeSet(d->propertySet, options); } else { d->propEditor->editor()->changeSet(d->propertySet, propertyToSelect, options); } } } } } void KexiMainWindow::slotDirtyFlagChanged(KexiWindow* window) { KexiPart::Item *item = window->partItem(); //update text in navigator and app. caption if (!d->userMode) { d->navigator->updateItemName(*item, window->isDirty()); } invalidateActions(); updateAppCaption(); d->mainWidget->tabWidget()->setTabText( d->mainWidget->tabWidget()->indexOf(window->parentWidget()), window->windowTitle()); } void KexiMainWindow::slotTipOfTheDay() { //! @todo } void KexiMainWindow::slotReportBug() { KexiBugReportDialog bugReport(this); bugReport.exec(); } bool KexiMainWindow::userMode() const { return d->userMode; } void KexiMainWindow::setupUserActions() { } void KexiMainWindow::slotToolsImportProject() { if (d->tabbedToolBar) d->tabbedToolBar->hideMainMenu(); showProjectMigrationWizard(QString(), QString()); } void KexiMainWindow::slotToolsImportTables() { if (project()) { QMap args; QDialog *dlg = KexiInternalPart::createModalDialogInstance("org.kexi-project.migration", "importtable", this, 0, &args); if (!dlg) return; //error msg has been shown by KexiInternalPart const int result = dlg->exec(); delete dlg; if (result != QDialog::Accepted) return; QString destinationTableName(args["destinationTableName"]); if (!destinationTableName.isEmpty()) { QString pluginId = "org.kexi-project.table"; bool openingCancelled; KexiMainWindow::openObject(pluginId, destinationTableName, Kexi::DataViewMode, &openingCancelled); } } } void KexiMainWindow::slotToolsCompactDatabase() { KexiProjectData *data = 0; KDbDriver *drv = 0; const bool projectWasOpened = d->prj; if (!d->prj) { //! @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->navigator->selectedPartItem(); if (item) copyItemToClipboardAsDataTable(item); } tristate KexiMainWindow::copyItemToClipboardAsDataTable(KexiPart::Item* item) { if (!item) return false; QMap args; if (!checkForDirtyFlagOnExport(item, &args)) { return false; } args.insert("destinationType", "clipboard"); args.insert("itemId", QString::number(item->identifier())); QDialog *dlg = KexiInternalPart::createModalDialogInstance( "org.kexi-project.importexport.csv", "KexiCSVExportWizard", this, 0, &args); if (!dlg) return false; //error msg has been shown by KexiInternalPart const int result = dlg->exec(); delete dlg; return result == QDialog::Rejected ? tristate(cancelled) : tristate(true); } void KexiMainWindow::slotEditPasteSpecialDataTable() { //! @todo allow data appending (it is not possible now) if (d->userMode) return; QMap args; args.insert("sourceType", "clipboard"); QDialog *dlg = KexiInternalPart::createModalDialogInstance( "org.kexi-project.importexport.csv", "KexiCSVImportDialog", this, 0, &args); if (!dlg) return; //error msg has been shown by KexiInternalPart dlg->exec(); delete dlg; } void KexiMainWindow::slotEditFind() { KexiSearchAndReplaceViewInterface* iface = d->currentViewSupportingSearchAndReplaceInterface(); if (!iface) return; d->updateFindDialogContents(true/*create if does not exist*/); d->findDialog()->setReplaceMode(false); d->findDialog()->show(); d->findDialog()->activateWindow(); d->findDialog()->raise(); } void KexiMainWindow::slotEditFind(bool next) { KexiSearchAndReplaceViewInterface* iface = d->currentViewSupportingSearchAndReplaceInterface(); if (!iface) return; tristate res = iface->find( d->findDialog()->valueToFind(), d->findDialog()->options(), next); if (~res) return; d->findDialog()->updateMessage(true == res); //! @todo result } void KexiMainWindow::slotEditFindNext() { slotEditFind(true); } void KexiMainWindow::slotEditFindPrevious() { slotEditFind(false); } void KexiMainWindow::slotEditReplace() { KexiSearchAndReplaceViewInterface* iface = d->currentViewSupportingSearchAndReplaceInterface(); if (!iface) return; d->updateFindDialogContents(true/*create if does not exist*/); d->findDialog()->setReplaceMode(true); //! @todo slotEditReplace() d->findDialog()->show(); d->findDialog()->activateWindow(); } void KexiMainWindow::slotEditReplaceNext() { slotEditReplace(false); } void KexiMainWindow::slotEditReplace(bool all) { KexiSearchAndReplaceViewInterface* iface = d->currentViewSupportingSearchAndReplaceInterface(); if (!iface) return; //! @todo add question: "Do you want to replace every occurrence of \"%1\" with \"%2\"? //! You won't be able to undo this." + "Do not ask again". tristate res = iface->findNextAndReplace( d->findDialog()->valueToFind(), d->findDialog()->valueToReplaceWith(), d->findDialog()->options(), all); d->findDialog()->updateMessage(true == res); //! @todo result } void KexiMainWindow::slotEditReplaceAll() { slotEditReplace(true); } void KexiMainWindow::highlightObject(const QString& pluginId, const QString& name) { if (!d->prj) return; KexiPart::Item *item = d->prj->itemForPluginId(pluginId, name); if (!item) return; if (d->navigator) { slotSetProjectNavigatorVisible(true); d->navigator->selectItem(*item); } } void KexiMainWindow::slotPartItemSelectedInNavigator(KexiPart::Item* item) { Q_UNUSED(item); } KToolBar *KexiMainWindow::toolBar(const QString& name) const { return d->tabbedToolBar ? d->tabbedToolBar->toolBar(name) : 0; } void KexiMainWindow::appendWidgetToToolbar(const QString& name, QWidget* widget) { if (d->tabbedToolBar) d->tabbedToolBar->appendWidgetToToolbar(name, widget); } void KexiMainWindow::setWidgetVisibleInToolbar(QWidget* widget, bool visible) { if (d->tabbedToolBar) d->tabbedToolBar->setWidgetVisibleInToolbar(widget, visible); } void KexiMainWindow::addToolBarAction(const QString& toolBarName, QAction *action) { if (d->tabbedToolBar) d->tabbedToolBar->addAction(toolBarName, action); } void KexiMainWindow::updatePropertyEditorInfoLabel(const QString& textToDisplayForNullSet) { d->propEditor->updateInfoLabelForPropertySet(d->propertySet, textToDisplayForNullSet); } void KexiMainWindow::addSearchableModel(KexiSearchableModel *model) { if (d->tabbedToolBar) { d->tabbedToolBar->addSearchableModel(model); } } void KexiMainWindow::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(); } } 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 2df64f063..d75b29175 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 6d3d824a1..234718c5d 100644 --- a/src/plugins/reports/KexiDBReportDataSource.cpp +++ b/src/plugins/reports/KexiDBReportDataSource.cpp @@ -1,393 +1,393 @@ /* * 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 { qDebug() << "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 ca8003c1a..b07b099a2 100644 --- a/src/plugins/reports/kexireportdesignview.cpp +++ b/src/plugins/reports/kexireportdesignview.cpp @@ -1,246 +1,246 @@ /* * 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); layout()->addWidget(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(); } void KexiReportDesignView::slotDesignerPropertySetChanged() { 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 8a7cc6809..d8b9ae651 100644 --- a/src/plugins/reports/kexireportpart.cpp +++ b/src/plugins/reports/kexireportpart.cpp @@ -1,339 +1,339 @@ /* * 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 #include "kexireportview.h" #include "kexireportdesignview.h" #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::setupCustomPropertyPanelTabs(QTabWidget *tab) { if (!d->sourceSelector) { d->sourceSelector = new KexiSourceSelector(KexiMainWindowIface::global()->project(), tab); } tab->addTab(d->sourceSelector, koIcon("server-database"), QString()); tab->setTabToolTip(tab->indexOf(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 bac95aba7..24a5c4074 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 setupCustomPropertyPanelTabs(QTabWidget *tab) 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 aaee5c4f6..ecde702d3 100644 --- a/src/plugins/reports/kexireportview.cpp +++ b/src/plugins/reports/kexireportview.cpp @@ -1,476 +1,476 @@ /* * 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 //! @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 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); layout()->addWidget(m_reportView); #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 0d1477c3e..e44cd8c2f 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 KexiPropertyPaneViewBase 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/navigator/KexiProjectNavigator.cpp b/src/widget/navigator/KexiProjectNavigator.cpp index 8b84f6918..409706645 100644 --- a/src/widget/navigator/KexiProjectNavigator.cpp +++ b/src/widget/navigator/KexiProjectNavigator.cpp @@ -1,763 +1,763 @@ /* 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", koIcon("document-properties"), xi18n("&Design"), xi18n("Design object"), xi18n("Starts designing of the object selected in the list."), SLOT(slotDesignObject())); d->editTextAction = addAction("editText_object", QIcon(), xi18n("Design in &Text View"), 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"); } if (m_actionCollection->action("editText_object") && m_actionCollection->action("editText_object")->isEnabled() && (partInfo.supportedViewModes() & Kexi::TextViewMode)) { addAction("editText_object"); } 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 1bc314f7c..aa3fe5614 100644 --- a/src/widget/undo/kundo2magicstring.h +++ b/src/widget/undo/kundo2magicstring.h @@ -1,318 +1,318 @@ /* * 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 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 37a937814..6757d9a26 100644 --- a/src/widget/undo/kundo2stack.cpp +++ b/src/widget/undo/kundo2stack.cpp @@ -1,1435 +1,1435 @@ /* * 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: \list \i indexChanged() and cleanChanged() are not emitted, \i canUndo() and canRedo() return false, \i calling undo() or redo() has no effect, \i the undo/redo actions are disabled. \endlist 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