diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index dd629d4f..0506c9fb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,253 +1,259 @@ set(PLAN_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/interfaces) set(PLANPLUGIN_INCLUDES ${CMAKE_CURRENT_SOURCE_DIR}/libs/plugin ) set(PLANKUNDO2_INCLUDES ${CMAKE_CURRENT_SOURCE_DIR}/libs/kundo2 ${CMAKE_CURRENT_BINARY_DIR}/libs/kundo2 ) set(PLANSTORE_INCLUDES ${CMAKE_CURRENT_SOURCE_DIR}/libs/store ${CMAKE_CURRENT_BINARY_DIR}/libs/store ) set(PLANODF_INCLUDES ${CMAKE_CURRENT_SOURCE_DIR}/libs/odf ${CMAKE_CURRENT_BINARY_DIR}/libs/odf ${PLANSTORE_INCLUDES} ) set(PLANWIDGETS_INCLUDES ${CMAKE_CURRENT_SOURCE_DIR}/libs/widgetutils ${CMAKE_CURRENT_BINARY_DIR}/libs/widgetutils ${CMAKE_CURRENT_SOURCE_DIR}/libs/widgets ${CMAKE_CURRENT_BINARY_DIR}/libs/widgets ) set(PLANMAIN_INCLUDES ${PLANWIDGETS_INCLUDES} ${PLANODF_INCLUDES} ${PLANKUNDO2_INCLUDES} ${CMAKE_CURRENT_SOURCE_DIR}/libs/main ${CMAKE_CURRENT_BINARY_DIR}/libs/main ) set(PLANKERNEL_INCLUDES ${CMAKE_CURRENT_SOURCE_DIR}/libs/kernel ${CMAKE_CURRENT_BINARY_DIR}/libs/kernel ) set(PLANMODELS_INCLUDES ${CMAKE_CURRENT_SOURCE_DIR}/libs/models ${CMAKE_CURRENT_BINARY_DIR}/libs/models ) set(PLANUI_INCLUDES ${CMAKE_CURRENT_SOURCE_DIR}/libs/ui ${CMAKE_CURRENT_BINARY_DIR}/libs/ui ) set(PLAN_INCLUDES ${CMAKE_CURRENT_BINARY_DIR} ${PLANKERNEL_INCLUDES} ${PLANMODELS_INCLUDES} ${PLANUI_INCLUDES} ${PLANMAIN_INCLUDES} ) # For odf set(RNG_SOURCE_DIR ${PROJECT_SOURCE_DIR}/devtools/scripts) if(KF5Holidays_FOUND) add_definitions(-DHAVE_KHOLIDAYS) endif() if (KF5AkonadiContact_FOUND) # disable for now: there is a bug # it only works if you use kde contacts (of course) but many use other stuff, so gets dissapointed add_definitions(-DPLAN_KDEPIMLIBS_FOUND) message(WARNING "AkonadiContacs available, but function is disabled due to Bug 311940") endif () if (PLANCHARTDEBUG) add_definitions(-DPLAN_CHART_DEBUG) endif () #add_subdirectory(interfaces) add_subdirectory(servicetypes) add_subdirectory( templates ) add_subdirectory( pics ) add_subdirectory( toolbar ) add_subdirectory( plugins ) add_subdirectory( libs ) if(BUILD_TESTING) add_subdirectory( tests ) endif() add_subdirectory( workpackage ) include_directories(${PLAN_INCLUDES}) add_definitions(-DTRANSLATION_DOMAIN=\"calligraplan\") ########### KPlato private library ############### set(planprivate_LIB_SRCS + welcome/WelcomeView.cpp + kptviewlistdocker.cpp kptviewlist.cpp kptviewlistdialog.cpp kptschedulesdocker.cpp kptconfig.cpp config/ConfigWorkVacationPanel.cpp config/ConfigProjectPanel.cpp config/kpttaskdefaultpanel.cpp config/kptworkpackageconfigpanel.cpp config/kptcolorsconfigpanel.cpp config/ConfigDocumentationPanel.cpp config/ConfigTaskModulesPanel.cpp + config/ConfigProjectTemplatesPanel.cpp config/ConfigDialog.cpp kptcontext.cpp kptfactory.cpp kptpart.cpp kptmaindocument.cpp kptview.cpp # KPtViewAdaptor.cpp kptprintingcontrolprivate.cpp kptschedulerpluginloader.cpp kptbuiltinschedulerplugin.cpp kptconfigskeleton.cpp kptinsertfiledlg.cpp kptloadsharedprojectsdialog.cpp about/aboutpage.cpp KPlatoXmlLoader.cpp ) ki18n_wrap_ui(planprivate_LIB_SRCS + welcome/WelcomeView.ui + kptviewlistaddview.ui kptviewlisteditview.ui kptviewlisteditcategory.ui config/ConfigWorkVacationPanel.ui config/ConfigProjectPanel.ui config/kptconfigtaskpanelbase.ui config/kptworkpackageconfigpanel.ui config/kptcolorsconfigpanel.ui config/ConfigDocumentationPanel.ui config/ConfigTaskModulesPanel.ui + config/ConfigProjectTemplatesPanel.ui kptinsertfilepanel.ui ) kconfig_add_kcfg_files(plansettings_SRCS calligraplansettings.kcfgc) add_library(planprivate SHARED ${planprivate_LIB_SRCS} ${plansettings_SRCS} ) generate_export_header(planprivate BASE_NAME plan) target_link_libraries(planprivate PUBLIC plankernel planmodels planui planmain PRIVATE planplugin KF5::IconThemes #KF5::KHtml ) if(KF5AkonadiContact_FOUND) target_link_libraries(planprivate PRIVATE KF5::AkonadiContact) endif() set_target_properties(planprivate PROPERTIES VERSION ${GENERIC_PLAN_LIB_VERSION} SOVERSION ${GENERIC_PLAN_LIB_SOVERSION} ) install(TARGETS planprivate ${INSTALL_TARGETS_DEFAULT_ARGS}) ########### KPlato part ############### set(planpart_PART_SRCS kptfactoryinit.cpp ) add_library(calligraplanpart MODULE ${planpart_PART_SRCS}) #calligraplan_part_desktop_to_json(calligraplanpart planpart.desktop) if(${KF5_VERSION} VERSION_LESS "5.16.0") kcoreaddons_desktop_to_json(calligraplanpart planpart.desktop) else() kcoreaddons_desktop_to_json(calligraplanpart planpart.desktop # SERVICE_TYPES ${PLAN_SOURCE_DIR}/servicetypes/calligraplan_part.desktop ) endif() target_link_libraries(calligraplanpart PUBLIC KF5::Parts KF5::CoreAddons PRIVATE planprivate) install(TARGETS calligraplanpart DESTINATION ${PLUGIN_INSTALL_DIR}/calligraplan/parts) ########### KPlato executable ############### set(calligraplan_KDEINIT_SRCS main.cpp ) file(GLOB ICONS_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/pics/*-apps-calligraplan.png") ecm_add_app_icon(kdeinit_app_ICONS_SRCS ICONS ${ICONS_SRCS}) if(WIN32) set(_resourcefile "${CMAKE_CURRENT_BINARY_DIR}/kdeinit_app_ICONS_SRCS.rc") endif() kf5_add_kdeinit_executable( calligraplan ${calligraplan_KDEINIT_SRCS}) if (APPLE) set_target_properties(calligraplan PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.template) set_target_properties(calligraplan PROPERTIES MACOSX_BUNDLE_GUI_IDENTIFIER "org.calligra.plan") set_target_properties(calligraplan PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "Plan") install( FILES ${CMAKE_CURRENT_BINARY_DIR}/calligraplan_KDEINIT_SRCS.icns DESTINATION ${BUNDLE_INSTALL_DIR}/calligraplan.app/Contents/Resources) endif () target_link_libraries(kdeinit_calligraplan planmain) install(TARGETS kdeinit_calligraplan ${INSTALL_TARGETS_DEFAULT_ARGS}) target_link_libraries(calligraplan kdeinit_calligraplan planmain) install(TARGETS calligraplan ${INSTALL_TARGETS_DEFAULT_ARGS}) ########### install files ############### install( FILES calligraplan.rc calligraplan_readonly.rc DESTINATION ${KXMLGUI_INSTALL_DIR}/calligraplan) install( PROGRAMS org.kde.calligraplan.desktop DESTINATION ${XDG_APPS_INSTALL_DIR}) install( FILES calligraplanrc DESTINATION ${CONFIG_INSTALL_DIR}) install(FILES calligraplansettings.kcfg DESTINATION ${KCFG_INSTALL_DIR}) install(FILES org.kde.calligraplan.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR}) # TODO: with the new embedded JSON data for plugins there is no schema ATM to define extended properties # plan_viewplugin.desktop install(FILES about/top-left-plan.png about/main.html about/intro.html about/tips.html about/tutorial.html about/plan.css DESTINATION ${DATA_INSTALL_DIR}/calligraplan/about ) configure_file(config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config.h ) #add_custom_target(apidox doc/api/gendocs.pl WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) diff --git a/src/calligraplansettings.kcfg b/src/calligraplansettings.kcfg index be952402..ff202967 100644 --- a/src/calligraplansettings.kcfg +++ b/src/calligraplansettings.kcfg @@ -1,381 +1,387 @@ 1760 176 40 8 true NoneExists true InWeekCalendar Default true 08:00 16:00 true 08:00 16:00 true 08:00 16:00 true 08:00 16:00 true 08:00 16:00 false 08:00 16:00 false 08:00 16:00 false AsSoonAsPossible CurrentdateTime CurrentdateTime Effort Hour 8.0 -75 100 Linear false #0000ff #0000ff #0000ff #0000ff #0000ff #00ff00 #ff0000 #A0A0A0 #ffff00 #0000ff #ff0000 #A0A0A0 #ffff00 EnumUnit::Minute EnumUnit::Month https://userbase.kde.org/Plan https://userbase.kde.org/Plan/Manual true + + + + + + diff --git a/src/config/ConfigDialog.cpp b/src/config/ConfigDialog.cpp index cc3a188e..cd4a7cb5 100644 --- a/src/config/ConfigDialog.cpp +++ b/src/config/ConfigDialog.cpp @@ -1,81 +1,90 @@ /* This file is part of the KDE project * Copyright (C) 2019 Dag Andersen * * 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 "ConfigDialog.h" #include "ConfigProjectPanel.h" #include "ConfigWorkVacationPanel.h" #include "kpttaskdefaultpanel.h" #include "kptworkpackageconfigpanel.h" #include "kptcolorsconfigpanel.h" #include "ConfigDocumentationPanel.h" #include "ConfigTaskModulesPanel.h" +#include "ConfigProjectTemplatesPanel.h" #include #include #include #include #include #include #include #include using namespace KPlato; ConfigDialog::ConfigDialog(QWidget *parent, const QString& name, KConfigSkeleton *config ) : KConfigDialog( parent, name, config ) { m_pages << addPage(new ConfigProjectPanel(), i18n("Project Defaults"), koIconName("calligraplan") ); m_pages << addPage(new ConfigWorkVacationPanel(), i18n("Work & Vacation"), koIconName("view-calendar") ); m_pages << addPage(new TaskDefaultPanel(), i18n("Task Defaults"), koIconName("view-task") ); m_pages << addPage(new ColorsConfigPanel(), i18n("Task Colors"), koIconName("fill-color") ); ConfigTaskModulesPanel *page = new ConfigTaskModulesPanel(); m_pages << addPage(page, i18n("Task Modules"), koIconName("calligraplanwork") ); connect(page, &ConfigTaskModulesPanel::settingsChanged, this, &ConfigDialog::updateButtons); connect(this, &ConfigDialog::updateWidgetsSettings, page, &ConfigTaskModulesPanel::updateSettings); connect(this, &ConfigDialog::updateWidgetsData, page, &ConfigTaskModulesPanel::updateWidgets); m_pages << addPage(new WorkPackageConfigPanel(), i18n("Work Package"), koIconName("calligraplanwork") ); m_pages << addPage(new ConfigDocumentationPanel(), i18n("Documentation"), koIconName("documents") ); + + ConfigProjectTemplatesPanel *p = new ConfigProjectTemplatesPanel(); + m_pages << addPage(p, i18n("Project Templates"), koIconName("calligraplan")); + connect(p, &ConfigProjectTemplatesPanel::settingsChanged, this, &ConfigDialog::updateButtons); + connect(this, &ConfigDialog::updateWidgetsSettings, p, &ConfigProjectTemplatesPanel::updateSettings); + connect(this, &ConfigDialog::updateWidgetsData, p, &ConfigProjectTemplatesPanel::updateWidgets); } void ConfigDialog::updateSettings() { emit updateWidgetsSettings(); new Help(KPlatoSettings::contextPath(), KPlatoSettings::contextLanguage()); + + KPlatoSettings::self()->save(); } void ConfigDialog::updateWidgets() { emit updateWidgetsData(); } bool ConfigDialog::hasChanged() { QWidget *w = currentPage()->widget()->findChild("ConfigWidget"); return w ? w->property("hasChanged").toBool() : false; } void ConfigDialog::showHelp() { Help::invoke("Configure_Plan"); } diff --git a/src/config/ConfigProjectTemplatesPanel.cpp b/src/config/ConfigProjectTemplatesPanel.cpp new file mode 100644 index 00000000..12e18309 --- /dev/null +++ b/src/config/ConfigProjectTemplatesPanel.cpp @@ -0,0 +1,76 @@ +/* This file is part of the KDE project + * Copyright (C) 2019 Dag Andersen + * + * 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. + */ + +// clazy:excludeall=qstring-arg +#include "ConfigProjectTemplatesPanel.h" + +#include "calligraplansettings.h" + +#include +#include + +using namespace KPlato; + +ConfigProjectTemplatesPanel::ConfigProjectTemplatesPanel(QWidget *parent) +{ + setObjectName("ConfigWidget"); + ui.setupUi(this); + model.setStringList(KPlatoSettings::projectTemplatePaths()); + ui.projectTemplatesView->setModel(&model); + + connect(ui.insertTemplatePath, &QToolButton::clicked, this, &ConfigProjectTemplatesPanel::slotInsertClicked); + connect(ui.removeTemplatePath, &QToolButton::clicked, this, &ConfigProjectTemplatesPanel::slotRemoveClicked); + + connect(&model, &QStringListModel::dataChanged, this, &ConfigProjectTemplatesPanel::settingsChanged); +} + +bool ConfigProjectTemplatesPanel::hasChanged() const +{ + bool changed = KPlatoSettings::projectTemplatePaths() != model.stringList(); + return changed; +} + +void ConfigProjectTemplatesPanel::updateSettings() +{ + KPlatoSettings::setProjectTemplatePaths(model.stringList()); +} + +void ConfigProjectTemplatesPanel::updateWidgets() +{ + model.setStringList(KPlatoSettings::projectTemplatePaths()); +} + +void ConfigProjectTemplatesPanel::slotInsertClicked() +{ + QString dirName = QFileDialog::getExistingDirectory(this, i18n("Project Templates Path")); + if (!dirName.isEmpty()) { + model.setStringList(model.stringList() << dirName); + } + emit settingsChanged(); +} + +void ConfigProjectTemplatesPanel::slotRemoveClicked() +{ + QList lst = ui.projectTemplatesView->selectionModel()->selectedRows(); + for (const QModelIndex &idx : lst) { + model.removeRow(idx.row(), idx.parent()); + } + emit settingsChanged(); +} + diff --git a/src/config/ConfigProjectTemplatesPanel.h b/src/config/ConfigProjectTemplatesPanel.h new file mode 100644 index 00000000..737071a3 --- /dev/null +++ b/src/config/ConfigProjectTemplatesPanel.h @@ -0,0 +1,61 @@ +/* This file is part of the KDE project + * Copyright (C) 2019 Dag Andersen + * + * 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 CONFIGPROJECTTEMPLATESPANEL_H +#define CONFIGPROJECTTEMPLATESPANEL_H + +#include "plan_export.h" + +#include "ui_ConfigProjectTemplatesPanel.h" + +#include +#include + +namespace KPlato +{ + + +class PLAN_EXPORT ConfigProjectTemplatesPanel : public QWidget +{ + Q_OBJECT + Q_PROPERTY(bool hasChanged READ hasChanged) + +public: + explicit ConfigProjectTemplatesPanel( QWidget *parent=nullptr ); + + QStringListModel model; + Ui::ConfigProjectTemplatesPanel ui; + + bool hasChanged() const; + +public Q_SLOTS: + void updateSettings(); + void updateWidgets(); + +Q_SIGNALS: + void settingsChanged(); + +private Q_SLOTS: + void slotInsertClicked(); + void slotRemoveClicked(); +}; + +} //KPlato namespace + +#endif diff --git a/src/config/ConfigProjectTemplatesPanel.ui b/src/config/ConfigProjectTemplatesPanel.ui new file mode 100644 index 00000000..6cb29ff8 --- /dev/null +++ b/src/config/ConfigProjectTemplatesPanel.ui @@ -0,0 +1,66 @@ + + + KPlato::ConfigProjectTemplatesPanel + + + + 0 + 0 + 400 + 300 + + + + + + + Insert + + + + libs/uilibs/ui + + + + + + + Qt::Vertical + + + + 20 + 181 + + + + + + + + Remove + + + + libs/uilibs/ui + + + + + + + false + + + false + + + false + + + + + + + + diff --git a/src/kptconfig.cpp b/src/kptconfig.cpp index 7aeba5de..9d118ebf 100644 --- a/src/kptconfig.cpp +++ b/src/kptconfig.cpp @@ -1,362 +1,367 @@ /* This file is part of the KDE project Copyright (C) 2004, 2007 Dag Andersen Copyright (C) 2011, 2012 Dag Andersen Copyright (C) 2019 Dag Andersen 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. */ // clazy:excludeall=qstring-arg #include "kptconfig.h" #include "calligraplansettings.h" #include "kptconfigskeleton.h" #include "kptfactory.h" #include "kptproject.h" #include "kptdebug.h" #include namespace KPlato { Config::Config() : ConfigBase() { debugPlan<<"Leader:"<save(); } bool Config::isWorkingday(int day) const { switch (day) { case Qt::Monday: return KPlatoSettings::monday(); break; case Qt::Tuesday: return KPlatoSettings::tuesday(); break; case Qt::Wednesday: return KPlatoSettings::wednesday(); break; case Qt::Thursday: return KPlatoSettings::thursday(); break; case Qt::Friday: return KPlatoSettings::friday(); break; case Qt::Saturday: return KPlatoSettings::saturday(); break; case Qt::Sunday: return KPlatoSettings::sunday(); break; default: break; }; return false; } QTime Config::dayStartTime(int day) const { switch (day) { case Qt::Monday: return QTime::fromString(KPlatoSettings::mondayStart()); break; case Qt::Tuesday: return QTime::fromString(KPlatoSettings::tuesdayStart()); break; case Qt::Wednesday: return QTime::fromString(KPlatoSettings::wednesdayStart()); break; case Qt::Thursday: return QTime::fromString(KPlatoSettings::thursdayStart()); break; case Qt::Friday: return QTime::fromString(KPlatoSettings::fridayStart()); break; case Qt::Saturday: return QTime::fromString(KPlatoSettings::saturdayStart()); break; case Qt::Sunday: return QTime::fromString(KPlatoSettings::sundayStart()); break; default: break; }; return QTime(); } int Config::dayLength(int day) const { QTime start = dayStartTime(day); QTime end; int value = 0; switch (day) { case Qt::Monday: end = QTime::fromString(KPlatoSettings::mondayEnd()); break; case Qt::Tuesday: end = QTime::fromString(KPlatoSettings::tuesdayEnd()); break; case Qt::Wednesday: end = QTime::fromString(KPlatoSettings::wednesdayEnd()); break; case Qt::Thursday: end = QTime::fromString(KPlatoSettings::thursdayEnd()); break; case Qt::Friday: end = QTime::fromString(KPlatoSettings::fridayEnd()); break; case Qt::Saturday: end = QTime::fromString(KPlatoSettings::saturdayEnd()); break; case Qt::Sunday: end = QTime::fromString(KPlatoSettings::sundayEnd()); break; default: break; }; value = start.msecsTo(end); if (value < 0) { value = (24*60*60*1000) + value; } else if (value == 0 && start == QTime(0, 0)) { value = 24*60*60*1000; } return value; } void Config::setDefaultValues( Project &project ) const { project.setLeader( KPlatoSettings::manager() ); project.setUseSharedResources( KPlatoSettings::useSharedResources() ); project.setSharedResourcesFile( KPlatoSettings::sharedResourcesFile() ); project.setSharedProjectsUrl( QUrl(KPlatoSettings::sharedProjectsPlace()) ); project.setDescription( KPlatoSettings::projectDescription() ); StandardWorktime *v = project.standardWorktime(); Q_ASSERT(v); if (v) { v->setYear(KPlatoSettings::hoursPrYear()); v->setMonth(KPlatoSettings::hoursPrMonth()); v->setWeek(KPlatoSettings::hoursPrWeek()); v->setDay(KPlatoSettings::hoursPrDay()); } Project::WorkPackageInfo wpi; wpi.checkForWorkPackages = KPlatoSettings::checkForWorkPackages(); wpi.retrieveUrl = KPlatoSettings::retrieveUrl(); wpi.deleteAfterRetrieval = KPlatoSettings::deleteFile(); wpi.archiveAfterRetrieval = !KPlatoSettings::deleteFile(); wpi.archiveUrl = KPlatoSettings::saveUrl(); #if 0 // not used atm wpi.publishUrl = KPlatoSettings::publishUrl(); #endif project.setWorkPackageInfo(wpi); project.setUseLocalTaskModules(useLocalTaskModules()); QList urls; for (const QString &s : taskModulePaths()) { urls << QUrl::fromUserInput(s); } project.setTaskModules(urls); } void Config::setDefaultValues( Task &task ) { task.setLeader( KPlatoSettings::leader() ); task.setDescription( KPlatoSettings::description() ); task.setConstraint( (Node::ConstraintType) KPlatoSettings::constraintType() ); // avoid problems with start <= end & end >= start task.setConstraintStartTime( DateTime() ); task.setConstraintEndTime( DateTime() ); switch ( KPlatoSettings::startTimeUsage() ) { case KPlatoSettings::EnumStartTimeUsage::CurrentdateTime: task.setConstraintStartTime( DateTime( QDateTime::currentDateTime() ) ); break; case KPlatoSettings::EnumStartTimeUsage::CurrentDate: task.setConstraintStartTime( DateTime( QDate::currentDate(), KPlatoSettings::constraintStartTime().time() ) ); break; case KPlatoSettings::EnumStartTimeUsage::SpecifiedDateTime: //fall through default: task.setConstraintStartTime( DateTime( KPlatoSettings::constraintStartTime() ) ); break; } switch ( KPlatoSettings::endTimeUsage() ) { case KPlatoSettings::EnumEndTimeUsage::CurrentdateTime: task.setConstraintEndTime( DateTime( QDateTime::currentDateTime() ) ); break; case KPlatoSettings::EnumEndTimeUsage::CurrentDate: task.setConstraintEndTime( DateTime( QDate::currentDate(), KPlatoSettings::constraintEndTime().time() ) ); break; case KPlatoSettings::EnumEndTimeUsage::SpecifiedDateTime: //fall through default: task.setConstraintEndTime( DateTime( KPlatoSettings::constraintEndTime() ) ); break; } task.estimate()->setType( (Estimate::Type) KPlatoSettings::estimateType() ); task.estimate()->setUnit( (Duration::Unit) KPlatoSettings::unit() ); task.estimate()->setExpectedEstimate( KPlatoSettings::expectedEstimate() ); task.estimate()->setPessimisticRatio( KPlatoSettings::pessimisticRatio() ); task.estimate()->setOptimisticRatio( KPlatoSettings::optimisticRatio() ); } int Config::minimumDurationUnit() const { return KPlatoSettings::minimumDurationUnit(); } int Config::maximumDurationUnit() const { return KPlatoSettings::maximumDurationUnit(); } QBrush Config::summaryTaskDefaultColor() const { QColor c = KPlatoSettings::summaryTaskDefaultColor(); if ( KPlatoSettings::colorGradientType() == KPlatoSettings::EnumColorGradientType::Linear ) { return gradientBrush( c ); } return c; } bool Config::summaryTaskLevelColorsEnabled() const { return KPlatoSettings::summaryTaskLevelColorsEnabled(); } QBrush Config::summaryTaskLevelColor_1() const { QColor c = KPlatoSettings::summaryTaskLevelColor_1(); if ( KPlatoSettings::colorGradientType() == KPlatoSettings::EnumColorGradientType::Linear ) { return gradientBrush( c ); } return c; } QBrush Config::summaryTaskLevelColor_2() const { QColor c = KPlatoSettings::summaryTaskLevelColor_2(); if ( KPlatoSettings::colorGradientType() == KPlatoSettings::EnumColorGradientType::Linear ) { return gradientBrush( c ); } return c; } QBrush Config::summaryTaskLevelColor_3() const { QColor c = KPlatoSettings::summaryTaskLevelColor_3(); if ( KPlatoSettings::colorGradientType() == KPlatoSettings::EnumColorGradientType::Linear ) { return gradientBrush( c ); } return c; } QBrush Config::summaryTaskLevelColor_4() const { QColor c = KPlatoSettings::summaryTaskLevelColor_4(); if ( KPlatoSettings::colorGradientType() == KPlatoSettings::EnumColorGradientType::Linear ) { return gradientBrush( c ); } return c; } QBrush Config::taskNormalColor() const { QColor c = KPlatoSettings::taskNormalColor(); if ( KPlatoSettings::colorGradientType() == KPlatoSettings::EnumColorGradientType::Linear ) { return gradientBrush( c ); } return c; } QBrush Config::taskErrorColor() const { QColor c = KPlatoSettings::taskErrorColor(); if ( KPlatoSettings::colorGradientType() == KPlatoSettings::EnumColorGradientType::Linear ) { return gradientBrush( c ); } return c; } QBrush Config::taskCriticalColor() const { QColor c = KPlatoSettings::taskCriticalColor(); if ( KPlatoSettings::colorGradientType() == KPlatoSettings::EnumColorGradientType::Linear ) { return gradientBrush( c ); } return c; } QBrush Config::taskFinishedColor() const { QColor c = KPlatoSettings::taskFinishedColor(); if ( KPlatoSettings::colorGradientType() == KPlatoSettings::EnumColorGradientType::Linear ) { return gradientBrush( c ); } return c; } QBrush Config::milestoneNormalColor() const { QColor c = KPlatoSettings::milestoneNormalColor(); if ( KPlatoSettings::colorGradientType() == KPlatoSettings::EnumColorGradientType::Linear ) { return gradientBrush( c ); } return c; } QBrush Config::milestoneErrorColor() const { QColor c = KPlatoSettings::milestoneErrorColor(); if ( KPlatoSettings::colorGradientType() == KPlatoSettings::EnumColorGradientType::Linear ) { return gradientBrush( c ); } return c; } QBrush Config::milestoneCriticalColor() const { QColor c = KPlatoSettings::milestoneCriticalColor(); if ( KPlatoSettings::colorGradientType() == KPlatoSettings::EnumColorGradientType::Linear ) { return gradientBrush( c ); } return c; } QBrush Config::milestoneFinishedColor() const { QColor c = KPlatoSettings::milestoneFinishedColor(); if ( KPlatoSettings::colorGradientType() == KPlatoSettings::EnumColorGradientType::Linear ) { return gradientBrush( c ); } return c; } QString Config::documentationPath() const { return KPlatoSettings::documentationPath(); } QString Config::contextPath() const { return KPlatoSettings::contextPath(); } QString Config::contextLanguage() const { return KPlatoSettings::contextLanguage(); } bool Config::useLocalTaskModules() const { return KPlatoSettings::useLocalTaskModules(); } QStringList Config::taskModulePaths() const { return KPlatoSettings::taskModulePaths(); } +QStringList Config::projectTemplatePaths() const +{ + return KPlatoSettings::projectTemplatePaths(); +} + } //KPlato namespace diff --git a/src/kptconfig.h b/src/kptconfig.h index 6422b446..14f8c729 100644 --- a/src/kptconfig.h +++ b/src/kptconfig.h @@ -1,78 +1,80 @@ /* This file is part of the KDE project Copyright (C) 2004, 2007 Dag Andersen Copyright (C) 2019 Dag Andersen 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 KPTCONFIG_H #define KPTCONFIG_H #include "kptconfigbase.h" namespace KPlato { class Config : public ConfigBase { Q_OBJECT public: Config(); ~Config() override; void readConfig(); void saveSettings(); bool isWorkingday(int day) const; QTime dayStartTime(int day) const; int dayLength(int day) const; void setDefaultValues( Project &project ) const override; void setDefaultValues( Task &task ) override; int minimumDurationUnit() const override; int maximumDurationUnit() const override; bool summaryTaskLevelColorsEnabled() const override; QBrush summaryTaskDefaultColor() const override; QBrush summaryTaskLevelColor_1() const override; QBrush summaryTaskLevelColor_2() const override; QBrush summaryTaskLevelColor_3() const override; QBrush summaryTaskLevelColor_4() const override; QBrush taskNormalColor() const override; QBrush taskErrorColor() const override; QBrush taskCriticalColor() const override; QBrush taskFinishedColor() const override; QBrush milestoneNormalColor() const override; QBrush milestoneErrorColor() const override; QBrush milestoneCriticalColor() const override; QBrush milestoneFinishedColor() const override; QString documentationPath() const override; QString contextPath() const override; QString contextLanguage() const override; bool useLocalTaskModules() const override; QStringList taskModulePaths() const override; + + QStringList projectTemplatePaths() const override; }; } //KPlato namespace #endif // CONFIG_H diff --git a/src/kptmaindocument.cpp b/src/kptmaindocument.cpp index 49453b44..11ad1ff2 100644 --- a/src/kptmaindocument.cpp +++ b/src/kptmaindocument.cpp @@ -1,1619 +1,1625 @@ /* This file is part of the KDE project * Copyright (C) 1998, 1999, 2000 Torben Weis * Copyright (C) 2004, 2010, 2012 Dag Andersen * Copyright (C) 2006 Raphael Langerhorst * Copyright (C) 2007 Thorsten Zachmann * Copyright (C) 2019 Dag Andersen * * 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. */ // clazy:excludeall=qstring-arg #include "kptmaindocument.h" #include "kptpart.h" #include "kptview.h" #include "kptfactory.h" #include "kptproject.h" #include "kptlocale.h" #include "kptresource.h" #include "kptcontext.h" #include "kptschedulerpluginloader.h" #include "kptschedulerplugin.h" #include "kptbuiltinschedulerplugin.h" #include "kptschedule.h" #include "kptcommand.h" #include "calligraplansettings.h" #include "kpttask.h" #include "KPlatoXmlLoader.h" #include "XmlSaveContext.h" #include "kptpackage.h" #include "kptdebug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_KHOLIDAYS #include #endif namespace KPlato { MainDocument::MainDocument(KoPart *part) : KoDocument(part), m_project( 0 ), m_context( 0 ), m_xmlLoader(), m_loadingTemplate( false ), m_loadingSharedResourcesTemplate( false ), m_viewlistModified( false ), m_checkingForWorkPackages( false ), m_loadingSharedProject(false), m_skipSharedProjects(false), m_isTaskModule(false), m_calculationCommand(nullptr), m_currentCalculationManager(nullptr), m_nextCalculationManager(nullptr), m_taskModulesWatch(nullptr) { Q_ASSERT(part); setAlwaysAllowSaving(true); m_config.setReadWrite( isReadWrite() ); loadSchedulerPlugins(); setProject( new Project( m_config ) ); // after config & plugins are loaded m_project->setId( m_project->uniqueNodeId() ); m_project->registerNodeId( m_project ); // register myself connect(this, &MainDocument::insertSharedProject, this, &MainDocument::slotInsertSharedProject); } MainDocument::~MainDocument() { qDeleteAll( m_schedulerPlugins ); if ( m_project ) { m_project->deref(); // deletes if last user } qDeleteAll( m_mergedPackages ); delete m_context; delete m_calculationCommand; } +void MainDocument::initEmpty() +{ + KoDocument::initEmpty(); + setProject(new Project(m_config)); +} + void MainDocument::slotNodeChanged(Node *node, int property) { switch (property) { case Node::TypeProperty: case Node::ResourceRequestProperty: case Node::ConstraintTypeProperty: case Node::StartConstraintProperty: case Node::EndConstraintProperty: case Node::PriorityProperty: case Node::EstimateProperty: case Node::EstimateRiskProperty: setCalculationNeeded(); break; case Node::EstimateOptimisticProperty: case Node::EstimatePessimisticProperty: if (node->estimate()->risktype() != Estimate::Risk_None) { setCalculationNeeded(); } break; default: break; } } void MainDocument::slotScheduleManagerChanged(ScheduleManager *sm, int property) { if (sm->schedulingMode() == ScheduleManager::AutoMode) { switch (property) { case ScheduleManager::DirectionProperty: case ScheduleManager::OverbookProperty: case ScheduleManager::DistributionProperty: case ScheduleManager::SchedulingModeProperty: case ScheduleManager::GranularityProperty: setCalculationNeeded(); break; default: break; } } } void MainDocument::setCalculationNeeded() { for (ScheduleManager *sm : m_project->allScheduleManagers()) { if (sm->isBaselined()) { continue; } if (sm->schedulingMode() == ScheduleManager::AutoMode) { m_nextCalculationManager = sm; break; } } if (!m_currentCalculationManager) { m_currentCalculationManager = m_nextCalculationManager; m_nextCalculationManager = nullptr; QTimer::singleShot(0, this, &MainDocument::slotStartCalculation); } } void MainDocument::slotStartCalculation() { if (m_currentCalculationManager) { m_calculationCommand = new CalculateScheduleCmd(*m_project, m_currentCalculationManager); m_calculationCommand->redo(); } } void MainDocument::slotCalculationFinished(Project *p, ScheduleManager *sm) { if (sm != m_currentCalculationManager) { return; } delete m_calculationCommand; m_calculationCommand = nullptr; m_currentCalculationManager = m_nextCalculationManager; m_nextCalculationManager = nullptr; if (m_currentCalculationManager) { QTimer::singleShot(0, this, &MainDocument::slotStartCalculation); } } void MainDocument::setReadWrite( bool rw ) { m_config.setReadWrite( rw ); KoDocument::setReadWrite( rw ); } void MainDocument::loadSchedulerPlugins() { // Add built-in scheduler addSchedulerPlugin( "Built-in", new BuiltinSchedulerPlugin( this ) ); // Add all real scheduler plugins SchedulerPluginLoader *loader = new SchedulerPluginLoader(this); connect(loader, &SchedulerPluginLoader::pluginLoaded, this, &MainDocument::addSchedulerPlugin); loader->loadAllPlugins(); } void MainDocument::addSchedulerPlugin( const QString &key, SchedulerPlugin *plugin) { debugPlan<setConfig( m_config ); } void MainDocument::setProject( Project *project ) { if ( m_project ) { delete m_project; } m_project = project; if ( m_project ) { connect( m_project, &Project::projectChanged, this, &MainDocument::changed ); // m_project->setConfig( config() ); m_project->setSchedulerPlugins( m_schedulerPlugins ); // For auto scheduling delete m_calculationCommand; m_calculationCommand = nullptr; m_currentCalculationManager = nullptr; m_nextCalculationManager = nullptr; connect(m_project, &Project::nodeAdded, this, &MainDocument::setCalculationNeeded); connect(m_project, &Project::nodeRemoved, this, &MainDocument::setCalculationNeeded); connect(m_project, &Project::relationAdded, this, &MainDocument::setCalculationNeeded); connect(m_project, &Project::relationRemoved, this, &MainDocument::setCalculationNeeded); connect(m_project, &Project::calendarChanged, this, &MainDocument::setCalculationNeeded); connect(m_project, &Project::defaultCalendarChanged, this, &MainDocument::setCalculationNeeded); connect(m_project, &Project::calendarAdded, this, &MainDocument::setCalculationNeeded); connect(m_project, &Project::calendarRemoved, this, &MainDocument::setCalculationNeeded); connect(m_project, &Project::scheduleManagerChanged, this, &MainDocument::slotScheduleManagerChanged); connect(m_project, &Project::nodeChanged, this, &MainDocument::slotNodeChanged); connect(m_project, &Project::sigCalculationFinished, this, &MainDocument::slotCalculationFinished); } m_aboutPage.setProject( project ); QString dir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); if (!dir.isEmpty()) { dir += "/taskmodules"; m_project->setLocalTaskModulesPath(QUrl::fromLocalFile(dir)); } setTaskModulesWatch(); connect(project, &Project::taskModulesChanged, this, &MainDocument::setTaskModulesWatch); emit changed(); } void MainDocument::setTaskModulesWatch() { delete m_taskModulesWatch; m_taskModulesWatch = new KDirWatch(this); for (const QUrl &url : m_project->taskModules()) { m_taskModulesWatch->addDir(url.toLocalFile()); } connect(m_taskModulesWatch, &KDirWatch::dirty, this, &MainDocument::taskModuleDirChanged); } void MainDocument::taskModuleDirChanged() { // HACK to trigger update FIXME m_project->setUseLocalTaskModules(m_project->useLocalTaskModules()); } bool MainDocument::loadOdf( KoOdfReadStore &odfStore ) { warnPlan<< "OpenDocument not supported, let's try native xml format"; return loadXML( odfStore.contentDoc(), 0 ); // We have only one format, so try to load that! } bool MainDocument::loadXML( const KoXmlDocument &document, KoStore* ) { QPointer updater; if (progressUpdater()) { updater = progressUpdater()->startSubtask(1, "Plan::Part::loadXML"); updater->setProgress(0); m_xmlLoader.setUpdater( updater ); } QString value; KoXmlElement plan = document.documentElement(); // Check if this is the right app value = plan.attribute( "mime", QString() ); if ( value.isEmpty() ) { errorPlan << "No mime type specified!"; setErrorMessage( i18n( "Invalid document. No mimetype specified." ) ); return false; } if ( value == "application/x-vnd.kde.kplato" ) { if (updater) { updater->setProgress(5); } m_xmlLoader.setMimetype( value ); QString message; Project *newProject = new Project(m_config, false); KPlatoXmlLoader loader( m_xmlLoader, newProject ); bool ok = loader.load( plan ); if ( ok ) { setProject( newProject ); setModified( false ); debugPlan<schedules(); // Cleanup after possible bug: // There should *not* be any deleted schedules (or with parent == 0) foreach ( Node *n, newProject->nodeDict()) { foreach ( Schedule *s, n->schedules()) { if ( s->isDeleted() ) { // true also if parent == 0 errorPlan<name()<takeSchedule( s ); delete s; } } } } else { setErrorMessage( loader.errorMessage() ); delete newProject; } if (updater) { updater->setProgress(100); // the rest is only processing, not loading } emit changed(); return ok; } if ( value != "application/x-vnd.kde.plan" ) { errorPlan << "Unknown mime type " << value; setErrorMessage( i18n( "Invalid document. Expected mimetype application/x-vnd.kde.plan, got %1", value ) ); return false; } QString syntaxVersion = plan.attribute( "version", PLAN_FILE_SYNTAX_VERSION ); m_xmlLoader.setVersion( syntaxVersion ); if ( syntaxVersion > PLAN_FILE_SYNTAX_VERSION ) { KMessageBox::ButtonCode ret = KMessageBox::warningContinueCancel( 0, i18n( "This document was created with a newer version of Plan (syntax version: %1)\n" "Opening it in this version of Plan will lose some information.", syntaxVersion ), i18n( "File-Format Mismatch" ), KGuiItem( i18n( "Continue" ) ) ); if ( ret == KMessageBox::Cancel ) { setErrorMessage( "USER_CANCELED" ); return false; } } if (updater) updater->setProgress(5); /* #ifdef KOXML_USE_QDOM int numNodes = plan.childNodes().count(); #else int numNodes = plan.childNodesCount(); #endif */ #if 0 This test does not work any longer. KoXml adds a couple of elements not present in the file!! if ( numNodes > 2 ) { //TODO: Make a proper bitching about this debugPlan <<"*** Error ***"; debugPlan <<" Children count should be maximum 2, but is" << numNodes; return false; } #endif m_xmlLoader.startLoad(); KoXmlNode n = plan.firstChild(); for ( ; ! n.isNull(); n = n.nextSibling() ) { if ( ! n.isElement() ) { continue; } KoXmlElement e = n.toElement(); if ( e.tagName() == "project" ) { Project *newProject = new Project(m_config, true); m_xmlLoader.setProject( newProject ); if ( newProject->load( e, m_xmlLoader ) ) { if ( newProject->id().isEmpty() ) { newProject->setId( newProject->uniqueNodeId() ); newProject->registerNodeId( newProject ); } // The load went fine. Throw out the old project setProject( newProject ); // Cleanup after possible bug: // There should *not* be any deleted schedules (or with parent == 0) foreach ( Node *n, newProject->nodeDict()) { foreach ( Schedule *s, n->schedules()) { if ( s->isDeleted() ) { // true also if parent == 0 errorPlan<name()<takeSchedule( s ); delete s; } } } } else { delete newProject; m_xmlLoader.addMsg( XMLLoaderObject::Errors, "Loading of project failed" ); //TODO add some ui here } } } m_xmlLoader.stopLoad(); if (updater) updater->setProgress(100); // the rest is only processing, not loading setModified( false ); emit changed(); return true; } QDomDocument MainDocument::saveXML() { debugPlan; // Save the project XmlSaveContext context(m_project); context.save(); return context.document; } QDomDocument MainDocument::saveWorkPackageXML( const Node *node, long id, Resource *resource ) { debugPlanWp<name() ); wp.setAttribute( "owner-id", resource->id() ); } wp.setAttribute( "time-tag", QDateTime::currentDateTime().toString( Qt::ISODate ) ); wp.setAttribute("save-url", m_project->workPackageInfo().retrieveUrl.toString(QUrl::None)); wp.setAttribute("load-url", m_project->workPackageInfo().publishUrl.toString(QUrl::None)); debugPlanWp<<"publish:"<workPackageInfo().publishUrl.toString(QUrl::None); debugPlanWp<<"retrieve:"<workPackageInfo().retrieveUrl.toString(QUrl::None); doc.appendChild( wp ); // Save the project m_project->saveWorkPackageXML( doc, node, id ); return document; } bool MainDocument::saveWorkPackageToStream( QIODevice *dev, const Node *node, long id, Resource *resource ) { QDomDocument doc = saveWorkPackageXML( node, id, resource ); // Save to buffer QByteArray s = doc.toByteArray(); // utf8 already dev->open( QIODevice::WriteOnly ); int nwritten = dev->write( s.data(), s.size() ); if ( nwritten != (int)s.size() ) { warnPlanWp<<"wrote:"<m_specialOutputFlag == SaveEncrypted ) { backend = KoStore::Encrypted; debugPlan <<"Saving using encrypted backend."; }*/ #endif QByteArray mimeType = "application/x-vnd.kde.plan.work"; debugPlanWp <<"MimeType=" << mimeType; KoStore *store = KoStore::createStore( file, KoStore::Write, mimeType, backend ); /* if ( d->m_specialOutputFlag == SaveEncrypted && !d->m_password.isNull( ) ) { store->setPassword( d->m_password ); }*/ if ( store->bad() ) { setErrorMessage( i18n( "Could not create the workpackage file for saving: %1", file ) ); // more details needed? delete store; return false; } // Tell KoStore not to touch the file names if ( ! store->open( "root" ) ) { setErrorMessage( i18n( "Not able to write '%1'. Partition full?", QString( "maindoc.xml") ) ); delete store; return false; } KoStoreDevice dev( store ); if ( !saveWorkPackageToStream( &dev, node, id, resource ) || !store->close() ) { errorPlanWp <<"saveToStream failed"; delete store; return false; } node->documents().saveToStore( store ); debugPlanWp <<"Saving done of url:" << file; if ( !store->finalize() ) { delete store; return false; } // Success delete store; return true; } bool MainDocument::saveWorkPackageUrl( const QUrl &_url, const Node *node, long id, Resource *resource ) { debugPlanWp<<_url; QApplication::setOverrideCursor( Qt::WaitCursor ); emit statusBarMessage( i18n("Saving...") ); bool ret = false; ret = saveWorkPackageFormat( _url.path(), node, id, resource ); // kzip don't handle file:// QApplication::restoreOverrideCursor(); emit clearStatusBarMessage(); return ret; } bool MainDocument::loadWorkPackage( Project &project, const QUrl &url ) { debugPlanWp<bad() ) { // d->lastErrorMessage = i18n( "Not a valid Calligra file: %1", file ); errorPlanWp<<"bad store"<open( "root" ) ) { // "old" file format (maindoc.xml) // i18n( "File does not have a maindoc.xml: %1", file ); errorPlanWp<<"No root"<device(), &errorMsg, &errorLine, &errorColumn ); if ( ! ok ) { errorPlanWp << "Parsing error in " << url.url() << "! Aborting!" << endl << " In line: " << errorLine << ", column: " << errorColumn << endl << " Error message: " << errorMsg; //d->lastErrorMessage = i18n( "Parsing error in %1 at line %2, column %3\nError message: %4",filename ,errorLine, errorColumn , QCoreApplication::translate("QXml", errorMsg.toUtf8(), 0, QCoreApplication::UnicodeUTF8)); } else { package = loadWorkPackageXML( project, store->device(), doc, url ); if ( package ) { package->url = url; m_workpackages.insert( package->timeTag, package ); if (!m_mergedPackages.contains(package->timeTag)) { m_mergedPackages[package->timeTag] = package->project; // register this for next time } } else { ok = false; } } store->close(); //### if ( ok && package && package->settings.documents ) { ok = extractFiles( store, package ); } delete store; if ( ! ok ) { // QApplication::restoreOverrideCursor(); return false; } return true; } Package *MainDocument::loadWorkPackageXML( Project &project, QIODevice *, const KoXmlDocument &document, const QUrl &url ) { QString value; bool ok = true; Project *proj = 0; Package *package = 0; KoXmlElement plan = document.documentElement(); // Check if this is the right app value = plan.attribute( "mime", QString() ); if ( value.isEmpty() ) { errorPlanWp<timeTag = QDateTime::fromString( loader.timeTag(), Qt::ISODate ); } else if ( value != "application/x-vnd.kde.plan.work" ) { errorPlanWp << "Unknown mime type " << value; setErrorMessage( i18n( "Invalid document. Expected mimetype application/x-vnd.kde.plan.work, got %1", value ) ); return 0; } else { if (plan.attribute("editor") != QStringLiteral("PlanWork")) { warnPlanWp<<"Skipped work package file not generated with PlanWork:"< PLANWORK_FILE_SYNTAX_VERSION ) { KMessageBox::ButtonCode ret = KMessageBox::warningContinueCancel( 0, i18n( "This document was created with a newer version of PlanWork (syntax version: %1)\n" "Opening it in this version of PlanWork will lose some information.", syntaxVersion ), i18n( "File-Format Mismatch" ), KGuiItem( i18n( "Continue" ) ) ); if ( ret == KMessageBox::Cancel ) { setErrorMessage( "USER_CANCELED" ); return 0; } } m_xmlLoader.setVersion( plan.attribute( "plan-version", PLAN_FILE_SYNTAX_VERSION ) ); m_xmlLoader.startLoad(); proj = new Project(); package = new Package(); package->project = proj; KoXmlNode n = plan.firstChild(); for ( ; ! n.isNull(); n = n.nextSibling() ) { if ( ! n.isElement() ) { continue; } KoXmlElement e = n.toElement(); if ( e.tagName() == "project" ) { m_xmlLoader.setProject( proj ); ok = proj->load( e, m_xmlLoader ); if ( ! ok ) { m_xmlLoader.addMsg( XMLLoaderObject::Errors, "Loading of work package failed" ); warnPlanWp<<"Skip workpackage:"<<"Loading project failed"; //TODO add some ui here } } else if ( e.tagName() == "workpackage" ) { package->timeTag = QDateTime::fromString( e.attribute( "time-tag" ), Qt::ISODate ); package->ownerId = e.attribute( "owner-id" ); package->ownerName = e.attribute( "owner" ); debugPlan<<"workpackage:"<timeTag<ownerId<ownerName; KoXmlElement elem; forEachElement( elem, e ) { if ( elem.tagName() != "settings" ) { continue; } package->settings.usedEffort = (bool)elem.attribute( "used-effort" ).toInt(); package->settings.progress = (bool)elem.attribute( "progress" ).toInt(); package->settings.documents = (bool)elem.attribute( "documents" ).toInt(); } } } if ( proj->numChildren() > 0 ) { package->task = static_cast( proj->childNode( 0 ) ); package->toTask = qobject_cast( m_project->findNode( package->task->id() ) ); WorkPackage &wp = package->task->workPackage(); if ( wp.ownerId().isEmpty() ) { wp.setOwnerId( package->ownerId ); wp.setOwnerName( package->ownerName ); } if (wp.ownerId() != package->ownerId) { warnPlanWp<<"Current owner:"<ownerName; } debugPlanWp<<"Task set:"<task->name(); } m_xmlLoader.stopLoad(); } if (ok && proj->id() != project.id()) { debugPlanWp<<"Skip workpackage:"<<"Not the correct project"; ok = false; } if (ok && (package->task == nullptr)) { warnPlanWp<<"Skip workpackage:"<<"No task in workpackage file"; ok = false; } if (ok && (package->toTask == nullptr)) { warnPlanWp<<"Skip workpackage:"<<"Cannot find task:"<task->id()<task->name(); ok = false; } if (ok && !package->timeTag.isValid()) { warnPlanWp<<"Work package is not time tagged:"<task->name()<url; ok = false; } if (ok && m_mergedPackages.contains(package->timeTag)) { debugPlanWp<<"Skip workpackage:"<<"already merged:"<task->name()<url; ok = false; // already merged } if (!ok) { delete proj; delete package; return nullptr; } return package; } bool MainDocument::extractFiles( KoStore *store, Package *package ) { if ( package->task == 0 ) { errorPlan<<"No task!"; return false; } foreach ( Document *doc, package->task->documents().documents() ) { if ( ! doc->isValid() || doc->type() != Document::Type_Product || doc->sendAs() != Document::SendAs_Copy ) { continue; } if ( ! extractFile( store, package, doc ) ) { return false; } } return true; } bool MainDocument::extractFile( KoStore *store, Package *package, const Document *doc ) { QTemporaryFile tmpfile; if ( ! tmpfile.open() ) { errorPlan<<"Failed to open temporary file"; return false; } if ( ! store->extractFile( doc->url().fileName(), tmpfile.fileName() ) ) { errorPlan<<"Failed to extract file:"<url().fileName()<<"to:"<documents.insert( tmpfile.fileName(), doc->url() ); tmpfile.setAutoRemove( false ); debugPlan<<"extracted:"<url().fileName()<<"->"<(sender()); if (m_project && m_project->workPackageInfo().checkForWorkPackages) { checkForWorkPackages( true ); } if (timer && timer->interval() != 10000) { timer->stop(); timer->setInterval(10000); timer->start(); } } void MainDocument::checkForWorkPackages( bool keep ) { if (m_checkingForWorkPackages || m_project == nullptr || m_project->numChildren() == 0 || m_project->workPackageInfo().retrieveUrl.isEmpty()) { return; } if ( ! keep ) { qDeleteAll( m_mergedPackages ); m_mergedPackages.clear(); } QDir dir( m_project->workPackageInfo().retrieveUrl.path(), "*.planwork" ); m_infoList = dir.entryInfoList( QDir::Files | QDir::Readable, QDir::Time ); checkForWorkPackage(); return; } void MainDocument::checkForWorkPackage() { if ( ! m_infoList.isEmpty() ) { m_checkingForWorkPackages = true; QUrl url = QUrl::fromLocalFile( m_infoList.takeLast().absoluteFilePath() ); if (!m_skipUrls.contains(url) && !loadWorkPackage(*m_project, url)) { m_skipUrls << url; debugPlanWp<<"skip url:"<toTask<url; if (m_workpackages.value(package->timeTag) == package) { m_workpackages.remove(package->timeTag); } QFile file( package->url.path() ); if ( ! file.exists() ) { warnPlanWp<<"File does not exist:"<toTask<url; return; } Project::WorkPackageInfo wpi = m_project->workPackageInfo(); debugPlanWp<<"retrieve:"<url.adjusted(QUrl::RemoveFilename); if (wpi.archiveAfterRetrieval && wpi.archiveUrl.isValid()) { QDir dir(wpi.archiveUrl.path()); if ( ! dir.exists() ) { if ( ! dir.mkpath( dir.path() ) ) { //TODO message warnPlanWp<<"Failed to create archive directory:"<generateUniqueIds(); m_project->setConstraintStartTime( QDateTime(QDate::currentDate(), QTime(0, 0, 0), Qt::LocalTime) ); m_project->setConstraintEndTime( m_project->constraintStartTime().addYears( 2 ) ); m_project->locale()->setCurrencyLocale(QLocale::AnyLanguage, QLocale::AnyCountry); m_project->locale()->setCurrencySymbol(QString()); } else if ( isImporting() ) { // NOTE: I don't think this is a good idea. // Let the filter generate ids for non-plan files. // If the user wants to create a new project from an old one, // he should use Tools -> Insert Project File //m_project->generateUniqueNodeIds(); } if (m_loadingSharedResourcesTemplate && m_project->calendarCount() > 0) { Calendar *c = m_project->calendarAt(0); c->setTimeZone(QTimeZone::systemTimeZone()); } if (m_project->useSharedResources() && !m_project->sharedResourcesFile().isEmpty() && !m_skipSharedProjects) { QUrl url = QUrl::fromLocalFile(m_project->sharedResourcesFile()); if (url.isValid()) { insertResourcesFile(url, m_project->loadProjectsAtStartup() ? m_project->sharedProjectsUrl() : QUrl()); } } if ( store == 0 ) { // can happen if loading a template debugPlan<<"No store"; return true; // continue anyway } delete m_context; m_context = new Context(); KoXmlDocument doc; if ( loadAndParse( store, "context.xml", doc ) ) { store->close(); m_context->load( doc ); } else warnPlan<<"No context"; return true; } // TODO: // Due to splitting of KoDocument into a document and a part, // we simulate the old behaviour by registering all views in the document. // Find a better solution! void MainDocument::registerView( View* view ) { if ( view && ! m_views.contains( view ) ) { m_views << QPointer( view ); } } bool MainDocument::completeSaving( KoStore *store ) { foreach ( View *view, m_views ) { if ( view ) { if ( store->open( "context.xml" ) ) { if ( m_context == 0 ) m_context = new Context(); QDomDocument doc = m_context->save( view ); KoStoreDevice dev( store ); QByteArray s = doc.toByteArray(); // this is already Utf8! (void)dev.write( s.data(), s.size() ); (void)store->close(); m_viewlistModified = false; emit viewlistModified( false ); } break; } } return true; } bool MainDocument::loadAndParse(KoStore *store, const QString &filename, KoXmlDocument &doc) { //debugPlan << "oldLoadAndParse: Trying to open " << filename; if (!store->open(filename)) { warnPlan << "Entry " << filename << " not found!"; // d->lastErrorMessage = i18n( "Could not find %1",filename ); return false; } // Error variables for QDomDocument::setContent QString errorMsg; int errorLine, errorColumn; bool ok = doc.setContent( store->device(), &errorMsg, &errorLine, &errorColumn ); if ( !ok ) { errorPlan << "Parsing error in " << filename << "! Aborting!" << endl << " In line: " << errorLine << ", column: " << errorColumn << endl << " Error message: " << errorMsg; /* d->lastErrorMessage = i18n( "Parsing error in %1 at line %2, column %3\nError message: %4" ,filename ,errorLine, errorColumn , QCoreApplication::translate("QXml", errorMsg.toUtf8(), 0, QCoreApplication::UnicodeUTF8));*/ store->close(); return false; } debugPlan << "File " << filename << " loaded and parsed"; return true; } void MainDocument::insertFile( const QUrl &url, Node *parent, Node *after ) { Part *part = new Part( this ); MainDocument *doc = new MainDocument( part ); part->setDocument( doc ); doc->disconnect(); // doc shall not handle feedback from openUrl() doc->setAutoSave( 0 ); //disable doc->m_insertFileInfo.url = url; doc->m_insertFileInfo.parent = parent; doc->m_insertFileInfo.after = after; connect(doc, &KoDocument::completed, this, &MainDocument::insertFileCompleted); connect(doc, &KoDocument::canceled, this, &MainDocument::insertFileCancelled); doc->openUrl( url ); } void MainDocument::insertFileCompleted() { debugPlan<( sender() ); if ( doc ) { Project &p = doc->getProject(); insertProject( p, doc->m_insertFileInfo.parent, doc->m_insertFileInfo.after ); doc->documentPart()->deleteLater(); // also deletes document } else { KMessageBox::error( 0, i18n("Internal error, failed to insert file.") ); } } void MainDocument::insertResourcesFile(const QUrl &url, const QUrl &projects) { insertSharedProjects(projects); // prepare for insertion after shared resources m_sharedProjectsFiles.removeAll(url); // resource file is not a project Part *part = new Part( this ); MainDocument *doc = new MainDocument( part ); doc->m_skipSharedProjects = true; // should not have shared projects, but... part->setDocument( doc ); doc->disconnect(); // doc shall not handle feedback from openUrl() doc->setAutoSave( 0 ); //disable doc->setCheckAutoSaveFile(false); connect(doc, &KoDocument::completed, this, &MainDocument::insertResourcesFileCompleted); connect(doc, &KoDocument::canceled, this, &MainDocument::insertFileCancelled); doc->openUrl( url ); } void MainDocument::insertResourcesFileCompleted() { debugPlanShared<( sender() ); if (doc) { Project &p = doc->getProject(); mergeResources(p); m_project->setSharedResourcesLoaded(true); doc->documentPart()->deleteLater(); // also deletes document slotInsertSharedProject(); // insert shared bookings } else { KMessageBox::error( 0, i18n("Internal error, failed to insert file.") ); } } void MainDocument::insertFileCancelled( const QString &error ) { debugPlan<( sender() ); if ( doc ) { doc->documentPart()->deleteLater(); // also deletes document } } void MainDocument::clearResourceAssignments() { foreach (Resource *r, m_project->resourceList()) { r->clearExternalAppointments(); } } void MainDocument::loadResourceAssignments(QUrl url) { insertSharedProjects(url); slotInsertSharedProject(); } void MainDocument::insertSharedProjects(const QList &urls) { clearResourceAssignments(); m_sharedProjectsFiles = urls; slotInsertSharedProject(); } void MainDocument::insertSharedProjects(const QUrl &url) { m_sharedProjectsFiles.clear(); QFileInfo fi(url.path()); if (!fi.exists()) { return; } if (fi.isFile()) { m_sharedProjectsFiles = QList() << url; debugPlan<<"Get all projects in file:"<m_skipSharedProjects = true; // never load recursively part->setDocument( doc ); doc->disconnect(); // doc shall not handle feedback from openUrl() doc->setAutoSave( 0 ); //disable doc->setCheckAutoSaveFile(false); doc->m_loadingSharedProject = true; connect(doc, &KoDocument::completed, this, &MainDocument::insertSharedProjectCompleted); connect(doc, &KoDocument::canceled, this, &MainDocument::insertSharedProjectCancelled); doc->openUrl(m_sharedProjectsFiles.takeFirst()); } void MainDocument::insertSharedProjectCompleted() { debugPlanShared<( sender() ); if (doc) { Project &p = doc->getProject(); debugPlanShared<id()<<"Loaded project:"<id() && p.isScheduled(ANYSCHEDULED)) { // FIXME: improve! // find a suitable schedule ScheduleManager *sm = 0; foreach(ScheduleManager *m, p.allScheduleManagers()) { if (m->isBaselined()) { sm = m; break; } if (m->isScheduled()) { sm = m; // take the last one, more likely to be subschedule } } if (sm) { foreach(Resource *r, p.resourceList()) { Resource *res = m_project->resource(r->id()); if (res && res->isShared()) { Appointment *app = new Appointment(); app->setAuxcilliaryInfo(p.name()); foreach(const Appointment *a, r->appointments(sm->scheduleId())) { *app += *a; } if (app->isEmpty()) { delete app; } else { res->addExternalAppointment(p.id(), app); debugPlanShared<name()<<"added:"<auxcilliaryInfo()<documentPart()->deleteLater(); // also deletes document emit insertSharedProject(); // do next file } else { KMessageBox::error( 0, i18n("Internal error, failed to insert file.") ); } } void MainDocument::insertSharedProjectCancelled( const QString &error ) { debugPlanShared<( sender() ); if ( doc ) { doc->documentPart()->deleteLater(); // also deletes document } } bool MainDocument::insertProject( Project &project, Node *parent, Node *after ) { debugPlan<<&project; // make sure node ids in new project is unique also in old project QList existingIds = m_project->nodeDict().keys(); foreach ( Node *n, project.allNodes() ) { QString oldid = n->id(); n->setId( project.uniqueNodeId( existingIds ) ); project.removeId( oldid ); // remove old id project.registerNodeId( n ); // register new id } MacroCommand *m = new InsertProjectCmd( project, parent==0?m_project:parent, after, kundo2_i18n( "Insert project" ) ); if ( m->isEmpty() ) { delete m; } else { addCommand( m ); } return true; } // check if calendar 'c' has children that will not be removed (normally 'Local' calendars) bool canRemoveCalendar(const Calendar *c, const QList &lst) { for (Calendar *cc : c->calendars()) { if (!lst.contains(cc)) { return false; } if (!canRemoveCalendar(cc, lst)) { return false; } } return true; } // sort parent calendars before children QList sortedRemoveCalendars(Project &shared, const QList &lst) { QList result; for (Calendar *c : lst) { if (c->isShared() && !shared.calendar(c->id())) { result << c; } result += sortedRemoveCalendars(shared, c->calendars()); } return result; } bool MainDocument::mergeResources(Project &project) { debugPlanShared<<&project; // Just in case, remove stuff not related to resources foreach(Node *n, project.childNodeIterator()) { debugPlanShared<<"Project not empty, delete node:"<name(); NodeDeleteCmd cmd(n); cmd.execute(); } foreach(ScheduleManager *m, project.scheduleManagers()) { debugPlanShared<<"Project not empty, delete schedule:"<name(); DeleteScheduleManagerCmd cmd(project, m); cmd.execute(); } foreach(Account *a, project.accounts().accountList()) { debugPlanShared<<"Project not empty, delete account:"<name(); RemoveAccountCmd cmd(project, a); cmd.execute(); } // Mark all resources / groups as shared foreach(ResourceGroup *g, project.resourceGroups()) { g->setShared(true); } foreach(Resource *r, project.resourceList()) { r->setShared(true); } // Mark all calendars shared foreach(Calendar *c, project.allCalendars()) { c->setShared(true); } // check if any shared stuff has been removed QList removedGroups; QList removedResources; QList removedCalendars; QStringList removed; foreach(ResourceGroup *g, m_project->resourceGroups()) { if (g->isShared() && !project.findResourceGroup(g->id())) { removedGroups << g; removed << i18n("Group: %1", g->name()); } } foreach(Resource *r, m_project->resourceList()) { if (r->isShared() && !project.findResource(r->id())) { removedResources << r; removed << i18n("Resource: %1", r->name()); } } removedCalendars = sortedRemoveCalendars(project, m_project->calendars()); for (Calendar *c : qAsConst(removedCalendars)) { removed << i18n("Calendar: %1", c->name()); } if (!removed.isEmpty()) { KMessageBox::ButtonCode result = KMessageBox::warningYesNoCancelList( 0, i18n("Shared resources has been removed from the shared resources file." "\nSelect how they shall be treated in this project."), removed, xi18nc("@title:window", "Shared resources"), KStandardGuiItem::remove(), KGuiItem(i18n("Convert")), KGuiItem(i18n("Keep")) ); switch (result) { case KMessageBox::Yes: // Remove for (Resource *r : qAsConst(removedResources)) { RemoveResourceCmd cmd(r->parentGroup(), r); cmd.redo(); } for (ResourceGroup *g : qAsConst(removedGroups)) { if (g->resources().isEmpty()) { RemoveResourceGroupCmd cmd(m_project, g); cmd.redo(); } else { // we may have put local resource(s) in this group // so we need to keep it g->setShared(false); m_project->removeResourceGroupId(g->id()); g->setId(m_project->uniqueResourceGroupId()); m_project->insertResourceGroupId(g->id(), g); } } for (Calendar *c : qAsConst(removedCalendars)) { CalendarRemoveCmd cmd(m_project, c); cmd.redo(); } break; case KMessageBox::No: // Convert for (Resource *r : qAsConst(removedResources)) { r->setShared(false); m_project->removeResourceId(r->id()); r->setId(m_project->uniqueResourceId()); m_project->insertResourceId(r->id(), r); } for (ResourceGroup *g : qAsConst(removedGroups)) { g->setShared(false); m_project->removeResourceGroupId(g->id()); g->setId(m_project->uniqueResourceGroupId()); m_project->insertResourceGroupId(g->id(), g); } for (Calendar *c : qAsConst(removedCalendars)) { c->setShared(false); m_project->removeCalendarId(c->id()); c->setId(m_project->uniqueCalendarId()); m_project->insertCalendarId(c->id(), c); } break; case KMessageBox::Cancel: // Keep break; default: break; } } // update values of already existing objects QStringList l1; foreach(ResourceGroup *g, project.resourceGroups()) { l1 << g->id(); } QStringList l2; foreach(ResourceGroup *g, m_project->resourceGroups()) { l2 << g->id(); } debugPlanShared< removegroups; foreach(ResourceGroup *g, project.resourceGroups()) { ResourceGroup *group = m_project->findResourceGroup(g->id()); if (group) { if (!group->isShared()) { // User has probably created shared resources from this project, // so the resources exists but are local ones. // Convert to shared and do not load the group from shared. removegroups << g; group->setShared(true); debugPlanShared<<"Set group to shared:"<id(); } group->setName(g->name()); group->setType(g->type()); debugPlanShared<<"Updated group:"<id(); } } QList removeresources; foreach(Resource *r, project.resourceList()) { Resource *resource = m_project->findResource(r->id()); if (resource) { if (!resource->isShared()) { // User has probably created shared resources from this project, // so the resources exists but are local ones. // Convert to shared and do not load the resource from shared. removeresources << r; resource->setShared(true); debugPlanShared<<"Set resource to shared:"<id(); } resource->setName(r->name()); resource->setInitials(r->initials()); resource->setEmail(r->email()); resource->setType(r->type()); resource->setAutoAllocate(r->autoAllocate()); resource->setAvailableFrom(r->availableFrom()); resource->setAvailableUntil(r->availableUntil()); resource->setUnits(r->units()); resource->setNormalRate(r->normalRate()); resource->setOvertimeRate(r->overtimeRate()); QString id = r->calendar(true) ? r->calendar(true)->id() : QString(); resource->setCalendar(m_project->findCalendar(id)); id = r->account() ? r->account()->name() : QString(); resource->setAccount(m_project->accounts().findAccount(id)); resource->setRequiredIds(r->requiredIds()); resource->setTeamMemberIds(r->teamMemberIds()); debugPlanShared<<"Updated resource:"<id(); } } QList removecalendars; foreach(Calendar *c, project.allCalendars()) { Calendar *calendar = m_project->findCalendar(c->id()); if (calendar) { if (!calendar->isShared()) { // User has probably created shared resources from this project, // so the calendar exists but are local ones. // Convert to shared and do not load the resource from shared. removecalendars << c; calendar->setShared(true); debugPlanShared<<"Set calendar to shared:"<id(); } *calendar = *c; debugPlanShared<<"Updated calendar:"<id(); } } debugPlanShared<<"Remove:"<childCount() == 0) { removecalendars.removeAt(i); debugPlanShared<<"Delete calendar:"<id(); CalendarRemoveCmd cmd(&project, c); cmd.execute(); } } } for (Resource *r : qAsConst(removeresources)) { debugPlanShared<<"Delete resource:"<id(); RemoveResourceCmd cmd(r->parentGroup(), r); cmd.execute(); } for (ResourceGroup *g : qAsConst(removegroups)) { debugPlanShared<<"Delete group:"<id(); RemoveResourceGroupCmd cmd(&project, g); cmd.execute(); } // insert new objects Q_ASSERT(project.childNodeIterator().isEmpty()); InsertProjectCmd cmd(project, m_project, 0); cmd.execute(); return true; } void MainDocument::insertViewListItem( View */*view*/, const ViewListItem *item, const ViewListItem *parent, int index ) { // FIXME callers should take care that they now get a signal even if originating from themselves emit viewListItemAdded(item, parent, index); setModified( true ); m_viewlistModified = true; } void MainDocument::removeViewListItem( View */*view*/, const ViewListItem *item ) { // FIXME callers should take care that they now get a signal even if originating from themselves emit viewListItemRemoved(item); setModified( true ); m_viewlistModified = true; } void MainDocument::setModified( bool mod ) { debugPlan<name().isEmpty()) { setUrl(QUrl(m_project->name() + ".plan")); } if (m_project->scheduleManagers().isEmpty()) { ScheduleManager *sm = m_project->createScheduleManager(); sm->setAllowOverbooking(false); sm->setSchedulingMode(ScheduleManager::AutoMode); } Calendar *week = nullptr; if (KPlatoSettings::generateWeek()) { bool always = KPlatoSettings::generateWeekChoice() == KPlatoSettings::EnumGenerateWeekChoice::Always; bool ifnone = KPlatoSettings::generateWeekChoice() == KPlatoSettings::EnumGenerateWeekChoice::NoneExists; if (always || (ifnone && m_project->calendarCount() == 0)) { // create a calendar week = new Calendar(i18nc("Base calendar name", "Base")); m_project->addCalendar(week); CalendarDay vd(CalendarDay::NonWorking); for (int i = Qt::Monday; i <= Qt::Sunday; ++i) { if (m_config.isWorkingday(i)) { CalendarDay wd(CalendarDay::Working); TimeInterval ti(m_config.dayStartTime(i), m_config.dayLength(i)); wd.addInterval(ti); week->setWeekday(i, wd); } else { week->setWeekday(i, vd); } } } } #ifdef HAVE_KHOLIDAYS if (KPlatoSettings::generateHolidays()) { bool inweek = week != 0 && KPlatoSettings::generateHolidaysChoice() == KPlatoSettings::EnumGenerateHolidaysChoice::InWeekCalendar; bool subcalendar = week != 0 && KPlatoSettings::generateHolidaysChoice() == KPlatoSettings::EnumGenerateHolidaysChoice::AsSubCalendar; bool separate = week == 0 || KPlatoSettings::generateHolidaysChoice() == KPlatoSettings::EnumGenerateHolidaysChoice::AsSeparateCalendar; Calendar *holiday = nullptr; if (inweek) { holiday = week; week->setDefault(true); debugPlan<<"in week"; } else if (subcalendar) { holiday = new Calendar(i18n("Holidays")); m_project->addCalendar(holiday, week); holiday->setDefault(true); debugPlan<<"subcalendar"; } else if (separate) { holiday = new Calendar(i18n("Holidays")); m_project->addCalendar(holiday); holiday->setDefault(true); debugPlan<<"separate"; } else { Q_ASSERT(false); // something wrong } debugPlan<setHolidayRegion(KPlatoSettings::region()); } #else week->setDefault(true); #endif } // creates a "new" project from current project (new ids etc) void MainDocument::createNewProject() { setEmpty(); clearUndoHistory(); setModified( false ); resetURL(); KoDocumentInfo *info = documentInfo(); info->resetMetaData(); info->setProperty( "title", "" ); setTitleModified(); m_project->generateUniqueNodeIds(); Duration dur = m_project->constraintEndTime() - m_project->constraintStartTime(); m_project->setConstraintStartTime( QDateTime(QDate::currentDate(), QTime(0, 0, 0), Qt::LocalTime) ); m_project->setConstraintEndTime( m_project->constraintStartTime() + dur ); while ( m_project->numScheduleManagers() > 0 ) { foreach ( ScheduleManager *sm, m_project->allScheduleManagers() ) { if ( sm->childCount() > 0 ) { continue; } if ( sm->expected() ) { sm->expected()->setDeleted( true ); sm->setExpected( 0 ); } m_project->takeScheduleManager( sm ); delete sm; } } foreach ( Schedule *s, m_project->schedules() ) { m_project->takeSchedule( s ); delete s; } foreach ( Node *n, m_project->allNodes() ) { foreach ( Schedule *s, n->schedules() ) { n->takeSchedule( s ); delete s; } } foreach ( Resource *r, m_project->resourceList() ) { foreach ( Schedule *s, r->schedules() ) { r->takeSchedule( s ); delete s; } } } void MainDocument::setIsTaskModule(bool value) { m_isTaskModule = value; } bool MainDocument::isTaskModule() const { return m_isTaskModule; } } //KPlato namespace diff --git a/src/kptmaindocument.h b/src/kptmaindocument.h index f10c5f84..d14d5603 100644 --- a/src/kptmaindocument.h +++ b/src/kptmaindocument.h @@ -1,276 +1,277 @@ /* This file is part of the KDE project * Copyright (C) 1998, 1999, 2000 Torben Weis * Copyright (C) 2004 - 2010 Dag Andersen * Copyright (C) 2006 Raphael Langerhorst * Copyright (C) 2007 Thorsten Zachmann * Copyright (C) 2019 Dag Andersen * * 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 KPTMAINDOCUMENT_H #define KPTMAINDOCUMENT_H #include "plan_export.h" #include "kptpackage.h" #include "kpttask.h" #include "kptconfig.h" #include "kptwbsdefinition.h" #include "kptxmlloaderobject.h" #include "about/aboutpage.h" #include "KoDocument.h" #include #include #define PLAN_MIME_TYPE "application/x-vnd.kde.plan" class KDirWatch; /// The main namespace. namespace KPlato { class DocumentChild; class Project; class Context; class SchedulerPlugin; class ViewListItem; class View; class Package; class PLAN_EXPORT MainDocument : public KoDocument { Q_OBJECT public: explicit MainDocument(KoPart *part); ~MainDocument() override; + void initEmpty() override; /// reimplemented from KoDocument QByteArray nativeFormatMimeType() const override { return PLAN_MIME_TYPE; } /// reimplemented from KoDocument QByteArray nativeOasisMimeType() const override { return ""; } /// reimplemented from KoDocument QStringList extraNativeMimeTypes() const override { return QStringList() << PLAN_MIME_TYPE; } void setReadWrite( bool rw ) override; void configChanged(); void paintContent( QPainter& painter, const QRect& rect) override; void setProject( Project *project ); Project *project() const override { return m_project; } Project &getProject() { return *m_project; } const Project &getProject() const { return * m_project; } QString projectName() const override { return m_project->name(); } /** * Return the set of SupportedSpecialFormats that the kplato wants to * offer in the "Save" file dialog. * Note: SaveEncrypted is not supported. */ int supportedSpecialFormats() const override { return SaveAsDirectoryStore; } // The load and save functions. Look in the file kplato.dtd for info bool loadXML( const KoXmlDocument &document, KoStore *store ) override; QDomDocument saveXML() override; /// Save a workpackage file containing @p node with schedule identity @p id, owned by @p resource QDomDocument saveWorkPackageXML( const Node *node, long id, Resource *resource = 0 ); bool saveOdf( SavingContext &/*documentContext */) override { return false; } bool loadOdf( KoOdfReadStore & odfStore ) override; Config &config() { return m_config; } Context *context() const { return m_context; } WBSDefinition &wbsDefinition() { return m_project->wbsDefinition(); } const XMLLoaderObject &xmlLoader() const { return m_xmlLoader; } DocumentChild *createChild( KoDocument *doc, const QRect &geometry = QRect() ); bool saveWorkPackageToStream( QIODevice * dev, const Node *node, long id, Resource *resource = 0 ); bool saveWorkPackageFormat( const QString &file, const Node *node, long id, Resource *resource = 0 ); bool saveWorkPackageUrl( const QUrl & _url, const Node *node, long id, Resource *resource = 0 ); /// Load the workpackage from @p url into @p project. Return true if successful, else false. bool loadWorkPackage( Project &project, const QUrl &url ); Package *loadWorkPackageXML( Project& project, QIODevice*, const KoXmlDocument& document, const QUrl& url ); QMap workPackages() const { return m_workpackages; } void clearWorkPackages() { qDeleteAll(m_workpackages); m_workpackages.clear(); m_checkingForWorkPackages = false; } void insertFile( const QUrl &url, Node *parent, Node *after = 0 ); bool insertProject( Project &project, Node *parent, Node *after ); bool mergeResources(Project &project); KPlatoAboutPage &aboutPage() { return m_aboutPage; } bool extractFiles( KoStore *store, Package *package ); bool extractFile( KoStore *store, Package *package, const Document *doc ); void registerView( View *view ); /// Create a new project from this project /// Generates new project id and task ids /// Keeps resource- and calendar ids void createNewProject(); bool isTaskModule() const; using KoDocument::setModified; public Q_SLOTS: void setModified( bool mod ) override; /// Inserts an item into all other views than @p view void insertViewListItem(KPlato::View *view, const KPlato::ViewListItem *item, const KPlato::ViewListItem *parent, int index); /// Removes the view list item from all other views than @p view void removeViewListItem(KPlato::View *view, const KPlato::ViewListItem *item); /// View selector has been modified void slotViewlistModified(); /// Check for workpackages /// If @p keep is true, packages that has been refused will not be checked for again void checkForWorkPackages(bool keep); /// Remove @p package void terminateWorkPackage( const KPlato::Package *package ); void setLoadingTemplate( bool ); void setLoadingSharedResourcesTemplate( bool ); void insertResourcesFile(const QUrl &url, const QUrl &projects = QUrl()); void slotProjectCreated(); /// Prepare for insertion of resource assignments of shared resources from the project(s) in @p urls void insertSharedProjects(const QList &urls); /// Prepare for insertion of resource assignments of shared resources from the project(s) in @p url void insertSharedProjects(const QUrl &url); /// Clear resource assignments of shared resources void clearResourceAssignments(); /// Load resource assignments of shared resources from the project(s) in @p url void loadResourceAssignments(QUrl url); void setIsTaskModule(bool value); void autoCheckForWorkPackages(); Q_SIGNALS: void changed(); void workPackageLoaded(); void viewlistModified( bool ); void viewListItemAdded(const KPlato::ViewListItem *item, const KPlato::ViewListItem *parent, int index); void viewListItemRemoved(const KPlato::ViewListItem *item); void insertSharedProject(); protected: /// Load kplato specific files bool completeLoading( KoStore* store ) override; /// Save kplato specific files bool completeSaving( KoStore* store ) override; // used by insert file struct InsertFileInfo { QUrl url; Node *parent; Node *after; } m_insertFileInfo; protected Q_SLOTS: void slotViewDestroyed(); void addSchedulerPlugin(const QString&, KPlato::SchedulerPlugin *plugin); void checkForWorkPackage(); void insertFileCompleted(); void insertResourcesFileCompleted(); void insertFileCancelled( const QString& ); void slotInsertSharedProject(); void insertSharedProjectCompleted(); void insertSharedProjectCancelled( const QString& ); void slotNodeChanged(KPlato::Node*, int); void slotScheduleManagerChanged(KPlato::ScheduleManager *sm, int property); void setCalculationNeeded(); void slotCalculationFinished(KPlato::Project *project, KPlato::ScheduleManager *sm); void slotStartCalculation(); void setTaskModulesWatch(); void taskModuleDirChanged(); private: bool loadAndParse(KoStore* store, const QString& filename, KoXmlDocument& doc); void loadSchedulerPlugins(); private: Project *m_project; QWidget* m_parentWidget; Config m_config; Context *m_context; XMLLoaderObject m_xmlLoader; bool m_loadingTemplate; bool m_loadingSharedResourcesTemplate; QMap m_schedulerPlugins; QMap m_workpackages; QFileInfoList m_infoList; QList m_skipUrls; QMap m_mergedPackages; KPlatoAboutPage m_aboutPage; QDomDocument m_reports; bool m_viewlistModified; bool m_checkingForWorkPackages; QList > m_views; bool m_loadingSharedProject; QList m_sharedProjectsFiles; bool m_skipSharedProjects; bool m_isTaskModule; KUndo2Command* m_calculationCommand; ScheduleManager* m_currentCalculationManager; ScheduleManager* m_nextCalculationManager; KDirWatch *m_taskModulesWatch; }; } //KPlato namespace #endif diff --git a/src/kptpart.cpp b/src/kptpart.cpp index 6b8841dc..222eda4b 100644 --- a/src/kptpart.cpp +++ b/src/kptpart.cpp @@ -1,232 +1,252 @@ /* This file is part of the KDE project Copyright (C) 2012 C. Boemann 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. */ // clazy:excludeall=qstring-arg #include "kptpart.h" #include "config/ConfigDialog.h" #include "kptview.h" #include "kptmaindocument.h" #include "kptfactory.h" #include "welcome/WelcomeView.h" #include "kpthtmlview.h" #include "Help.h" #include "calligraplansettings.h" #include "kptdebug.h" #include #include #include #include #include #include #include #include #include #include using namespace KPlato; Part::Part(QObject *parent) : KoPart(Factory::global(), parent) , startUpWidget(0) { setTemplatesResourcePath(QLatin1String("calligraplan/templates/")); new Help(KPlatoSettings::contextPath(), KPlatoSettings::contextLanguage()); } Part::~Part() { } void Part::setDocument(KPlato::MainDocument *document) { KoPart::setDocument(document); m_document = document; } KoView *Part::createViewInstance(KoDocument *document, QWidget *parent) { // synchronize view selector View *view = dynamic_cast(views().value(0)); /*FIXME if (view && m_context) { QDomDocument doc = m_context->save(view); m_context->setContent(doc.toString()); }*/ view = new View(this, qobject_cast(document), parent); // connect(view, SIGNAL(destroyed()), this, SLOT(slotViewDestroyed())); // connect(document, SIGNAL(viewListItemAdded(const ViewListItem*,const ViewListItem*,int)), view, SLOT(addViewListItem(const ViewListItem*,const ViewListItem*,int))); // connect(document, SIGNAL(viewListItemRemoved(const ViewListItem*)), view, SLOT(removeViewListItem(const ViewListItem*))); return view; } KoMainWindow *Part::createMainWindow() { KoMainWindow *w = new KoMainWindow(PLAN_MIME_TYPE, componentData()); QAction *handbookAction = w->action("help_contents"); if (handbookAction) { // we do not want to use khelpcenter as we do not install docs disconnect(handbookAction, 0, 0, 0); connect(handbookAction, &QAction::triggered, this, &Part::slotHelpContents); } return w; } void Part::slotHelpContents() { QDesktopServices::openUrl(QUrl::fromUserInput(KPlatoSettings::documentationPath())); } void Part::showStartUpWidget(KoMainWindow *parent) { m_toolbarVisible = parent->factory()->container("mainToolBar", parent)->isVisible(); if (m_toolbarVisible) { parent->factory()->container("mainToolBar", parent)->hide(); } if (startUpWidget) { startUpWidget->show(); } else { createStarUpWidget(parent); parent->setCentralWidget(startUpWidget); } parent->setPartToOpen(this); } void Part::slotOpenTemplate(const QUrl &url) { openTemplate(url); } void Part::openTemplate(const QUrl &url) { debugPlan<<"Open shared resources template:"<setLoadingTemplate(true); m_document->setLoadingSharedResourcesTemplate(url.fileName() == "SharedResources.plant"); KoPart::openTemplate(url); m_document->setLoadingTemplate(false); } +bool Part::openProjectTemplate(const QUrl &url) +{ + QApplication::setOverrideCursor(Qt::BusyCursor); + m_document->setLoadingTemplate(true); + bool ok = m_document->loadNativeFormat(url.path()); + m_document->setModified(false); + m_document->undoStack()->clear(); + + if (ok) { + m_document->resetURL(); + m_document->setEmpty(); + } else { + m_document->showLoadingErrorDialog(); + m_document->initEmpty(); + } + m_document->setLoadingTemplate(false); + QApplication::restoreOverrideCursor(); + return ok; +} + void Part::openTaskModule(const QUrl &url) { Part *part = new Part(0); MainDocument *doc = new MainDocument(part); part->setDocument(doc); doc->setIsTaskModule(true); mainWindows().first()->openDocument(part, url); } void Part::createStarUpWidget(KoMainWindow *parent) { startUpWidget = new QStackedWidget(parent); startUpWidget->addWidget(createWelcomeView(parent)); startUpWidget->addWidget(createIntroductionView()); } void Part::finish() { mainWindows().first()->setRootDocument(document(), this); if (m_toolbarVisible) { KoPart::mainWindows().first()->factory()->container("mainToolBar", mainWindows().first())->show(); } } QWidget *Part::createWelcomeView(KoMainWindow *mw) { MainDocument *doc = static_cast(document()); WelcomeView *v = new WelcomeView(this, doc, startUpWidget); v->setProject(&(doc->getProject())); KSharedConfigPtr configPtr = Factory::global().config(); KRecentFilesAction recent("x", 0); recent.loadEntries(configPtr->group("RecentFiles")); v->setRecentFiles(recent.actions()); connect(v, &WelcomeView::loadSharedResources, doc, &MainDocument::insertResourcesFile); connect(v, &WelcomeView::recentProject, mw, &KoMainWindow::slotFileOpenRecent); connect(v, &WelcomeView::showIntroduction, this, &Part::slotShowIntroduction); connect(v, &WelcomeView::projectCreated, doc, &MainDocument::slotProjectCreated); connect(v, &WelcomeView::finished, this, &Part::finish); connect(v, &WelcomeView::openTemplate, this, &Part::slotOpenTemplate); return v; } void Part::slotShowIntroduction() { startUpWidget->setCurrentIndex(1); slotOpenUrlRequest(static_cast(startUpWidget->currentWidget()), QUrl("about:plan/main")); } void Part::slotOpenUrlRequest( HtmlView *v, const QUrl &url ) { debugPlan<setCurrentIndex(0); return; } if ( url.url().startsWith( QLatin1String( "about:plan" ) ) ) { MainDocument *doc = static_cast(document()); doc->aboutPage().generatePage( v->htmlPart(), url ); return; } } if ( url.scheme() == QLatin1String("help") ) { KHelpClient::invokeHelp( "", url.fileName() ); return; } // try to open the url debugPlan<htmlPart().setJScriptEnabled(false); v->htmlPart().setJavaEnabled(false); v->htmlPart().setMetaRefreshEnabled(false); v->htmlPart().setPluginsEnabled(false); slotOpenUrlRequest( v, QUrl( "about:plan/main" ) ); connect( v, &KPlato::HtmlView::openUrlRequest, this, &KPlato::Part::slotOpenUrlRequest ); return v; } void Part::configure(KoMainWindow *mw) { //debugPlan; if( KConfigDialog::showDialog("Plan Settings")) { return; } ConfigDialog *dialog = new ConfigDialog(mw, "Plan Settings", KPlatoSettings::self()); dialog->open(); } diff --git a/src/kptpart.h b/src/kptpart.h index be8451e5..27f2c970 100644 --- a/src/kptpart.h +++ b/src/kptpart.h @@ -1,83 +1,85 @@ /* This file is part of the KDE project Copyright (C) 2012 C. Boemann 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 KPTPART_H #define KPTPART_H #include #include "plan_export.h" #include class KoView; class QStackedWidget; /// The main namespace. namespace KPlato { class MainDocument; class HtmlView; class PLAN_EXPORT Part : public KoPart { Q_OBJECT public: explicit Part(QObject *parent); ~Part() override; void setDocument(KPlato::MainDocument *document); /// reimplemented KoView *createViewInstance(KoDocument *document, QWidget *parent) override; /// reimplemented KoMainWindow *createMainWindow() override; void showStartUpWidget(KoMainWindow *parent) override; void configure(KoMainWindow *mw) override; + bool openProjectTemplate(const QUrl &url) override; + public Q_SLOTS: void openTaskModule(const QUrl& url); + void finish(); protected Q_SLOTS: - void finish(); void slotShowIntroduction(); void slotOpenUrlRequest(KPlato::HtmlView *v, const QUrl &url); void openTemplate( const QUrl& url ) override; void slotOpenTemplate(const QUrl& url); void slotHelpContents(); protected: void createStarUpWidget(KoMainWindow *parent); QWidget *createWelcomeView(KoMainWindow *parent); QWidget *createIntroductionView(); private: KPlato::MainDocument *m_document; QPointer startUpWidget; bool m_toolbarVisible; }; } //KPlato namespace #endif diff --git a/src/kptview.cpp b/src/kptview.cpp index 9f9e4550..82d6d3ca 100644 --- a/src/kptview.cpp +++ b/src/kptview.cpp @@ -1,3231 +1,3247 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999, 2000 Torben Weis Copyright (C) 2002 - 2011 Dag Andersen Copyright (C) 2012 Dag Andersen Copyright (C) 2019 Dag Andersen 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. */ // clazy:excludeall=qstring-arg #include "kptview.h" #include #include #include "KoDocumentInfo.h" #include "KoMainWindow.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 "kptlocale.h" #include "kptviewbase.h" #include "kptaccountsview.h" #include "kptaccountseditor.h" #include "kptcalendareditor.h" #include "kptfactory.h" #include "kptmilestoneprogressdialog.h" #include "kpttaskdescriptiondialog.h" #include "kptdocumentsdialog.h" #include "kptnode.h" #include "kptmaindocument.h" #include "kptproject.h" #include "kptmainprojectdialog.h" #include "kpttask.h" #include "kptsummarytaskdialog.h" #include "kpttaskdialog.h" #include "kpttaskprogressdialog.h" #include "kptganttview.h" #include "kpttaskeditor.h" #include "kptdependencyeditor.h" #include "kptperteditor.h" #include "kptdatetime.h" #include "kptcommand.h" #include "kptrelation.h" #include "kptrelationdialog.h" #include "kptresourceappointmentsview.h" #include "kptresourceeditor.h" #include "kptscheduleeditor.h" #include "kptresourcedialog.h" #include "kptresource.h" #include "kptstandardworktimedialog.h" #include "kptwbsdefinitiondialog.h" #include "kptresourceassignmentview.h" #include "kpttaskstatusview.h" #include "kptsplitterview.h" #include "kptpertresult.h" #include "kptinsertfiledlg.h" #include "kptloadsharedprojectsdialog.h" #include "kpthtmlview.h" #include "about/aboutpage.h" #include "kptlocaleconfigmoneydialog.h" #include "kptflatproxymodel.h" #include "kpttaskstatusmodel.h" #include "kptworkpackagemergedialog.h" #include "Help.h" #include "performance/PerformanceStatusView.h" #include "performance/ProjectStatusView.h" #include "reportsgenerator/ReportsGeneratorView.h" #ifdef PLAN_USE_KREPORT #include "reports/reportview.h" #include "reports/reportdata.h" #endif #include "kptviewlistdialog.h" #include "kptviewlistdocker.h" #include "kptviewlist.h" #include "kptschedulesdocker.h" #include "kptpart.h" #include "kptdebug.h" #include "calligraplansettings.h" #include "kptprintingcontrolprivate.h" // #include "KPtViewAdaptor.h" #include using namespace KPlato; View::View(KoPart *part, MainDocument *doc, QWidget *parent) : KoView(part, doc, parent), m_currentEstimateType( Estimate::Use_Expected ), m_scheduleActionGroup( new QActionGroup( this ) ), m_readWrite( false ), m_defaultView(1), m_partpart (part) { //debugPlan; new Help(KPlatoSettings::contextPath(), KPlatoSettings::contextLanguage()); doc->registerView( this ); setComponentName(Factory::global().componentName(), Factory::global().componentDisplayName()); if ( !doc->isReadWrite() ) setXMLFile( "calligraplan_readonly.rc" ); else setXMLFile( "calligraplan.rc" ); // new ViewAdaptor( this ); m_sp = new QSplitter( this ); QVBoxLayout *layout = new QVBoxLayout( this ); layout->setMargin(0); layout->addWidget( m_sp ); ViewListDocker *docker = 0; if ( mainWindow() == 0 ) { // Don't use docker if embedded m_viewlist = new ViewListWidget(doc, m_sp); m_viewlist->setProject( &( getProject() ) ); connect( m_viewlist, &ViewListWidget::selectionChanged, this, &View::slotSelectionChanged); connect( this, &View::currentScheduleManagerChanged, m_viewlist, &ViewListWidget::setSelectedSchedule); connect( m_viewlist, &ViewListWidget::updateViewInfo, this, &View::slotUpdateViewInfo); } else { ViewListDockerFactory vl(this); docker = static_cast(mainWindow()->createDockWidget(&vl)); if (docker->view() != this) { docker->setView(this); } m_viewlist = docker->viewList(); #if 0 //SchedulesDocker SchedulesDockerFactory sdf; SchedulesDocker *sd = dynamic_cast( createDockWidget( &sdf ) ); Q_ASSERT( sd ); sd->setProject( &getProject() ); connect(sd, SIGNAL(selectionChanged(KPlato::ScheduleManager*)), SLOT(slotSelectionChanged(KPlato::ScheduleManager*))); connect(this, &View::currentScheduleManagerChanged, sd, SLOT(setSelectedSchedule(KPlato::ScheduleManager*))); #endif } m_tab = new QStackedWidget( m_sp ); //////////////////////////////////////////////////////////////////////////////////////////////////// // Add sub views createIntroductionView(); // The menu items // ------ File -/* actionCreateTemplate = new QAction( i18n( "&Create Template From Document..." ), this ); + actionCreateTemplate = new QAction(koIcon("document-save-as-template"), i18n( "Create Project Template..." ), this); actionCollection()->addAction("file_createtemplate", actionCreateTemplate ); connect( actionCreateTemplate, SIGNAL(triggered(bool)), SLOT(slotCreateTemplate()) ); -*/ - actionCreateNewProject = new QAction( i18n( "&Create New Project..." ), this ); + + actionCreateNewProject = new QAction( i18n( "Create New Project..." ), this ); actionCollection()->addAction("file_createnewproject", actionCreateNewProject ); connect( actionCreateNewProject, &QAction::triggered, this, &View::slotCreateNewProject ); // ------ Edit actionCut = actionCollection()->addAction(KStandardAction::Cut, "edit_cut", this, SLOT(slotEditCut())); actionCopy = actionCollection()->addAction(KStandardAction::Copy, "edit_copy", this, SLOT(slotEditCopy())); actionPaste = actionCollection()->addAction(KStandardAction::Paste, "edit_paste", this, SLOT(slotEditPaste())); // ------ View actionCollection()->addAction( KStandardAction::Redisplay, "view_refresh" , this, SLOT(slotRefreshView()) ); actionViewSelector = new KToggleAction(i18n("Show Selector"), this); actionCollection()->addAction("view_show_selector", actionViewSelector ); connect( actionViewSelector, &QAction::triggered, this, &View::slotViewSelector ); // ------ Insert // ------ Project actionEditMainProject = new QAction(koIcon("view-time-schedule-edit"), i18n("Edit Main Project..."), this); actionCollection()->addAction("project_edit", actionEditMainProject ); connect( actionEditMainProject, &QAction::triggered, this, &View::slotProjectEdit ); actionEditStandardWorktime = new QAction(koIcon("configure"), i18n("Define Estimate Conversions..."), this); actionCollection()->addAction("project_worktime", actionEditStandardWorktime ); connect( actionEditStandardWorktime, &QAction::triggered, this, &View::slotProjectWorktime ); actionDefineWBS = new QAction(koIcon("configure"), i18n("Define WBS Pattern..."), this); actionCollection()->addAction("tools_define_wbs", actionDefineWBS ); connect( actionDefineWBS, &QAction::triggered, this, &View::slotDefineWBS ); actionCurrencyConfig = new QAction(koIcon("configure"), i18n("Define Currency..."), this); actionCollection()->addAction( "config_currency", actionCurrencyConfig ); connect( actionCurrencyConfig, &QAction::triggered, this, &View::slotCurrencyConfig ); QAction *actionProjectDescription = new QAction(koIcon("document-edit"), i18n("Edit Description..."), this); actionCollection()->addAction( "edit_project_description", actionProjectDescription ); connect( actionProjectDescription, &QAction::triggered, this, &View::slotOpenProjectDescription ); // ------ Tools actionInsertFile = new QAction(koIcon("document-import"), i18n("Insert Project File..."), this); actionCollection()->addAction("insert_file", actionInsertFile ); connect( actionInsertFile, &QAction::triggered, this, &View::slotInsertFile ); actionLoadSharedProjects = new QAction(koIcon("document-import"), i18n("Load Shared Projects..."), this); actionCollection()->addAction("load_shared_projects", actionLoadSharedProjects ); connect( actionLoadSharedProjects, &QAction::triggered, this, &View::slotLoadSharedProjects ); #ifdef PLAN_USE_KREPORT actionOpenReportFile = new QAction(koIcon("document-open"), i18n("Open Report Definition File..."), this); actionCollection()->addAction( "reportdesigner_open_file", actionOpenReportFile ); connect( actionOpenReportFile, QAction::triggered, this, &View::slotOpenReportFile); #endif // ------ Help actionIntroduction = new QAction(koIcon("dialog-information"), i18n("Introduction to Plan"), this); actionCollection()->addAction("plan_introduction", actionIntroduction ); connect( actionIntroduction, &QAction::triggered, this, &View::slotIntroduction ); // ------ Popup actionOpenNode = new QAction(koIcon("document-edit"), i18n("Edit..."), this); actionCollection()->addAction("node_properties", actionOpenNode ); connect( actionOpenNode, &QAction::triggered, this, &View::slotOpenCurrentNode ); actionTaskProgress = new QAction(koIcon("document-edit"), i18n("Progress..."), this); actionCollection()->addAction("task_progress", actionTaskProgress ); connect( actionTaskProgress, &QAction::triggered, this, &View::slotTaskProgress ); actionDeleteTask = new QAction(koIcon("edit-delete"), i18n("Delete Task"), this); actionCollection()->addAction("delete_task", actionDeleteTask ); connect( actionDeleteTask, &QAction::triggered, this, &View::slotDeleteCurrentTask ); actionTaskDescription = new QAction(koIcon("document-edit"), i18n("Description..."), this); actionCollection()->addAction("task_description", actionTaskDescription ); connect( actionTaskDescription, &QAction::triggered, this, &View::slotTaskDescription ); actionDocuments = new QAction(koIcon("document-edit"), i18n("Documents..."), this); actionCollection()->addAction("task_documents", actionDocuments ); connect( actionDocuments, &QAction::triggered, this, &View::slotDocuments ); actionIndentTask = new QAction(koIcon("format-indent-more"), i18n("Indent Task"), this); actionCollection()->addAction("indent_task", actionIndentTask ); connect( actionIndentTask, &QAction::triggered, this, &View::slotIndentTask ); actionUnindentTask= new QAction(koIcon("format-indent-less"), i18n("Unindent Task"), this); actionCollection()->addAction("unindent_task", actionUnindentTask ); connect( actionUnindentTask, &QAction::triggered, this, &View::slotUnindentTask ); actionMoveTaskUp = new QAction(koIcon("arrow-up"), i18n("Move Task Up"), this); actionCollection()->addAction("move_task_up", actionMoveTaskUp ); connect( actionMoveTaskUp, &QAction::triggered, this, &View::slotMoveTaskUp ); actionMoveTaskDown = new QAction(koIcon("arrow-down"), i18n("Move Task Down"), this); actionCollection()->addAction("move_task_down", actionMoveTaskDown ); connect( actionMoveTaskDown, &QAction::triggered, this, &View::slotMoveTaskDown ); actionEditResource = new QAction(koIcon("document-edit"), i18n("Edit Resource..."), this); actionCollection()->addAction("edit_resource", actionEditResource ); connect( actionEditResource, &QAction::triggered, this, &View::slotEditCurrentResource ); actionEditRelation = new QAction(koIcon("document-edit"), i18n("Edit Dependency..."), this); actionCollection()->addAction("edit_dependency", actionEditRelation ); connect( actionEditRelation, &QAction::triggered, this, &View::slotModifyCurrentRelation ); actionDeleteRelation = new QAction(koIcon("edit-delete"), i18n("Delete Dependency"), this); actionCollection()->addAction("delete_dependency", actionDeleteRelation ); connect( actionDeleteRelation, &QAction::triggered, this, &View::slotDeleteRelation ); // Viewlist popup connect( m_viewlist, &ViewListWidget::createView, this, &View::slotCreateView ); m_workPackageButton = new QToolButton(this); m_workPackageButton->hide(); m_workPackageButton->setIcon(koIcon("application-x-vnd.kde.plan.work")); m_workPackageButton->setText(i18n("Work Packages...")); m_workPackageButton->setToolTip(i18nc("@info:tooltip", "Work packages available")); m_workPackageButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); connect(m_workPackageButton, &QToolButton::clicked, this, &View::openWorkPackageMergeDialog); m_estlabel = new QLabel( "", 0 ); if ( statusBar() ) { addStatusBarItem( m_estlabel, 0, true ); } connect( &getProject(), &Project::scheduleManagerAdded, this, &View::slotScheduleAdded ); connect( &getProject(), &Project::scheduleManagerRemoved, this, &View::slotScheduleRemoved ); connect( &getProject(), &Project::scheduleManagersSwapped, this, &View::slotScheduleSwapped ); connect( &getProject(), &Project::sigCalculationFinished, this, &View::slotScheduleCalculated ); slotPlugScheduleActions(); connect( doc, &MainDocument::changed, this, &View::slotUpdate ); connect( m_scheduleActionGroup, &QActionGroup::triggered, this, &View::slotViewSchedule ); connect( getPart(), &MainDocument::workPackageLoaded, this, &View::slotWorkPackageLoaded ); // views take time for large projects QTimer::singleShot(0, this, &View::initiateViews); const QList pluginFactories = KoPluginLoader::instantiatePluginFactories(QStringLiteral("calligraplan/extensions")); foreach (KPluginFactory* factory, pluginFactories) { QObject *object = factory->create(this, QVariantList()); KXMLGUIClient *clientPlugin = dynamic_cast(object); if (clientPlugin) { insertChildClient(clientPlugin); } else { // not our/valid plugin, so delete the created object object->deleteLater(); } } //debugPlan<<" end"; } View::~View() { // Disconnect and delete so we do not get called by destroyed() signal const QMap map = m_scheduleActions; // clazy:exclude=qmap-with-pointer-key QMap::const_iterator it; for (it = map.constBegin(); it != map.constEnd(); ++it) { disconnect(it.key(), &QObject::destroyed, this, &View::slotActionDestroyed); m_scheduleActionGroup->removeAction(it.key()); delete it.key(); } ViewBase *view = currentView(); if (view) { // deactivate view to remove dockers etc slotGuiActivated(view, false); } /* removeStatusBarItem( m_estlabel ); delete m_estlabel;*/ } void View::initiateViews() { QApplication::setOverrideCursor( Qt::WaitCursor ); createViews(); connect( m_viewlist, &ViewListWidget::activated, this, &View::slotViewActivated ); // after createViews() !! connect( m_viewlist, &ViewListWidget::viewListItemRemoved, this, &View::slotViewListItemRemoved ); // after createViews() !! connect( m_viewlist, &ViewListWidget::viewListItemInserted, this, &View::slotViewListItemInserted ); ViewListDocker *docker = qobject_cast( m_viewlist->parent() ); if ( docker ) { // after createViews() !! connect( m_viewlist, &ViewListWidget::modified, docker, &ViewListDocker::slotModified); connect( m_viewlist, &ViewListWidget::modified, getPart(), &MainDocument::slotViewlistModified); connect(getPart(), &MainDocument::viewlistModified, docker, &ViewListDocker::updateWindowTitle); } connect( m_tab, &QStackedWidget::currentChanged, this, &View::slotCurrentChanged ); slotSelectDefaultView(); loadContext(); QApplication::restoreOverrideCursor(); } void View::slotCreateNewProject() { debugPlan; if ( KMessageBox::Continue == KMessageBox::warningContinueCancel( this, xi18nc( "@info", "This action cannot be undone." "Create a new Project from the current project " "with new project- and task identities." "Resource- and calendar identities are not changed." "All scheduling information is removed." "Do you want to continue?" ) ) ) { emit currentScheduleManagerChanged(0); getPart()->createNewProject(); slotOpenNode( &getProject() ); } } +void View::slotCreateTemplate() +{ + debugPlan; + KoFileDialog dlg(nullptr, KoFileDialog::SaveFile, "Create Template"); + dlg.setNameFilters(QStringList()<<"Plan Template (*.plant)"); + QString file = dlg.filename(); + if (!file.isEmpty()) { + QUrl url = QUrl::fromUserInput(file); + koDocument()->exportDocument(url); + qInfo()<context(); if ( ctx && ctx->isLoaded() ) { debugPlan<<"isLoaded"; KoXmlNode n = ctx->context().namedItem( "categories" ); if ( n.isNull() ) { warnPlan<<"No categories"; } else { n = n.firstChild(); for ( ; ! n.isNull(); n = n.nextSibling() ) { if ( ! n.isElement() ) { continue; } KoXmlElement e = n.toElement(); if (e.tagName() != "category") { continue; } debugPlan<<"category: "<addCategory( ct, cn ); KoXmlNode n1 = e.firstChild(); for ( ; ! n1.isNull(); n1 = n1.nextSibling() ) { if ( ! n1.isElement() ) { continue; } KoXmlElement e1 = n1.toElement(); if (e1.tagName() != "view") { continue; } ViewBase *v = 0; QString type = e1.attribute( "viewtype" ); QString tag = e1.attribute( "tag" ); QString name = e1.attribute( "name" ); QString tip = e1.attribute( "tooltip" ); v = createView( cat, type, tag, name, tip ); //KoXmlNode settings = e1.namedItem( "settings " ); ???? KoXmlNode settings = e1.firstChild(); for ( ; ! settings.isNull(); settings = settings.nextSibling() ) { if ( settings.nodeName() == "settings" ) { break; } } if ( v && settings.isElement() ) { debugPlan<<" settings"; v->loadContext( settings.toElement() ); } } } } } else { debugPlan<<"Default"; ViewBase *v = 0; ViewListItem *cat; QString ct = "Editors"; cat = m_viewlist->addCategory( ct, defaultCategoryInfo( ct ).name ); createCalendarEditor( cat, "CalendarEditor", QString(), TIP_USE_DEFAULT_TEXT ); createAccountsEditor( cat, "AccountsEditor", QString(), TIP_USE_DEFAULT_TEXT ); v = createResourceEditor( cat, "ResourceEditor", QString(), TIP_USE_DEFAULT_TEXT ); v = createTaskEditor( cat, "TaskEditor", QString(), TIP_USE_DEFAULT_TEXT ); m_defaultView = m_tab->count() - 1; v->showColumns(QList() << NodeModel::NodeName << NodeModel::NodeType << NodeModel::NodeAllocation << NodeModel::NodeEstimateCalendar << NodeModel::NodeEstimate << NodeModel::NodeOptimisticRatio << NodeModel::NodePessimisticRatio << NodeModel::NodeRisk << NodeModel::NodeResponsible << NodeModel::NodeDescription ); v = createTaskEditor( cat, "TaskConstraintEditor", i18n("Task Constraints"), i18n("Edit task scheduling constraints") ); v->showColumns(QList() << NodeModel::NodeName << NodeModel::NodeType << NodeModel::NodePriority << NodeModel::NodeConstraint << NodeModel::NodeConstraintStart << NodeModel::NodeConstraintEnd << NodeModel::NodeDescription ); v = createTaskEditor( cat, "TaskCostEditor", i18n("Task Cost"), i18n("Edit task cost") ); v->showColumns(QList() << NodeModel::NodeName << NodeModel::NodeType << NodeModel::NodeRunningAccount << NodeModel::NodeStartupAccount << NodeModel::NodeStartupCost << NodeModel::NodeShutdownAccount << NodeModel::NodeShutdownCost << NodeModel::NodeDescription ); createDependencyEditor( cat, "DependencyEditor", QString(), TIP_USE_DEFAULT_TEXT ); // Do not show by default // createPertEditor( cat, "PertEditor", QString(), TIP_USE_DEFAULT_TEXT ); createScheduleHandler( cat, "ScheduleHandlerView", QString(), TIP_USE_DEFAULT_TEXT ); ct = "Views"; cat = m_viewlist->addCategory( ct, defaultCategoryInfo( ct ).name ); createGanttView( cat, "GanttView", QString(), TIP_USE_DEFAULT_TEXT ); createMilestoneGanttView( cat, "MilestoneGanttView", QString(), TIP_USE_DEFAULT_TEXT ); createResourceAppointmentsView( cat, "ResourceAppointmentsView", QString(), TIP_USE_DEFAULT_TEXT ); createResourceAppointmentsGanttView( cat, "ResourceAppointmentsGanttView", QString(), TIP_USE_DEFAULT_TEXT ); createAccountsView( cat, "AccountsView", QString(), TIP_USE_DEFAULT_TEXT ); ct = "Execution"; cat = m_viewlist->addCategory( ct, defaultCategoryInfo( ct ).name ); createProjectStatusView( cat, "ProjectStatusView", QString(), TIP_USE_DEFAULT_TEXT ); createPerformanceStatusView( cat, "PerformanceStatusView", QString(), TIP_USE_DEFAULT_TEXT ); v = createTaskStatusView( cat, "TaskStatusView", QString(), TIP_USE_DEFAULT_TEXT ); v = createTaskView( cat, "TaskView", QString(), TIP_USE_DEFAULT_TEXT ); v = createTaskWorkPackageView( cat, "TaskWorkPackageView", QString(), TIP_USE_DEFAULT_TEXT ); ct = "Reports"; cat = m_viewlist->addCategory(ct, defaultCategoryInfo(ct).name); createReportsGeneratorView(cat, "ReportsGeneratorView", i18n("Generate reports"), TIP_USE_DEFAULT_TEXT); #ifdef PLAN_USE_KREPORT // Let user add reports explicitly, we prefer reportsgenerator now // A little hack to get the user started... #if 0 ReportView *rv = qobject_cast( createReportView( cat, "ReportView", i18n( "Task Status Report" ), TIP_USE_DEFAULT_TEXT ) ); if ( rv ) { QDomDocument doc; doc.setContent( standardTaskStatusReport() ); rv->loadXML( doc ); } #endif #endif } } ViewBase *View::createView( ViewListItem *cat, const QString &type, const QString &tag, const QString &name, const QString &tip, int index ) { ViewBase *v = 0; //NOTE: type is the same as classname (so if it is changed...) if ( type == "CalendarEditor" ) { v = createCalendarEditor( cat, tag, name, tip, index ); } else if ( type == "AccountsEditor" ) { v = createAccountsEditor( cat, tag, name, tip, index ); } else if ( type == "ResourceEditor" ) { v = createResourceEditor( cat, tag, name, tip, index ); } else if ( type == "TaskEditor" ) { v = createTaskEditor( cat, tag, name, tip, index ); } else if ( type == "DependencyEditor" ) { v = createDependencyEditor( cat, tag, name, tip, index ); } else if ( type == "PertEditor" ) { v = createPertEditor( cat, tag, name, tip, index ); } else if ( type == "ScheduleEditor" ) { v = createScheduleEditor( cat, tag, name, tip, index ); } else if ( type == "ScheduleHandlerView" ) { v = createScheduleHandler( cat, tag, name, tip, index ); } else if ( type == "ProjectStatusView" ) { v = createProjectStatusView( cat, tag, name, tip, index ); } else if ( type == "TaskStatusView" ) { v = createTaskStatusView( cat, tag, name, tip, index ); } else if ( type == "TaskView" ) { v = createTaskView( cat, tag, name, tip, index ); } else if ( type == "TaskWorkPackageView" ) { v = createTaskWorkPackageView( cat, tag, name, tip, index ); } else if ( type == "GanttView" ) { v = createGanttView( cat, tag, name, tip, index ); } else if ( type == "MilestoneGanttView" ) { v = createMilestoneGanttView( cat, tag, name, tip, index ); } else if ( type == "ResourceAppointmentsView" ) { v = createResourceAppointmentsView( cat, tag, name, tip, index ); } else if ( type == "ResourceAppointmentsGanttView" ) { v = createResourceAppointmentsGanttView( cat, tag, name, tip, index ); } else if ( type == "AccountsView" ) { v = createAccountsView( cat, tag, name, tip, index ); } else if ( type == "PerformanceStatusView" ) { v = createPerformanceStatusView( cat, tag, name, tip, index ); } else if ( type == "ReportsGeneratorView" ) { v = createReportsGeneratorView(cat, tag, name, tip, index); } else if ( type == "ReportView" ) { #ifdef PLAN_USE_KREPORT v = createReportView( cat, tag, name, tip, index ); #endif } else { warnPlan<<"Unknown viewtype: "<type() == ViewListItem::ItemType_SubView ) { itm->setViewInfo( defaultViewInfo( itm->viewType() ) ); } else if ( itm->type() == ViewListItem::ItemType_Category ) { ViewInfo vi = defaultCategoryInfo( itm->tag() ); itm->setViewInfo( vi ); } } ViewInfo View::defaultViewInfo( const QString &type ) const { ViewInfo vi; if ( type == "CalendarEditor" ) { vi.name = i18n( "Work & Vacation" ); vi.tip = xi18nc( "@info:tooltip", "Edit working- and vacation days for resources" ); } else if ( type == "AccountsEditor" ) { vi.name = i18n( "Cost Breakdown Structure" ); vi.tip = xi18nc( "@info:tooltip", "Edit cost breakdown structure." ); } else if ( type == "ResourceEditor" ) { vi.name = i18n( "Resources" ); vi.tip = xi18nc( "@info:tooltip", "Edit resource breakdown structure" ); } else if ( type == "TaskEditor" ) { vi.name = i18n( "Tasks" ); vi.tip = xi18nc( "@info:tooltip", "Edit work breakdown structure" ); } else if ( type == "DependencyEditor" ) { vi.name = i18n( "Dependencies (Graphic)" ); vi.tip = xi18nc( "@info:tooltip", "Edit task dependencies" ); } else if ( type == "PertEditor" ) { vi.name = i18n( "Dependencies (List)" ); vi.tip = xi18nc( "@info:tooltip", "Edit task dependencies" ); } else if ( type == "ScheduleEditor" ) { // This view is not used stand-alone atm vi.name = i18n( "Schedules" ); } else if ( type == "ScheduleHandlerView" ) { vi.name = i18n( "Schedules" ); vi.tip = xi18nc( "@info:tooltip", "Calculate and analyze project schedules" ); } else if ( type == "ProjectStatusView" ) { vi.name = i18n( "Project Performance Chart" ); vi.tip = xi18nc( "@info:tooltip", "View project status information" ); } else if ( type == "TaskStatusView" ) { vi.name = i18n( "Task Status" ); vi.tip = xi18nc( "@info:tooltip", "View task progress information" ); } else if ( type == "TaskView" ) { vi.name = i18n( "Task Execution" ); vi.tip = xi18nc( "@info:tooltip", "View task execution information" ); } else if ( type == "TaskWorkPackageView" ) { vi.name = i18n( "Work Package View" ); vi.tip = xi18nc( "@info:tooltip", "View task work package information" ); } else if ( type == "GanttView" ) { vi.name = i18n( "Gantt" ); vi.tip = xi18nc( "@info:tooltip", "View Gantt chart" ); } else if ( type == "MilestoneGanttView" ) { vi.name = i18n( "Milestone Gantt" ); vi.tip = xi18nc( "@info:tooltip", "View milestone Gantt chart" ); } else if ( type == "ResourceAppointmentsView" ) { vi.name = i18n( "Resource Assignments" ); vi.tip = xi18nc( "@info:tooltip", "View resource assignments in a table" ); } else if ( type == "ResourceAppointmentsGanttView" ) { vi.name = i18n( "Resource Assignments (Gantt)" ); vi.tip = xi18nc( "@info:tooltip", "View resource assignments in Gantt chart" ); } else if ( type == "AccountsView" ) { vi.name = i18n( "Cost Breakdown" ); vi.tip = xi18nc( "@info:tooltip", "View planned and actual cost" ); } else if ( type == "PerformanceStatusView" ) { vi.name = i18n( "Tasks Performance Chart" ); vi.tip = xi18nc( "@info:tooltip", "View tasks performance status information" ); } else if ( type == "ReportsGeneratorView" ) { vi.name = i18n( "Reports Generator" ); vi.tip = xi18nc( "@info:tooltip", "Generate reports" ); } else if ( type == "ReportView" ) { vi.name = i18n( "Report" ); vi.tip = xi18nc( "@info:tooltip", "View report" ); } else { warnPlan<<"Unknown viewtype: "<count()-1) : m_visitedViews.at(m_visitedViews.count() - 2); debugPlan<<"Prev:"<setCurrentIndex(view); return; } if ( url.url().startsWith( QLatin1String( "about:plan" ) ) ) { getPart()->aboutPage().generatePage( v->htmlPart(), url ); return; } } if ( url.scheme() == QLatin1String("help") ) { KHelpClient::invokeHelp( "", url.fileName() ); return; } // try to open the url debugPlan<htmlPart().setJScriptEnabled(false); v->htmlPart().setJavaEnabled(false); v->htmlPart().setMetaRefreshEnabled(false); v->htmlPart().setPluginsEnabled(false); slotOpenUrlRequest( v, QUrl( "about:plan/main" ) ); connect( v, &HtmlView::openUrlRequest, this, &View::slotOpenUrlRequest ); m_tab->addWidget( v ); return v; } ViewBase *View::createResourceAppointmentsGanttView( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index ) { ResourceAppointmentsGanttView *v = new ResourceAppointmentsGanttView(getKoPart(), getPart(), m_tab ); m_tab->addWidget( v ); ViewListItem *i = m_viewlist->addView( cat, tag, name, v, getPart(), "", index ); ViewInfo vi = defaultViewInfo( "ResourceAppointmentsGanttView" ); if ( name.isEmpty() ) { i->setText( 0, vi.name ); } if ( tip == TIP_USE_DEFAULT_TEXT ) { i->setToolTip( 0, vi.tip ); } else { i->setToolTip( 0, tip ); } connect( v, &ViewBase::guiActivated, this, &View::slotGuiActivated ); connect( this, &View::currentScheduleManagerChanged, v, &ResourceAppointmentsGanttView::setScheduleManager); connect( v, &ResourceAppointmentsGanttView::requestPopupMenu, this, &View::slotPopupMenuRequested); v->setProject( &( getProject() ) ); v->setScheduleManager( currentScheduleManager() ); v->updateReadWrite( m_readWrite ); return v; } ViewBase *View::createResourceAppointmentsView( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index ) { ResourceAppointmentsView *v = new ResourceAppointmentsView(getKoPart(), getPart(), m_tab ); m_tab->addWidget( v ); ViewListItem *i = m_viewlist->addView( cat, tag, name, v, getPart(), "", index ); ViewInfo vi = defaultViewInfo( "ResourceAppointmentsView" ); if ( name.isEmpty() ) { i->setText( 0, vi.name ); } if ( tip == TIP_USE_DEFAULT_TEXT ) { i->setToolTip( 0, vi.tip ); } else { i->setToolTip( 0, tip ); } connect( v, &ViewBase::guiActivated, this, &View::slotGuiActivated ); connect( this, &View::currentScheduleManagerChanged, v, &ResourceAppointmentsView::setScheduleManager); connect( v, &ResourceAppointmentsView::requestPopupMenu, this, &View::slotPopupMenuRequested); v->setProject( &( getProject() ) ); v->setScheduleManager( currentScheduleManager() ); v->updateReadWrite( m_readWrite ); return v; } ViewBase *View::createResourceEditor( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index ) { ResourceEditor *resourceeditor = new ResourceEditor(getKoPart(), getPart(), m_tab ); resourceeditor->setViewSplitMode(false); m_tab->addWidget( resourceeditor ); resourceeditor->setProject( &(getProject()) ); ViewListItem *i = m_viewlist->addView( cat, tag, name, resourceeditor, getPart(), "", index ); ViewInfo vi = defaultViewInfo( "ResourceEditor" ); if ( name.isEmpty() ) { i->setText( 0, vi.name ); } if ( tip == TIP_USE_DEFAULT_TEXT ) { i->setToolTip( 0, vi.tip ); } else { i->setToolTip( 0, tip ); } connect( resourceeditor, &ViewBase::guiActivated, this, &View::slotGuiActivated ); connect( resourceeditor, &ResourceEditor::deleteObjectList, this, &View::slotDeleteResourceObjects ); connect( resourceeditor, &ResourceEditor::requestPopupMenu, this, &View::slotPopupMenuRequested); resourceeditor->updateReadWrite( m_readWrite ); return resourceeditor; } ViewBase *View::createTaskEditor( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index ) { TaskEditor *taskeditor = new TaskEditor(getKoPart(), getPart(), m_tab ); taskeditor->setViewSplitMode(false); m_tab->addWidget( taskeditor ); ViewListItem *i = m_viewlist->addView( cat, tag, name, taskeditor, getPart(), "", index ); ViewInfo vi = defaultViewInfo( "TaskEditor" ); if ( name.isEmpty() ) { i->setText( 0, vi.name ); } if ( tip == TIP_USE_DEFAULT_TEXT ) { i->setToolTip( 0, vi.tip ); } else { i->setToolTip( 0, tip ); } taskeditor->setProject( &(getProject()) ); taskeditor->setScheduleManager( currentScheduleManager() ); connect( this, &View::currentScheduleManagerChanged, taskeditor, &TaskEditor::setScheduleManager); connect( taskeditor, &ViewBase::guiActivated, this, &View::slotGuiActivated ); connect( taskeditor, &TaskEditor::addTask, this, &View::slotAddTask ); connect( taskeditor, &TaskEditor::addMilestone, this, &View::slotAddMilestone ); connect( taskeditor, &TaskEditor::addSubtask, this, &View::slotAddSubTask ); connect( taskeditor, &TaskEditor::addSubMilestone, this, &View::slotAddSubMilestone ); connect(taskeditor, &TaskEditor::deleteTaskList, this, &View::slotDeleteTaskList); connect( taskeditor, &TaskEditor::moveTaskUp, this, &View::slotMoveTaskUp ); connect( taskeditor, &TaskEditor::moveTaskDown, this, &View::slotMoveTaskDown ); connect( taskeditor, &TaskEditor::indentTask, this, &View::slotIndentTask ); connect( taskeditor, &TaskEditor::unindentTask, this, &View::slotUnindentTask ); connect(taskeditor, &TaskEditor::saveTaskModule, this, &View::saveTaskModule); connect(taskeditor, &TaskEditor::removeTaskModule, this, &View::removeTaskModule); connect(taskeditor, &ViewBase::openDocument, static_cast(m_partpart), &Part::openTaskModule); connect( taskeditor, &TaskEditor::requestPopupMenu, this, &View::slotPopupMenuRequested); connect( taskeditor, &TaskEditor::openTaskDescription, this, &View::slotOpenTaskDescription); taskeditor->updateReadWrite( m_readWrite ); return taskeditor; } ViewBase *View::createAccountsEditor( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index ) { AccountsEditor *ae = new AccountsEditor(getKoPart(), getPart(), m_tab ); m_tab->addWidget( ae ); ViewListItem *i = m_viewlist->addView( cat, tag, name, ae, getPart(), "", index ); ViewInfo vi = defaultViewInfo( "AccountsEditor" ); if ( name.isEmpty() ) { i->setText( 0, vi.name ); } if ( tip == TIP_USE_DEFAULT_TEXT ) { i->setToolTip( 0, vi.tip ); } else { i->setToolTip( 0, tip ); } ae->draw( getProject() ); connect( ae, &ViewBase::guiActivated, this, &View::slotGuiActivated ); ae->updateReadWrite( m_readWrite ); return ae; } ViewBase *View::createCalendarEditor( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index ) { CalendarEditor *calendareditor = new CalendarEditor(getKoPart(), getPart(), m_tab ); m_tab->addWidget( calendareditor ); ViewListItem *i = m_viewlist->addView( cat, tag, name, calendareditor, getPart(), "", index ); ViewInfo vi = defaultViewInfo( "CalendarEditor" ); if ( name.isEmpty() ) { i->setText( 0, vi.name ); } if ( tip == TIP_USE_DEFAULT_TEXT ) { i->setToolTip( 0, vi.tip ); } else { i->setToolTip( 0, tip ); } calendareditor->draw( getProject() ); connect( calendareditor, &ViewBase::guiActivated, this, &View::slotGuiActivated ); connect( calendareditor, &CalendarEditor::requestPopupMenu, this, &View::slotPopupMenuRequested); calendareditor->updateReadWrite( m_readWrite ); return calendareditor; } ViewBase *View::createScheduleHandler( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index ) { ScheduleHandlerView *handler = new ScheduleHandlerView(getKoPart(), getPart(), m_tab ); m_tab->addWidget( handler ); ViewListItem *i = m_viewlist->addView( cat, tag, name, handler, getPart(), "", index ); ViewInfo vi = defaultViewInfo( "ScheduleHandlerView" ); if ( name.isEmpty() ) { i->setText( 0, vi.name ); } if ( tip == TIP_USE_DEFAULT_TEXT ) { i->setToolTip( 0, vi.tip ); } else { i->setToolTip( 0, tip ); } connect( handler->scheduleEditor(), &ScheduleEditor::addScheduleManager, this, &View::slotAddScheduleManager ); connect( handler->scheduleEditor(), &ScheduleEditor::deleteScheduleManager, this, &View::slotDeleteScheduleManager ); connect( handler->scheduleEditor(), &ScheduleEditor::moveScheduleManager, this, &View::slotMoveScheduleManager); connect( handler->scheduleEditor(), &ScheduleEditor::calculateSchedule, this, &View::slotCalculateSchedule ); connect( handler->scheduleEditor(), &ScheduleEditor::baselineSchedule, this, &View::slotBaselineSchedule ); connect( handler, &ViewBase::guiActivated, this, &View::slotGuiActivated ); connect( this, &View::currentScheduleManagerChanged, handler, &ScheduleHandlerView::currentScheduleManagerChanged ); connect( handler, &ScheduleHandlerView::requestPopupMenu, this, &View::slotPopupMenuRequested); connect(handler, &ScheduleHandlerView::editNode, this, &View::slotOpenNode); connect(handler, &ScheduleHandlerView::editResource, this, &View::slotEditResource); handler->draw( getProject() ); handler->updateReadWrite( m_readWrite ); return handler; } ScheduleEditor *View::createScheduleEditor( QWidget *parent ) { ScheduleEditor *scheduleeditor = new ScheduleEditor(getKoPart(), getPart(), parent ); connect( scheduleeditor, &ScheduleEditor::addScheduleManager, this, &View::slotAddScheduleManager ); connect( scheduleeditor, &ScheduleEditor::deleteScheduleManager, this, &View::slotDeleteScheduleManager ); connect( scheduleeditor, &ScheduleEditor::calculateSchedule, this, &View::slotCalculateSchedule ); connect( scheduleeditor, &ScheduleEditor::baselineSchedule, this, &View::slotBaselineSchedule ); scheduleeditor->updateReadWrite( m_readWrite ); return scheduleeditor; } ViewBase *View::createScheduleEditor( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index ) { ScheduleEditor *scheduleeditor = new ScheduleEditor(getKoPart(), getPart(), m_tab ); m_tab->addWidget( scheduleeditor ); ViewListItem *i = m_viewlist->addView( cat, tag, name, scheduleeditor, getPart(), "", index ); ViewInfo vi = defaultViewInfo( "ScheduleEditor" ); if ( name.isEmpty() ) { i->setText( 0, vi.name ); } if ( tip == TIP_USE_DEFAULT_TEXT ) { i->setToolTip( 0, vi.tip ); } else { i->setToolTip( 0, tip ); } scheduleeditor->setProject( &( getProject() ) ); connect( scheduleeditor, &ViewBase::guiActivated, this, &View::slotGuiActivated ); connect( scheduleeditor, &ScheduleEditor::addScheduleManager, this, &View::slotAddScheduleManager ); connect( scheduleeditor, &ScheduleEditor::deleteScheduleManager, this, &View::slotDeleteScheduleManager ); connect( scheduleeditor, &ScheduleEditor::calculateSchedule, this, &View::slotCalculateSchedule ); connect( scheduleeditor, &ScheduleEditor::baselineSchedule, this, &View::slotBaselineSchedule ); scheduleeditor->updateReadWrite( m_readWrite ); return scheduleeditor; } ViewBase *View::createDependencyEditor( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index ) { DependencyEditor *editor = new DependencyEditor(getKoPart(), getPart(), m_tab ); m_tab->addWidget( editor ); ViewListItem *i = m_viewlist->addView( cat, tag, name, editor, getPart(), "", index ); ViewInfo vi = defaultViewInfo( "DependencyEditor" ); if ( name.isEmpty() ) { i->setText( 0, vi.name ); } if ( tip == TIP_USE_DEFAULT_TEXT ) { i->setToolTip( 0, vi.tip ); } else { i->setToolTip( 0, tip ); } editor->draw( getProject() ); connect( editor, &ViewBase::guiActivated, this, &View::slotGuiActivated ); connect( editor, &DependencyEditor::addRelation, this, &View::slotAddRelation); connect( editor, &DependencyEditor::modifyRelation, this, &View::slotModifyRelation); connect( editor, &DependencyEditor::editRelation, this, &View::slotEditRelation); connect( editor, &DependencyEditor::editNode, this, &View::slotOpenNode); connect( editor, &DependencyEditor::addTask, this, &View::slotAddTask ); connect( editor, &DependencyEditor::addMilestone, this, &View::slotAddMilestone ); connect( editor, &DependencyEditor::addSubMilestone, this, &View::slotAddSubMilestone ); connect( editor, &DependencyEditor::addSubtask, this, &View::slotAddSubTask ); connect( editor, &DependencyEditor::deleteTaskList, this, &View::slotDeleteTaskList); connect( this, &View::currentScheduleManagerChanged, editor, &DependencyEditor::setScheduleManager); connect( editor, &DependencyEditor::requestPopupMenu, this, &View::slotPopupMenuRequested); editor->updateReadWrite( m_readWrite ); editor->setScheduleManager( currentScheduleManager() ); return editor; } ViewBase *View::createPertEditor( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index ) { PertEditor *perteditor = new PertEditor(getKoPart(), getPart(), m_tab ); m_tab->addWidget( perteditor ); ViewListItem *i = m_viewlist->addView( cat, tag, name, perteditor, getPart(), "", index ); ViewInfo vi = defaultViewInfo( "PertEditor" ); if ( name.isEmpty() ) { i->setText( 0, vi.name ); } if ( tip == TIP_USE_DEFAULT_TEXT ) { i->setToolTip( 0, vi.tip ); } else { i->setToolTip( 0, tip ); } perteditor->draw( getProject() ); connect( perteditor, &ViewBase::guiActivated, this, &View::slotGuiActivated ); m_updatePertEditor = true; perteditor->updateReadWrite( m_readWrite ); return perteditor; } ViewBase *View::createProjectStatusView( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index ) { ProjectStatusView *v = new ProjectStatusView(getKoPart(), getPart(), m_tab ); m_tab->addWidget( v ); ViewListItem *i = m_viewlist->addView( cat, tag, name, v, getPart(), "", index ); ViewInfo vi = defaultViewInfo( "ProjectStatusView" ); if ( name.isEmpty() ) { i->setText( 0, vi.name ); } if ( tip == TIP_USE_DEFAULT_TEXT ) { i->setToolTip( 0, vi.tip ); } else { i->setToolTip( 0, tip ); } connect( v, &ViewBase::guiActivated, this, &View::slotGuiActivated ); connect( this, &View::currentScheduleManagerChanged, v, &ProjectStatusView::setScheduleManager); v->updateReadWrite( m_readWrite ); v->setProject( &getProject() ); v->setScheduleManager( currentScheduleManager() ); return v; } ViewBase *View::createPerformanceStatusView( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index ) { PerformanceStatusView *v = new PerformanceStatusView(getKoPart(), getPart(), m_tab ); m_tab->addWidget( v ); ViewListItem *i = m_viewlist->addView( cat, tag, name, v, getPart(), "", index ); ViewInfo vi = defaultViewInfo( "PerformanceStatusView" ); if ( name.isEmpty() ) { i->setText( 0, vi.name ); } if ( tip == TIP_USE_DEFAULT_TEXT ) { i->setToolTip( 0, vi.tip ); } else { i->setToolTip( 0, tip ); } connect( v, &ViewBase::guiActivated, this, &View::slotGuiActivated ); connect( this, &View::currentScheduleManagerChanged, v, &PerformanceStatusView::setScheduleManager); connect( v, &PerformanceStatusView::requestPopupMenu, this, &View::slotPopupMenuRequested); v->updateReadWrite( m_readWrite ); v->setProject( &getProject() ); v->setScheduleManager( currentScheduleManager() ); return v; } ViewBase *View::createTaskStatusView( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index ) { TaskStatusView *taskstatusview = new TaskStatusView(getKoPart(), getPart(), m_tab ); taskstatusview->setViewSplitMode(false); m_tab->addWidget( taskstatusview ); ViewListItem *i = m_viewlist->addView( cat, tag, name, taskstatusview, getPart(), "", index ); ViewInfo vi = defaultViewInfo( "TaskStatusView" ); if ( name.isEmpty() ) { i->setText( 0, vi.name ); } if ( tip == TIP_USE_DEFAULT_TEXT ) { i->setToolTip( 0, vi.tip ); } else { i->setToolTip( 0, tip ); } connect( taskstatusview, &ViewBase::guiActivated, this, &View::slotGuiActivated ); connect( this, &View::currentScheduleManagerChanged, taskstatusview, &TaskStatusView::setScheduleManager); connect( taskstatusview, &TaskStatusView::requestPopupMenu, this, &View::slotPopupMenuRequested); connect( taskstatusview, &TaskStatusView::openTaskDescription, this, &View::slotOpenTaskDescription); taskstatusview->updateReadWrite( m_readWrite ); taskstatusview->draw( getProject() ); taskstatusview->setScheduleManager( currentScheduleManager() ); return taskstatusview; } ViewBase *View::createTaskView( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index ) { TaskView *v = new TaskView(getKoPart(), getPart(), m_tab ); v->setViewSplitMode(false); m_tab->addWidget( v ); ViewListItem *i = m_viewlist->addView( cat, tag, name, v, getPart(), "", index ); ViewInfo vi = defaultViewInfo( "TaskView" ); if ( name.isEmpty() ) { i->setText( 0, vi.name ); } if ( tip == TIP_USE_DEFAULT_TEXT ) { i->setToolTip( 0, vi.tip ); } else { i->setToolTip( 0, tip ); } v->draw( getProject() ); v->setScheduleManager( currentScheduleManager() ); connect( this, &View::currentScheduleManagerChanged, v, &TaskView::setScheduleManager); connect( v, &ViewBase::guiActivated, this, &View::slotGuiActivated ); connect( v, &TaskView::requestPopupMenu, this, &View::slotPopupMenuRequested); connect( v, &TaskView::openTaskDescription, this, &View::slotOpenTaskDescription); v->updateReadWrite( m_readWrite ); return v; } ViewBase *View::createTaskWorkPackageView( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index ) { TaskWorkPackageView *v = new TaskWorkPackageView(getKoPart(), getPart(), m_tab ); v->setViewSplitMode(false); m_tab->addWidget( v ); ViewListItem *i = m_viewlist->addView( cat, tag, name, v, getPart(), "", index ); ViewInfo vi = defaultViewInfo( "TaskWorkPackageView" ); if ( name.isEmpty() ) { i->setText( 0, vi.name ); } if ( tip == TIP_USE_DEFAULT_TEXT ) { i->setToolTip( 0, vi.tip ); } else { i->setToolTip( 0, tip ); } v->setProject( &getProject() ); v->setScheduleManager( currentScheduleManager() ); connect( this, &View::currentScheduleManagerChanged, v, &TaskWorkPackageView::setScheduleManager); connect( v, &ViewBase::guiActivated, this, &View::slotGuiActivated ); connect( v, &TaskWorkPackageView::requestPopupMenu, this, &View::slotPopupMenuRequested); connect( v, &TaskWorkPackageView::mailWorkpackage, this, &View::slotMailWorkpackage ); connect( v, &TaskWorkPackageView::publishWorkpackages, this, &View::slotPublishWorkpackages ); connect(v, &TaskWorkPackageView::openWorkpackages, this, &View::openWorkPackageMergeDialog); connect(this, &View::workPackagesAvailable, v, &TaskWorkPackageView::slotWorkpackagesAvailable); connect(v, &TaskWorkPackageView::checkForWorkPackages, getPart(), &MainDocument::checkForWorkPackages); connect(v, &TaskWorkPackageView::loadWorkPackageUrl, this, &View::loadWorkPackage); connect(v, &TaskWorkPackageView::openTaskDescription, this, &View::slotOpenTaskDescription); v->updateReadWrite( m_readWrite ); return v; } ViewBase *View::createGanttView( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index ) { GanttView *ganttview = new GanttView(getKoPart(), getPart(), m_tab, koDocument()->isReadWrite() ); m_tab->addWidget( ganttview ); ViewListItem *i = m_viewlist->addView( cat, tag, name, ganttview, getPart(), "", index ); ViewInfo vi = defaultViewInfo( "GanttView" ); if ( name.isEmpty() ) { i->setText( 0, vi.name ); } if ( tip == TIP_USE_DEFAULT_TEXT ) { i->setToolTip( 0, vi.tip ); } else { i->setToolTip( 0, tip ); } ganttview->setProject( &( getProject() ) ); ganttview->setScheduleManager( currentScheduleManager() ); connect( ganttview, &ViewBase::guiActivated, this, &View::slotGuiActivated ); /* TODO: Review these connect( ganttview, SIGNAL(addRelation(KPlato::Node*,KPlato::Node*,int)), SLOT(slotAddRelation(KPlato::Node*,KPlato::Node*,int)) ); connect( ganttview, SIGNAL(modifyRelation(KPlato::Relation*,int)), SLOT(slotModifyRelation(KPlato::Relation*,int)) ); connect( ganttview, SIGNAL(modifyRelation(KPlato::Relation*)), SLOT(slotModifyRelation(KPlato::Relation*)) ); connect( ganttview, SIGNAL(itemDoubleClicked()), SLOT(slotOpenNode()) ); connect( ganttview, SIGNAL(itemRenamed(KPlato::Node*,QString)), this, SLOT(slotRenameNode(KPlato::Node*,QString)) );*/ connect( this, &View::currentScheduleManagerChanged, ganttview, &GanttView::setScheduleManager); connect( ganttview, &GanttView::requestPopupMenu, this, &View::slotPopupMenuRequested); connect( ganttview, &GanttView::openTaskDescription, this, &View::slotOpenTaskDescription); ganttview->updateReadWrite( m_readWrite ); return ganttview; } ViewBase *View::createMilestoneGanttView( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index ) { MilestoneGanttView *ganttview = new MilestoneGanttView(getKoPart(), getPart(), m_tab, koDocument()->isReadWrite() ); m_tab->addWidget( ganttview ); ViewListItem *i = m_viewlist->addView( cat, tag, name, ganttview, getPart(), "", index ); ViewInfo vi = defaultViewInfo( "MilestoneGanttView" ); if ( name.isEmpty() ) { i->setText( 0, vi.name ); } if ( tip == TIP_USE_DEFAULT_TEXT ) { i->setToolTip( 0, vi.tip ); } else { i->setToolTip( 0, tip ); } ganttview->setProject( &( getProject() ) ); ganttview->setScheduleManager( currentScheduleManager() ); connect( ganttview, &ViewBase::guiActivated, this, &View::slotGuiActivated ); connect( this, &View::currentScheduleManagerChanged, ganttview, &MilestoneGanttView::setScheduleManager); connect( ganttview, &MilestoneGanttView::requestPopupMenu, this, &View::slotPopupMenuRequested); connect( ganttview, &MilestoneGanttView::openTaskDescription, this, &View::slotOpenTaskDescription); ganttview->updateReadWrite( m_readWrite ); return ganttview; } ViewBase *View::createAccountsView( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index ) { AccountsView *accountsview = new AccountsView(getKoPart(), &getProject(), getPart(), m_tab ); m_tab->addWidget( accountsview ); ViewListItem *i = m_viewlist->addView( cat, tag, name, accountsview, getPart(), "", index ); ViewInfo vi = defaultViewInfo( "AccountsView" ); if ( name.isEmpty() ) { i->setText( 0, vi.name ); } if ( tip == TIP_USE_DEFAULT_TEXT ) { i->setToolTip( 0, vi.tip ); } else { i->setToolTip( 0, tip ); } accountsview->setScheduleManager( currentScheduleManager() ); connect( this, &View::currentScheduleManagerChanged, accountsview, &AccountsView::setScheduleManager); connect( accountsview, &ViewBase::guiActivated, this, &View::slotGuiActivated ); accountsview->updateReadWrite( m_readWrite ); return accountsview; } ViewBase *View::createResourceAssignmentView( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index ) { ResourceAssignmentView *resourceAssignmentView = new ResourceAssignmentView(getKoPart(), getPart(), m_tab ); m_tab->addWidget( resourceAssignmentView ); m_updateResourceAssignmentView = true; ViewListItem *i = m_viewlist->addView( cat, tag, name, resourceAssignmentView, getPart(), "", index ); ViewInfo vi = defaultViewInfo( "ResourceAssignmentView" ); if ( name.isEmpty() ) { i->setText( 0, vi.name ); } if ( tip == TIP_USE_DEFAULT_TEXT ) { i->setToolTip( 0, vi.tip ); } else { i->setToolTip( 0, tip ); } resourceAssignmentView->draw( getProject() ); connect( resourceAssignmentView, &ViewBase::guiActivated, this, &View::slotGuiActivated ); connect( resourceAssignmentView, &ResourceAssignmentView::requestPopupMenu, this, &View::slotPopupMenuRequested); resourceAssignmentView->updateReadWrite( m_readWrite ); return resourceAssignmentView; } ViewBase *View::createReportsGeneratorView(ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index) { ReportsGeneratorView *v = new ReportsGeneratorView(getKoPart(), getPart(), m_tab ); m_tab->addWidget( v ); ViewListItem *i = m_viewlist->addView(cat, tag, name, v, getPart(), "", index); ViewInfo vi = defaultViewInfo( "ReportsGeneratorView" ); if ( name.isEmpty() ) { i->setText( 0, vi.name ); } if ( tip == TIP_USE_DEFAULT_TEXT ) { i->setToolTip( 0, vi.tip ); } else { i->setToolTip( 0, tip ); } v->setProject( &getProject() ); connect( this, &View::currentScheduleManagerChanged, v, &ViewBase::setScheduleManager ); connect( this, &View::currentScheduleManagerChanged, v, &ViewBase::slotRefreshView); v->setScheduleManager( currentScheduleManager() ); connect( v, &ViewBase::guiActivated, this, &View::slotGuiActivated ); connect( v, &ReportsGeneratorView::requestPopupMenu, this, &View::slotPopupMenuRequested); v->updateReadWrite( m_readWrite ); return v; } ViewBase *View::createReportView( ViewListItem *cat, const QString &tag, const QString &name, const QString &tip, int index ) { #ifdef PLAN_USE_KREPORT ReportView *v = new ReportView(getKoPart(), getPart(), m_tab ); m_tab->addWidget( v ); ViewListItem *i = m_viewlist->addView( cat, tag, name, v, getPart(), "", index ); ViewInfo vi = defaultViewInfo( "ReportView" ); if ( name.isEmpty() ) { i->setText( 0, vi.name ); } if ( tip == TIP_USE_DEFAULT_TEXT ) { i->setToolTip( 0, vi.tip ); } else { i->setToolTip( 0, tip ); } v->setProject( &getProject() ); connect( this, &View::currentScheduleManagerChanged, v, &ReportView::setScheduleManager); connect( this, &View::currentScheduleManagerChanged, v, SLOT(slotRefreshView())); v->setScheduleManager( currentScheduleManager() ); connect( v, &ReportView::guiActivated, this, &View::slotGuiActivated); v->updateReadWrite( m_readWrite ); return v; #else Q_UNUSED(cat) Q_UNUSED(tag) Q_UNUSED(name) Q_UNUSED(tip) Q_UNUSED(index) return 0; #endif } Project& View::getProject() const { return getPart() ->getProject(); } KoPrintJob * View::createPrintJob() { KoView *v = qobject_cast( canvas() ); if ( v == 0 ) { return 0; } return v->createPrintJob(); } ViewBase *View::currentView() const { return qobject_cast( m_tab->currentWidget() ); } void View::slotEditCut() { ViewBase *v = currentView(); if ( v ) { v->slotEditCut(); } } void View::slotEditCopy() { ViewBase *v = currentView(); if ( v ) { v->slotEditCopy(); } } void View::slotEditPaste() { ViewBase *v = currentView(); if ( v ) { v->slotEditPaste(); } } void View::slotRefreshView() { ViewBase *v = currentView(); if ( v ) { debugPlan<slotRefreshView(); } } void View::slotViewSelector( bool show ) { //debugPlan; m_viewlist->setVisible( show ); } void View::slotInsertResourcesFile(const QString &file, const QUrl &projects) { getPart()->insertResourcesFile(QUrl(file), projects); } void View::slotInsertFile() { InsertFileDialog *dlg = new InsertFileDialog( getProject(), currentTask(), this ); connect(dlg, &QDialog::finished, this, &View::slotInsertFileFinished); dlg->open(); } void View::slotInsertFileFinished( int result ) { InsertFileDialog *dlg = qobject_cast( sender() ); if ( dlg == 0 ) { return; } if ( result == QDialog::Accepted ) { getPart()->insertFile( dlg->url(), dlg->parentNode(), dlg->afterNode() ); } dlg->deleteLater(); } void View::slotLoadSharedProjects() { LoadSharedProjectsDialog *dlg = new LoadSharedProjectsDialog( getProject(), getPart()->url(), this ); connect(dlg, &QDialog::finished, this, &View::slotLoadSharedProjectsFinished); dlg->open(); } void View::slotLoadSharedProjectsFinished( int result ) { LoadSharedProjectsDialog *dlg = qobject_cast( sender() ); if ( dlg == 0 ) { return; } if ( result == QDialog::Accepted ) { getPart()->insertSharedProjects(dlg->urls()); } dlg->deleteLater(); } void View::slotProjectEdit() { slotOpenNode( &getProject() ); } void View::slotProjectWorktime() { StandardWorktimeDialog *dia = new StandardWorktimeDialog( getProject(), this ); connect(dia, &QDialog::finished, this, &View::slotProjectWorktimeFinished); dia->open(); } void View::slotProjectWorktimeFinished( int result ) { StandardWorktimeDialog *dia = qobject_cast( sender() ); if ( dia == 0 ) { return; } if ( result == QDialog::Accepted) { KUndo2Command * cmd = dia->buildCommand(); if ( cmd ) { //debugPlan<<"Modifying calendar(s)"; getPart() ->addCommand( cmd ); //also executes } } dia->deleteLater(); } void View::slotSelectionChanged( ScheduleManager *sm ) { debugPlan<setChecked( true ); // this doesn't trigger QActionGroup slotViewSchedule( a ); } QList View::sortedActionList() { QMap lst; const QMap map = m_scheduleActions; // clazy:exclude=qmap-with-pointer-key QMap::const_iterator it; for (it = map.constBegin(); it != map.constEnd(); ++it) { lst.insert(it.key()->objectName(), it.key()); } return lst.values(); } void View::slotScheduleSwapped(ScheduleManager *from, ScheduleManager *to) { if (currentScheduleManager() == from) { QAction *a = m_scheduleActions.key(to); if (a) { a->setChecked(true); } } } void View::slotScheduleRemoved( const ScheduleManager *sch ) { debugPlan<name(); QAction *a = 0; QAction *checked = m_scheduleActionGroup->checkedAction(); QMapIterator i( m_scheduleActions ); while (i.hasNext()) { i.next(); if ( i.value() == sch ) { a = i.key(); break; } } if ( a ) { unplugActionList( "view_schedule_list" ); delete a; plugActionList( "view_schedule_list", sortedActionList() ); if ( checked && checked != a ) { checked->setChecked( true ); } else if ( ! m_scheduleActions.isEmpty() ) { m_scheduleActions.firstKey()->setChecked( true ); } } slotViewSchedule( m_scheduleActionGroup->checkedAction() ); } void View::slotScheduleAdded( const ScheduleManager *sch ) { ScheduleManager *s = const_cast( sch ); QAction *checked = m_scheduleActionGroup->checkedAction(); unplugActionList( "view_schedule_list" ); QAction *act = addScheduleAction( s ); plugActionList( "view_schedule_list", sortedActionList() ); if (!currentScheduleManager()) { if ( act ) { act->setChecked( true ); } else if ( ! m_scheduleActions.isEmpty() ) { m_scheduleActions.firstKey()->setChecked( true ); } slotViewSchedule( m_scheduleActionGroup->checkedAction() ); } } void View::slotScheduleCalculated(Project *project, ScheduleManager *manager) { Q_UNUSED(project); if (manager == currentScheduleManager()) { slotViewScheduleManager(manager); } } QAction *View::addScheduleAction( ScheduleManager *sch ) { QAction *act = 0; QString n = sch->name(); act = new KToggleAction( n, this); actionCollection()->addAction(n, act ); m_scheduleActions.insert( act, sch ); m_scheduleActionGroup->addAction( act ); //debugPlan<<"Add:"<name(); m_scheduleActions.remove( static_cast( o ) ); } void View::slotPlugScheduleActions() { ScheduleManager *current = currentScheduleManager(); unplugActionList( "view_schedule_list" ); const QMap map = m_scheduleActions; // clazy:exclude=qmap-with-pointer-key QMap::const_iterator it; for (it = map.constBegin(); it != map.constEnd(); ++it) { m_scheduleActionGroup->removeAction(it.key()); delete it.key(); } m_scheduleActions.clear(); QAction *ca = 0; foreach( ScheduleManager *sm, getProject().allScheduleManagers() ) { QAction *act = addScheduleAction(sm); if (sm == current) { ca = act; } } plugActionList( "view_schedule_list", sortedActionList() ); if ( ca == 0 && m_scheduleActionGroup->actions().count() > 0 ) { ca = m_scheduleActionGroup->actions().constFirst(); } if ( ca ) { ca->setChecked( true ); } slotViewSchedule( ca ); } void View::slotCalculateSchedule( Project *project, ScheduleManager *sm ) { if ( project == 0 || sm == 0 ) { return; } if ( sm->parentManager() && ! sm->parentManager()->isScheduled() ) { // the parent must be scheduled return; } CalculateScheduleCmd *cmd = new CalculateScheduleCmd( *project, sm, kundo2_i18nc("@info:status 1=schedule name", "Calculate %1", sm->name() ) ); getPart() ->addCommand( cmd ); slotUpdate(); } void View::slotRemoveCommands() { while ( ! m_undocommands.isEmpty() ) { m_undocommands.last()->undo(); delete m_undocommands.takeLast(); } } void View::slotBaselineSchedule( Project *project, ScheduleManager *sm ) { if ( project == 0 || sm == 0 ) { return; } if ( ! sm->isBaselined() && project->isBaselined() ) { KMessageBox::sorry( this, i18n( "Cannot baseline. The project is already baselined." ) ); return; } MacroCommand *cmd = nullptr; if ( sm->isBaselined() ) { KMessageBox::ButtonCode res = KMessageBox::warningContinueCancel( this, i18n( "This schedule is baselined. Do you want to remove the baseline?" ) ); if ( res == KMessageBox::Cancel ) { return; } cmd = new MacroCommand(kundo2_i18n("Reset baseline %1", sm->name())); cmd->addCommand(new ResetBaselineScheduleCmd(*sm)); } else { cmd = new MacroCommand(kundo2_i18n( "Baseline %1", sm->name() ) ); if (sm->schedulingMode() == ScheduleManager::AutoMode) { cmd->addCommand(new ModifyScheduleManagerSchedulingModeCmd(*sm, ScheduleManager::ManualMode)); } cmd->addCommand( new BaselineScheduleCmd( *sm, kundo2_i18n( "Baseline %1", sm->name() ) )); } getPart() ->addCommand( cmd ); } void View::slotAddScheduleManager( Project *project ) { if ( project == 0 ) { return; } ScheduleManager *sm = project->createScheduleManager(); AddScheduleManagerCmd *cmd = new AddScheduleManagerCmd( *project, sm, -1, kundo2_i18n( "Add schedule %1", sm->name() ) ); getPart() ->addCommand( cmd ); } void View::slotDeleteScheduleManager( Project *project, ScheduleManager *sm ) { if ( project == 0 || sm == 0) { return; } DeleteScheduleManagerCmd *cmd = new DeleteScheduleManagerCmd( *project, sm, kundo2_i18n( "Delete schedule %1", sm->name() ) ); getPart() ->addCommand( cmd ); } void View::slotMoveScheduleManager( ScheduleManager *sm, ScheduleManager *parent, int index ) { if ( sm == 0 ) { return; } MoveScheduleManagerCmd *cmd = new MoveScheduleManagerCmd( sm, parent, index, kundo2_i18n( "Move schedule %1", sm->name() ) ); getPart() ->addCommand( cmd ); } void View::slotAddSubTask() { Task * node = getProject().createTask( getPart() ->config().taskDefaults() ); SubTaskAddDialog *dia = new SubTaskAddDialog( getProject(), *node, currentNode(), getProject().accounts(), this ); connect(dia, &QDialog::finished, this, &View::slotAddSubTaskFinished); dia->open(); } void View::slotAddSubTaskFinished( int result ) { SubTaskAddDialog *dia = qobject_cast( sender() ); if ( dia == 0 ) { return; } if ( result == QDialog::Accepted) { KUndo2Command *m = dia->buildCommand(); getPart() ->addCommand( m ); // add task to project } dia->deleteLater(); } void View::slotAddTask() { Task * node = getProject().createTask( getPart() ->config().taskDefaults() ); TaskAddDialog *dia = new TaskAddDialog( getProject(), *node, currentNode(), getProject().accounts(), this ); connect(dia, &QDialog::finished, this, &View::slotAddTaskFinished); dia->open(); } void View::slotAddTaskFinished( int result ) { TaskAddDialog *dia = qobject_cast( sender() ); if ( dia == 0 ) { return; } if ( result == QDialog::Accepted) { KUndo2Command *m = dia->buildCommand(); getPart() ->addCommand( m ); // add task to project } dia->deleteLater(); } void View::slotAddMilestone() { Task * node = getProject().createTask(); node->estimate() ->clear(); TaskAddDialog *dia = new TaskAddDialog( getProject(), *node, currentNode(), getProject().accounts(), this ); connect(dia, &QDialog::finished, this, &View::slotAddMilestoneFinished); dia->open(); } void View::slotAddMilestoneFinished( int result ) { TaskAddDialog *dia = qobject_cast( sender() ); if ( dia == 0 ) { return; } if ( result == QDialog::Accepted) { MacroCommand *c = new MacroCommand( kundo2_i18n( "Add milestone" ) ); c->addCommand( dia->buildCommand() ); getPart() ->addCommand( c ); // add task to project } dia->deleteLater(); } void View::slotAddSubMilestone() { Task * node = getProject().createTask(); node->estimate() ->clear(); SubTaskAddDialog *dia = new SubTaskAddDialog( getProject(), *node, currentNode(), getProject().accounts(), this ); connect(dia, &QDialog::finished, this, &View::slotAddSubMilestoneFinished); dia->open(); } void View::slotAddSubMilestoneFinished( int result ) { SubTaskAddDialog *dia = qobject_cast( sender() ); if ( dia == 0 ) { return; } if ( result == QDialog::Accepted) { MacroCommand *c = new MacroCommand( kundo2_i18n( "Add sub-milestone" ) ); c->addCommand( dia->buildCommand() ); getPart() ->addCommand( c ); // add task to project } dia->deleteLater(); } void View::slotDefineWBS() { //debugPlan; Project &p = getProject(); WBSDefinitionDialog *dia = new WBSDefinitionDialog( p, p.wbsDefinition(), this ); connect(dia, &QDialog::finished, this, &View::slotDefineWBSFinished); dia->open(); } void View::slotDefineWBSFinished( int result ) { //debugPlan; WBSDefinitionDialog *dia = qobject_cast( sender() ); if ( dia == 0 ) { return; } if ( result == QDialog::Accepted ) { KUndo2Command *cmd = dia->buildCommand(); if ( cmd ) { getPart()->addCommand( cmd ); } } dia->deleteLater(); } void View::slotIntroduction() { m_tab->setCurrentIndex(0); } Calendar *View::currentCalendar() { ViewBase *v = dynamic_cast( m_tab->currentWidget() ); if ( v == 0 ) { return 0; } return v->currentCalendar(); } Node *View::currentNode() const { ViewBase *v = dynamic_cast( m_tab->currentWidget() ); if ( v == 0 ) { return 0; } Node * task = v->currentNode(); if ( 0 != task ) { return task; } return &( getProject() ); } Task *View::currentTask() const { ViewBase *v = dynamic_cast( m_tab->currentWidget() ); if ( v == 0 ) { return 0; } Node * task = v->currentNode(); if ( task ) { return dynamic_cast( task ); } return 0; } Resource *View::currentResource() { ViewBase *v = dynamic_cast( m_tab->currentWidget() ); if ( v == 0 ) { return 0; } return v->currentResource(); } ResourceGroup *View::currentResourceGroup() { ViewBase *v = dynamic_cast( m_tab->currentWidget() ); if ( v == 0 ) { return 0; } return v->currentResourceGroup(); } void View::slotOpenCurrentNode() { //debugPlan; Node * node = currentNode(); slotOpenNode( node ); } void View::slotOpenNode( Node *node ) { //debugPlan; if ( !node ) return ; switch ( node->type() ) { case Node::Type_Project: { Project * project = static_cast( node ); MainProjectDialog *dia = new MainProjectDialog( *project, this ); connect(dia, &MainProjectDialog::dialogFinished, this, &View::slotProjectEditFinished); connect(dia, &MainProjectDialog::sigLoadSharedResources, this, &View::slotInsertResourcesFile); connect(dia, &MainProjectDialog::loadResourceAssignments, getPart(), &MainDocument::loadResourceAssignments); connect(dia, &MainProjectDialog::clearResourceAssignments, getPart(), &MainDocument::clearResourceAssignments); dia->open(); break; } case Node::Type_Subproject: //TODO break; case Node::Type_Task: { Task *task = static_cast( node ); TaskDialog *dia = new TaskDialog( getProject(), *task, getProject().accounts(), this ); connect(dia, &QDialog::finished, this, &View::slotTaskEditFinished); dia->open(); break; } case Node::Type_Milestone: { // Use the normal task dialog for now. // Maybe milestone should have it's own dialog, but we need to be able to // enter a duration in case we accidentally set a tasks duration to zero // and hence, create a milestone Task *task = static_cast( node ); TaskDialog *dia = new TaskDialog( getProject(), *task, getProject().accounts(), this ); connect(dia, &QDialog::finished, this, &View::slotTaskEditFinished); dia->open(); break; } case Node::Type_Summarytask: { Task *task = dynamic_cast( node ); Q_ASSERT( task ); SummaryTaskDialog *dia = new SummaryTaskDialog( *task, this ); connect(dia, &QDialog::finished, this, &View::slotSummaryTaskEditFinished); dia->open(); break; } default: break; // avoid warnings } } void View::slotProjectEditFinished( int result ) { MainProjectDialog *dia = qobject_cast( sender() ); if ( dia == 0 ) { return; } if ( result == QDialog::Accepted) { KUndo2Command * cmd = dia->buildCommand(); if ( cmd ) { getPart() ->addCommand( cmd ); } } dia->deleteLater(); } void View::slotTaskEditFinished( int result ) { TaskDialog *dia = qobject_cast( sender() ); if ( dia == 0 ) { return; } if ( result == QDialog::Accepted) { KUndo2Command * cmd = dia->buildCommand(); if ( cmd ) { getPart() ->addCommand( cmd ); } } dia->deleteLater(); } void View::slotSummaryTaskEditFinished( int result ) { SummaryTaskDialog *dia = qobject_cast( sender() ); if ( dia == 0 ) { return; } if ( result == QDialog::Accepted) { KUndo2Command * cmd = dia->buildCommand(); if ( cmd ) { getPart() ->addCommand( cmd ); } } dia->deleteLater(); } ScheduleManager *View::currentScheduleManager() const { return m_scheduleActions.value( m_scheduleActionGroup->checkedAction() ); } long View::activeScheduleId() const { ScheduleManager *s = m_scheduleActions.value( m_scheduleActionGroup->checkedAction() ); return s == nullptr || s->expected() == nullptr ? -1 : s->expected()->id(); } void View::setActiveSchedule( long id ) { if ( id != -1 ) { QMap::const_iterator it = m_scheduleActions.constBegin(); for (; it != m_scheduleActions.constEnd(); ++it ) { int mid = it.value()->expected() == nullptr ? -1 : it.value()->expected()->id(); if (mid == id) { it.key()->setChecked( true ); slotViewSchedule( it.key() ); // signal not emitted from group, so trigger it here break; } } } } void View::slotTaskProgress() { //debugPlan; Node * node = currentNode(); if ( !node ) return ; switch ( node->type() ) { case Node::Type_Project: { break; } case Node::Type_Subproject: //TODO break; case Node::Type_Task: { Task *task = dynamic_cast( node ); Q_ASSERT( task ); TaskProgressDialog *dia = new TaskProgressDialog( *task, currentScheduleManager(), getProject().standardWorktime(), this ); connect(dia, &QDialog::finished, this, &View::slotTaskProgressFinished); dia->open(); break; } case Node::Type_Milestone: { Task *task = dynamic_cast( node ); Q_ASSERT( task ); MilestoneProgressDialog *dia = new MilestoneProgressDialog( *task, this ); connect(dia, &QDialog::finished, this, &View::slotMilestoneProgressFinished); dia->open(); break; } case Node::Type_Summarytask: { // TODO break; } default: break; // avoid warnings } } void View::slotTaskProgressFinished( int result ) { TaskProgressDialog *dia = qobject_cast(sender() ); if ( dia == 0 ) { return; } if ( result == QDialog::Accepted) { KUndo2Command * m = dia->buildCommand(); if ( m ) { getPart() ->addCommand( m ); } } dia->deleteLater(); } void View::slotMilestoneProgressFinished( int result ) { MilestoneProgressDialog *dia = qobject_cast(sender() ); if ( dia == 0 ) { return; } if ( result == QDialog::Accepted) { KUndo2Command * m = dia->buildCommand(); if ( m ) { getPart() ->addCommand( m ); } } dia->deleteLater(); } void View::slotOpenProjectDescription() { debugPlan<isReadWrite(); TaskDescriptionDialog *dia = new TaskDescriptionDialog(getProject(), this, !koDocument()->isReadWrite()); connect(dia, &QDialog::finished, this, &View::slotTaskDescriptionFinished); dia->open(); } void View::slotTaskDescription() { slotOpenTaskDescription(!koDocument()->isReadWrite()); } void View::slotOpenTaskDescription(bool ro) { //debugPlan; Node * node = currentNode(); if ( !node ) return ; switch ( node->type() ) { case Node::Type_Subproject: //TODO break; case Node::Type_Project: case Node::Type_Task: case Node::Type_Milestone: case Node::Type_Summarytask: { TaskDescriptionDialog *dia = new TaskDescriptionDialog( *node, this, ro ); connect(dia, &QDialog::finished, this, &View::slotTaskDescriptionFinished); dia->open(); break; } default: break; // avoid warnings } } void View::slotTaskDescriptionFinished( int result ) { TaskDescriptionDialog *dia = qobject_cast(sender() ); if ( dia == 0 ) { return; } if ( result == QDialog::Accepted) { KUndo2Command * m = dia->buildCommand(); if ( m ) { getPart() ->addCommand( m ); } } dia->deleteLater(); } void View::slotDocuments() { //debugPlan; Node * node = currentNode(); if ( !node ) { return ; } switch ( node->type() ) { case Node::Type_Subproject: //TODO break; case Node::Type_Project: case Node::Type_Summarytask: case Node::Type_Task: case Node::Type_Milestone: { DocumentsDialog *dia = new DocumentsDialog(*node, this); connect(dia, &QDialog::finished, this, &View::slotDocumentsFinished); dia->open(); break; } default: break; // avoid warnings } } void View::slotDocumentsFinished( int result ) { DocumentsDialog *dia = qobject_cast(sender() ); if ( dia == 0 ) { return; } if ( result == QDialog::Accepted) { KUndo2Command * m = dia->buildCommand(); if ( m ) { getPart()->addCommand( m ); } } dia->deleteLater(); } void View::slotDeleteTaskList( QList lst ) { //debugPlan; foreach ( Node *n, lst ) { if ( n->isScheduled() ) { KMessageBox::ButtonCode res = KMessageBox::warningContinueCancel( this, i18n( "A task that has been scheduled will be deleted. This will invalidate the schedule." ) ); if ( res == KMessageBox::Cancel ) { return; } break; } } if ( lst.count() == 1 ) { getPart()->addCommand( new NodeDeleteCmd( lst.takeFirst(), kundo2_i18n( "Delete task" ) ) ); return; } int num = 0; MacroCommand *cmd = new MacroCommand( kundo2_i18np( "Delete task", "Delete tasks", lst.count() ) ); while ( !lst.isEmpty() ) { Node *node = lst.takeFirst(); if ( node == 0 || node->parentNode() == 0 ) { debugPlan << ( node ?"Task is main project" :"No current task" ); continue; } bool del = true; foreach ( Node *n, lst ) { if ( node->isChildOf( n ) ) { del = false; // node is going to be deleted when we delete n break; } } if ( del ) { //debugPlan<name(); cmd->addCommand( new NodeDeleteCmd( node, kundo2_i18n( "Delete task" ) ) ); num++; } } if ( num > 0 ) { getPart()->addCommand( cmd ); } else { delete cmd; } } void View::slotDeleteTask( Node *node ) { //debugPlan; if ( node == 0 || node->parentNode() == 0 ) { debugPlan << ( node ?"Task is main project" :"No current task" ); return ; } if ( node->isScheduled() ) { KMessageBox::ButtonCode res = KMessageBox::warningContinueCancel( this, i18n( "This task has been scheduled. This will invalidate the schedule." ) ); if ( res == KMessageBox::Cancel ) { return; } } NodeDeleteCmd *cmd = new NodeDeleteCmd( node, kundo2_i18n( "Delete task" ) ); getPart() ->addCommand( cmd ); } void View::slotDeleteCurrentTask() { //debugPlan; return slotDeleteTask( currentNode() ); } void View::slotIndentTask() { //debugPlan; Node * node = currentNode(); if ( node == 0 || node->parentNode() == 0 ) { debugPlan << ( node ?"Task is main project" :"No current task" ); return ; } if ( getProject().canIndentTask( node ) ) { NodeIndentCmd * cmd = new NodeIndentCmd( *node, kundo2_i18n( "Indent task" ) ); getPart() ->addCommand( cmd ); } } void View::slotUnindentTask() { //debugPlan; Node * node = currentNode(); if ( node == 0 || node->parentNode() == 0 ) { debugPlan << ( node ?"Task is main project" :"No current task" ); return ; } if ( getProject().canUnindentTask( node ) ) { NodeUnindentCmd * cmd = new NodeUnindentCmd( *node, kundo2_i18n( "Unindent task" ) ); getPart() ->addCommand( cmd ); } } void View::slotMoveTaskUp() { //debugPlan; Node * task = currentNode(); if ( 0 == task ) { // is always != 0. At least we would get the Project, but you never know who might change that // so better be careful errorPlan << "No current task" << endl; return ; } if ( Node::Type_Project == task->type() ) { debugPlan <<"The root node cannot be moved up"; return ; } if ( getProject().canMoveTaskUp( task ) ) { NodeMoveUpCmd * cmd = new NodeMoveUpCmd( *task, kundo2_i18n( "Move task up" ) ); getPart() ->addCommand( cmd ); } } void View::slotMoveTaskDown() { //debugPlan; Node * task = currentNode(); if ( 0 == task ) { // is always != 0. At least we would get the Project, but you never know who might change that // so better be careful return ; } if ( Node::Type_Project == task->type() ) { debugPlan <<"The root node cannot be moved down"; return ; } if ( getProject().canMoveTaskDown( task ) ) { NodeMoveDownCmd * cmd = new NodeMoveDownCmd( *task, kundo2_i18n( "Move task down" ) ); getPart() ->addCommand( cmd ); } } void View::openRelationDialog( Node *par, Node *child ) { //debugPlan; Relation * rel = new Relation( par, child ); AddRelationDialog *dia = new AddRelationDialog( getProject(), rel, this ); connect(dia, &QDialog::finished, this, &View::slotAddRelationFinished); dia->open(); } void View::slotAddRelationFinished( int result ) { AddRelationDialog *dia = qobject_cast(sender() ); if ( dia == 0 ) { return; } if ( result == QDialog::Accepted) { KUndo2Command * m = dia->buildCommand(); if ( m ) { getPart() ->addCommand( m ); } } dia->deleteLater(); } void View::slotAddRelation( Node *par, Node *child, int linkType ) { //debugPlan; if ( linkType == Relation::FinishStart || linkType == Relation::StartStart || linkType == Relation::FinishFinish ) { Relation * rel = new Relation( par, child, static_cast( linkType ) ); getPart() ->addCommand( new AddRelationCmd( getProject(), rel, kundo2_i18n( "Add task dependency" ) ) ); } else { openRelationDialog( par, child ); } } void View::slotEditRelation( Relation *rel ) { //debugPlan; ModifyRelationDialog *dia = new ModifyRelationDialog( getProject(), rel, this ); connect(dia, &QDialog::finished, this, &View::slotModifyRelationFinished); dia->open(); } void View::slotModifyRelationFinished( int result ) { ModifyRelationDialog *dia = qobject_cast( sender() ); if ( dia == 0 ) { return ; } if ( result == QDialog::Accepted) { KUndo2Command *cmd = dia->buildCommand(); if ( cmd ) { getPart() ->addCommand( cmd ); } } dia->deleteLater(); } void View::slotModifyRelation( Relation *rel, int linkType ) { //debugPlan; if ( linkType == Relation::FinishStart || linkType == Relation::StartStart || linkType == Relation::FinishFinish ) { getPart() ->addCommand( new ModifyRelationTypeCmd( rel, static_cast( linkType ) ) ); } else { slotEditRelation( rel ); } } void View::slotModifyCurrentRelation() { ViewBase *v = dynamic_cast( m_tab->currentWidget() ); if ( v == 0 ) { return; } Relation *rel = v->currentRelation(); if ( rel ) { slotEditRelation( rel ); } } void View::slotDeleteRelation() { ViewBase *v = dynamic_cast( m_tab->currentWidget() ); if ( v == 0 ) { return; } Relation *rel = v->currentRelation(); if ( rel ) { getPart()->addCommand( new DeleteRelationCmd( getProject(), rel, kundo2_i18n( "Delete task dependency" ) ) ); } } void View::slotEditCurrentResource() { //debugPlan; slotEditResource( currentResource() ); } void View::slotEditResource( Resource *resource ) { if ( resource == 0 ) { return ; } ResourceDialog *dia = new ResourceDialog( getProject(), resource, this ); connect(dia, &QDialog::finished, this, &View::slotEditResourceFinished); dia->open(); } void View::slotEditResourceFinished( int result ) { //debugPlan; ResourceDialog *dia = qobject_cast( sender() ); if ( dia == 0 ) { return ; } if ( result == QDialog::Accepted) { KUndo2Command * cmd = dia->buildCommand(); if ( cmd ) getPart() ->addCommand( cmd ); } dia->deleteLater(); } void View::slotDeleteResource( Resource *resource ) { getPart()->addCommand( new RemoveResourceCmd( resource->parentGroup(), resource, kundo2_i18n( "Delete resource" ) ) ); } void View::slotDeleteResourceGroup( ResourceGroup *group ) { getPart()->addCommand( new RemoveResourceGroupCmd( group->project(), group, kundo2_i18n( "Delete resourcegroup" ) ) ); } void View::slotDeleteResourceObjects( QObjectList lst ) { //debugPlan; foreach ( QObject *o, lst ) { Resource *r = qobject_cast( o ); if ( r && r->isScheduled() ) { KMessageBox::ButtonCode res = KMessageBox::warningContinueCancel( this, i18n( "A resource that has been scheduled will be deleted. This will invalidate the schedule." ) ); if ( res == KMessageBox::Cancel ) { return; } break; } ResourceGroup *g = qobject_cast( o ); if ( g && g->isScheduled() ) { KMessageBox::ButtonCode res = KMessageBox::warningContinueCancel( this, i18n( "A resource that has been scheduled will be deleted. This will invalidate the schedule." ) ); if ( res == KMessageBox::Cancel ) { return; } break; } } if ( lst.count() == 1 ) { Resource *r = qobject_cast( lst.first() ); if ( r ) { slotDeleteResource( r ); } else { ResourceGroup *g = qobject_cast( lst.first() ); if ( g ) { slotDeleteResourceGroup( g ); } } return; } // int num = 0; MacroCommand *cmd = 0, *rc = 0, *gc = 0; foreach ( QObject *o, lst ) { Resource *r = qobject_cast( o ); if ( r ) { if ( rc == 0 ) rc = new MacroCommand( KUndo2MagicString() ); rc->addCommand( new RemoveResourceCmd( r->parentGroup(), r ) ); continue; } ResourceGroup *g = qobject_cast( o ); if ( g ) { if ( gc == 0 ) gc = new MacroCommand( KUndo2MagicString() ); gc->addCommand( new RemoveResourceGroupCmd( g->project(), g ) ); } } if ( rc || gc ) { KUndo2MagicString s; if ( rc && gc ) { s = kundo2_i18n( "Delete resourcegroups and resources" ); } else if ( rc ) { s = kundo2_i18np( "Delete resource", "Delete resources", lst.count() ); } else { s = kundo2_i18np( "Delete resourcegroup", "Delete resourcegroups", lst.count() ); } cmd = new MacroCommand( s ); } if ( rc ) cmd->addCommand( rc ); if ( gc ) cmd->addCommand( gc ); if ( cmd ) getPart()->addCommand( cmd ); } void View::updateReadWrite( bool readwrite ) { m_readWrite = readwrite; m_viewlist->setReadWrite( readwrite ); } MainDocument *View::getPart() const { return ( MainDocument * ) koDocument(); } KoPart *View::getKoPart() const { return m_partpart; } void View::slotConnectNode() { //debugPlan; /* NodeItem *curr = ganttview->currentItem(); if (curr) { debugPlan<<"node="<getNode().name(); }*/ } QMenu * View::popupMenu( const QString& name ) { //debugPlan; if ( factory() ) { return ( ( QMenu* ) factory() ->container( name, this ) ); } debugPlan<<"No factory"; return 0L; } void View::slotUpdate() { //debugPlan<<"calculate="<currentWidget() ); } void View::slotGuiActivated( ViewBase *view, bool activate ) { if ( activate ) { foreach ( DockWidget *ds, view->dockers() ) { m_dockers.append( ds ); ds->activate( mainWindow() ); } if (!m_dockers.isEmpty()) {debugPlan<<"Added dockers:"<deactivate( mainWindow() ); } } } void View::guiActivateEvent( bool activated ) { if ( activated ) { // plug my own actionlists, they may be gone slotPlugScheduleActions(); } // propagate to sub-view ViewBase *v = dynamic_cast( m_tab->currentWidget() ); if ( v ) { v->setGuiActive( activated ); } } void View::slotViewListItemRemoved( ViewListItem *item ) { getPart()->removeViewListItem( this, item ); } void View::removeViewListItem( const ViewListItem *item ) { if ( item == 0 ) { return; } ViewListItem *itm = m_viewlist->findItem( item->tag() ); if ( itm == 0 ) { return; } m_viewlist->removeViewListItem( itm ); return; } void View::slotViewListItemInserted( ViewListItem *item, ViewListItem *parent, int index ) { getPart()->insertViewListItem( this, item, parent, index ); } void View::addViewListItem( const ViewListItem *item, const ViewListItem *parent, int index ) { if ( item == 0 ) { return; } if ( parent == 0 ) { if ( item->type() != ViewListItem::ItemType_Category ) { return; } m_viewlist->blockSignals( true ); ViewListItem *cat = m_viewlist->addCategory( item->tag(), item->text( 0 ) ); cat->setToolTip( 0, item->toolTip( 0 ) ); m_viewlist->blockSignals( false ); return; } ViewListItem *cat = m_viewlist->findCategory( parent->tag() ); if ( cat == 0 ) { return; } m_viewlist->blockSignals( true ); createView( cat, item->viewType(), item->tag(), item->text( 0 ), item->toolTip( 0 ), index ); m_viewlist->blockSignals( false ); } void View::createReportView(const QDomDocument &doc) { #ifdef PLAN_USE_KREPORT QPointer vd = new ViewListReportsDialog( this, *m_viewlist, doc, this ); vd->exec(); // FIXME make non-crash delete vd; #else Q_UNUSED(doc) #endif } void View::slotOpenReportFile() { #ifdef PLAN_USE_KREPORT QFileDialog *dlg = new QFileDialog(this); connect(dlg, &QDialog::finished, &View::slotOpenReportFileFinished(int))); dlg->open(); #endif } void View::slotOpenReportFileFinished( int result ) { #ifdef PLAN_USE_KREPORT QFileDialog *fdlg = qobject_cast( sender() ); if ( fdlg == 0 || result != QDialog::Accepted ) { return; } QString fn = fdlg->selectedFiles().value(0); if ( fn.isEmpty() ) { return; } QFile file( fn ); if ( ! file.open( QIODevice::ReadOnly | QIODevice::Text ) ) { KMessageBox::sorry( this, xi18nc( "@info", "Cannot open file:
%1", fn ) ); return; } QDomDocument doc; doc.setContent( &file ); createReportView(doc); #else Q_UNUSED(result) #endif } void View::slotReportDesignFinished( int /*result */) { #ifdef PLAN_USE_KREPORT if ( sender() ) { sender()->deleteLater(); } #endif } void View::slotCreateView() { ViewListDialog *dlg = new ViewListDialog( this, *m_viewlist, this ); connect(dlg, &QDialog::finished, this, &View::slotCreateViewFinished); dlg->open(); } void View::slotCreateViewFinished( int ) { if ( sender() ) { sender()->deleteLater(); } } void View::slotViewActivated( ViewListItem *item, ViewListItem *prev ) { QApplication::setOverrideCursor( Qt::WaitCursor ); if ( prev && prev->type() == ViewListItem::ItemType_Category && m_viewlist->previousViewItem() ) { // A view is shown anyway... ViewBase *v = qobject_cast( m_viewlist->previousViewItem()->view() ); if ( v ) { factory()->removeClient(v); v->setGuiActive( false ); } } else if ( prev && prev->type() == ViewListItem::ItemType_SubView ) { ViewBase *v = qobject_cast( prev->view() ); if ( v ) { factory()->removeClient(v); v->setGuiActive( false ); } } if ( item && item->type() == ViewListItem::ItemType_SubView ) { //debugPlan<<"Activate:"<setCurrentWidget( item->view() ); // Add sub-view specific gui ViewBase *v = dynamic_cast( m_tab->currentWidget() ); if ( v ) { factory()->addClient(v); v->setGuiActive( true ); } } QApplication::restoreOverrideCursor(); } QWidget *View::canvas() const { return m_tab->currentWidget();//KoView::canvas(); } KoPageLayout View::pageLayout() const { return currentView()->pageLayout(); } void View::setPageLayout(const KoPageLayout &pageLayout) { currentView()->setPageLayout(pageLayout); } QPrintDialog *View::createPrintDialog( KoPrintJob *printJob, QWidget *parent ) { debugPlan<( printJob ); if ( ! job ) { return 0; } QPrintDialog *dia = KoView::createPrintDialog( job, parent ); PrintingDialog *j = dynamic_cast( job ); if ( j ) { new PrintingControlPrivate( j, dia ); } return dia; } void View::slotCurrentChanged( int view ) { m_visitedViews << view; ViewListItem *item = m_viewlist->findItem( qobject_cast( m_tab->currentWidget() ) ); m_viewlist->setCurrentItem( item ); } void View::slotSelectDefaultView() { m_tab->setCurrentIndex(qMin(m_defaultView, m_tab->count()-1)); } void View::updateView( QWidget * ) { QApplication::setOverrideCursor( Qt::WaitCursor ); //setScheduleActionsEnabled(); QWidget *widget2; widget2 = m_viewlist->findView( "ResourceAssignmentView" ); if ( widget2 && m_updateResourceAssignmentView ) static_cast( widget2 ) ->draw( getProject() ); m_updateResourceAssignmentView = false; QApplication::restoreOverrideCursor(); } void View::slotRenameNode( Node *node, const QString& name ) { //debugPlan<type() ) { case Node::Type_Task: s = kundo2_i18n( "Modify task name" ); break; case Node::Type_Milestone: s = kundo2_i18n( "Modify milestone name" ); break; case Node::Type_Summarytask: s = kundo2_i18n( "Modify summarytask name" ); break; case Node::Type_Project: s = kundo2_i18n( "Modify project name" ); break; } NodeModifyNameCmd * cmd = new NodeModifyNameCmd( *node, name, s ); getPart() ->addCommand( cmd ); } } void View::slotPopupMenuRequested( const QString& menuname, const QPoint & pos ) { QMenu * menu = this->popupMenu( menuname ); if ( menu ) { //debugPlan<actions().count(); ViewBase *v = qobject_cast( m_tab->currentWidget() ); //debugPlan< lst; if ( v ) { lst = v->contextActionList(); debugPlan<addSeparator(); foreach ( QAction *a, lst ) { menu->addAction( a ); } } } menu->exec( pos ); foreach ( QAction *a, lst ) { menu->removeAction( a ); } } } void View::slotPopupMenu( const QString& menuname, const QPoint &pos, ViewListItem *item ) { //debugPlan<context(); if ( ctx == 0 || ! ctx->isLoaded() ) { return false; } KoXmlElement n = ctx->context(); QString cv = n.attribute( "current-view" ); if ( ! cv.isEmpty() ) { m_viewlist->setSelected( m_viewlist->findItem( cv ) ); } else debugPlan<<"No current view"; long id = n.attribute( "current-schedule", "-1" ).toLong(); if ( id != -1 ) { setActiveSchedule( id ); } else debugPlan<<"No current schedule"; return true; } void View::saveContext( QDomElement &me ) const { //debugPlan; long id = activeScheduleId(); if ( id != -1 ) { me.setAttribute( "current-schedule", QString::number((qlonglong)id) ); } ViewListItem *item = m_viewlist->findItem( qobject_cast( m_tab->currentWidget() ) ); if ( item ) { me.setAttribute("current-view", item->tag() ); } m_viewlist->save( me ); } void View::loadWorkPackage(Project *project, const QList &urls) { bool loaded = false; for (const QUrl &url : urls) { loaded |= getPart()->loadWorkPackage(*project, url); } if (loaded) { slotWorkPackageLoaded(); } } void View::setLabel( ScheduleManager *sm ) { //debugPlan; Schedule *s = sm == 0 ? 0 : sm->expected(); if ( s && !s->isDeleted() && s->isScheduled() ) { m_estlabel->setText( sm->name() ); return; } m_estlabel->setText( xi18nc( "@info:status", "Not scheduled" ) ); } void View::slotWorkPackageLoaded() { debugPlan<workPackages(); addStatusBarItem(m_workPackageButton, 0, true); emit workPackagesAvailable(true); } void View::openWorkPackageMergeDialog() { WorkPackageMergeDialog *dlg = new WorkPackageMergeDialog(&getProject(), getPart()->workPackages(), this); connect(dlg, &QDialog::finished, this, &View::workPackageMergeDialogFinished); connect(dlg, SIGNAL(terminateWorkPackage(const KPlato::Package*)), getPart(), SLOT(terminateWorkPackage(const KPlato::Package*))); connect(dlg, &WorkPackageMergeDialog::executeCommand, koDocument(), &KoDocument::addCommand); dlg->open(); removeStatusBarItem(m_workPackageButton); emit workPackagesAvailable(false); } void View::workPackageMergeDialogFinished( int result ) { debugPlanWp<<"result:"<( sender() ); Q_ASSERT(dlg); if (!getPart()->workPackages().isEmpty()) { slotWorkPackageLoaded(); } if (dlg) { dlg->deleteLater(); } } void View::slotMailWorkpackage( Node *node, Resource *resource ) { debugPlan; QTemporaryFile tmpfile(QDir::tempPath() + QLatin1String("/calligraplanwork_XXXXXX") + QLatin1String( ".planwork" )); tmpfile.setAutoRemove( false ); if ( ! tmpfile.open() ) { debugPlan<<"Failed to open file"; KMessageBox::error(0, i18n("Failed to open temporary file" ) ); return; } QUrl url = QUrl::fromLocalFile( tmpfile.fileName() ); if ( ! getPart()->saveWorkPackageUrl( url, node, activeScheduleId(), resource ) ) { debugPlan<<"Failed to save to file"; KMessageBox::error(0, xi18nc( "@info", "Failed to save to temporary file:
%1", url.url() ) ); return; } QStringList attachURLs; attachURLs << url.url(); QString to = resource == 0 ? node->leader() : ( resource->name() + " <" + resource->email() + '>' ); QString cc; QString bcc; QString subject = i18n( "Work Package: %1", node->name() ); QString body = i18nc( "1=project name, 2=task name", "%1\n%2", getProject().name(), node->name() ); QString messageFile; KToolInvocation::invokeMailer( to, cc, bcc, subject, body, messageFile, attachURLs ); } void View::slotPublishWorkpackages( const QList &nodes, Resource *resource, bool mailTo ) { debugPlanWp<leader() yet"; return; } bool mail = mailTo; QString body; QStringList attachURLs; QString path; if (getProject().workPackageInfo().publishUrl.isValid()) { path = getProject().workPackageInfo().publishUrl.path(); debugPlanWp<<"publish:"<saveWorkPackageUrl( url, n, activeScheduleId(), resource ) ) { debugPlan<<"Failed to save to file"; KMessageBox::error(0, xi18nc( "@info", "Failed to save to temporary file:
%1", url.url() ) ); return; } attachURLs << url.url(); body += n->name() + '\n'; } if (mail) { debugPlanWp<name() + " <" + resource->email() + '>'; QString subject = i18n( "Work Package for project: %1", getProject().name() ); QString cc; QString bcc; QString messageFile; KToolInvocation::invokeMailer( to, cc, bcc, subject, body, messageFile, attachURLs ); } } void View::slotCurrencyConfig() { LocaleConfigMoneyDialog *dlg = new LocaleConfigMoneyDialog( getProject().locale(), this ); connect(dlg, &QDialog::finished, this, &View::slotCurrencyConfigFinished); dlg->open(); } void View::slotCurrencyConfigFinished( int result ) { LocaleConfigMoneyDialog *dlg = qobject_cast( sender() ); if ( dlg == 0 ) { return; } if ( result == QDialog::Accepted ) { KUndo2Command *c = dlg->buildCommand( getProject() ); if ( c ) { getPart()->addCommand( c ); } } dlg->deleteLater(); } void View::saveTaskModule( const QUrl &url, Project *project ) { qInfo()<" "" "" "%1" "" "" "predefined" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "") .arg( i18n( "Report" ), i18nc( "Project manager", "Manager:" ), i18n( "Project:" ), i18n( "Task Status Report" ), i18nc( "As in: Page 1 of 2", "of" ), i18n( "Page" ), i18nc( "Task name", "Name" ), i18nc( "Task completion", "Completion (%)" ) ); #endif return s; } diff --git a/src/kptview.h b/src/kptview.h index e58b7277..43405945 100644 --- a/src/kptview.h +++ b/src/kptview.h @@ -1,413 +1,415 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999, 2000 Torben Weis Copyright (C) 2002 - 2010 Dag Andersen Copyright (C) 2019 Dag Andersen 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 KPTVIEW_H #define KPTVIEW_H #include "plan_export.h" #include #include "kptcontext.h" #include "kptviewbase.h" #include #include #include #include class QMenu; class QPrintDialog; class QStackedWidget; class QSplitter; class QUrl; class QToolButton; class KUndo2Command; class QAction; class KToggleAction; class QLabel; class KConfigSkeleton; class KConfigSkeletonItem; class KPageWidgetItem; class KoView; namespace KPlato { class View; class ViewBase; class ViewListItem; class ViewListWidget; struct ViewInfo; class AccountsView; class ResourceCoverageView; class GanttView; class PertEditor; class AccountsEditor; class TaskEditor; class CalendarEditor; class ScheduleEditor; class ScheduleManager; class CalculateScheduleCmd; class ResourceAssignmentView; class TaskStatusView; class Calendar; class MainDocument; class Part; class Node; class Project; class Task; class MainSchedule; class Schedule; class Resource; class ResourceGroup; class Relation; class Context; class ViewAdaptor; class HtmlView; class ReportView; class ReportDesignDialog; class DockWidget; class PLAN_EXPORT View : public KoView { Q_OBJECT public: explicit View(KoPart *part, MainDocument *doc, QWidget *parent = 0); ~View() override; MainDocument *getPart() const; KoPart *getKoPart() const; Project& getProject() const; QMenu *popupMenu( const QString& name ); virtual bool loadContext(); virtual void saveContext( QDomElement &context ) const; QWidget *canvas() const; KoPageLayout pageLayout() const override; void setPageLayout(const KoPageLayout &pageLayout) override; ScheduleManager *currentScheduleManager() const; long activeScheduleId() const; void setActiveSchedule( long id ); /// Returns the default view information like standard name and tooltip for view type @p type ViewInfo defaultViewInfo( const QString &type ) const; /// Returns the default category information like standard name and tooltip for category type @p type ViewInfo defaultCategoryInfo( const QString &type ) const; ViewBase *createTaskEditor( ViewListItem *cat, const QString &tag, const QString &name = QString(), const QString &tip = QString(), int index = -1 ); ViewBase *createResourceEditor( ViewListItem *cat, const QString &tag, const QString &name = QString(), const QString &tip = QString(), int index = -1 ); ViewBase *createAccountsEditor( ViewListItem *cat, const QString &tag, const QString &name = QString(), const QString &tip = QString(), int index = -1 ); ViewBase *createCalendarEditor( ViewListItem *cat, const QString &tag, const QString &name = QString(), const QString &tip = QString(), int index = -1 ); ViewBase *createScheduleHandler( ViewListItem *cat, const QString &tag, const QString &name = QString(), const QString &tip = QString(), int index = -1 ); ScheduleEditor *createScheduleEditor( QWidget *parent ); ViewBase *createScheduleEditor( ViewListItem *cat, const QString &tag, const QString &name = QString(), const QString &tip = QString(), int index = -1 ); ViewBase *createDependencyEditor( ViewListItem *cat, const QString &tag, const QString &name = QString(), const QString &tip = QString(), int index = -1 ); ViewBase *createPertEditor( ViewListItem *cat, const QString &tag, const QString &name = QString(), const QString &tip = QString(), int index = -1 ); ViewBase *createProjectStatusView( ViewListItem *cat, const QString &tag, const QString &name = QString(), const QString &tip = QString(), int index = -1 ); ViewBase *createPerformanceStatusView( ViewListItem *cat, const QString &tag, const QString &name = QString(), const QString &tip = QString(), int index = -1 ); ViewBase *createTaskStatusView( ViewListItem *cat, const QString &tag, const QString &name = QString(), const QString &tip = QString(), int index = -1 ); ViewBase *createTaskView( ViewListItem *cat, const QString &tag, const QString &name = QString(), const QString &tip = QString(), int index = -1 ); ViewBase *createTaskWorkPackageView( ViewListItem *cat, const QString &tag, const QString &name = QString(), const QString &tip = QString(), int index = -1 ); ViewBase *createGanttView( ViewListItem *cat, const QString &tag, const QString &name = QString(), const QString &tip = QString(), int index = -1 ); ViewBase *createMilestoneGanttView( ViewListItem *cat, const QString &tag, const QString &name = QString(), const QString &tip = QString(), int index = -1 ); ViewBase *createResourceAppointmentsView( ViewListItem *cat, const QString &tag, const QString &name = QString(), const QString &tip = QString(), int index = -1 ); ViewBase *createResourceAppointmentsGanttView( ViewListItem *cat, const QString &tag, const QString &name = QString(), const QString &tip = QString(), int index = -1 ); ViewBase *createAccountsView( ViewListItem *cat, const QString &tag, const QString &name = QString(), const QString &tip = QString(), int index = -1 ); ViewBase *createResourceAssignmentView( ViewListItem *cat, const QString &tag, const QString &name = QString(), const QString &tip = QString(), int index = -1 ); ViewBase *createChartView( ViewListItem *cat, const QString &tag, const QString &name = QString(), const QString &tip = QString(), int index = -1 ); ViewBase *createReportView( ViewListItem *cat, const QString &tag, const QString &name = QString(), const QString &tip = QString(), int index = -1 ); ViewBase *createReportsGeneratorView( ViewListItem *cat, const QString &tag, const QString &name = QString(), const QString &tip = QString(), int index = -1 ); KoPrintJob * createPrintJob() override; QPrintDialog* createPrintDialog(KoPrintJob*, QWidget*) override; Q_SIGNALS: void currentScheduleManagerChanged(KPlato::ScheduleManager *sm); void taskModulesChanged(const QStringList &modules); void workPackagesAvailable(bool); public Q_SLOTS: void slotUpdate(); void slotCreateNewProject(); void slotEditCurrentResource(); void slotEditResource(KPlato::Resource *resource); void slotEditCut(); void slotEditCopy(); void slotEditPaste(); void slotRefreshView(); void slotViewSelector( bool show ); void slotAddTask(); void slotAddSubTask(); void slotAddMilestone(); void slotAddSubMilestone(); void slotProjectEdit(); void slotDefineWBS(); void slotCurrencyConfig(); void slotCreateView(); void slotIntroduction(); void openRelationDialog(KPlato::Node *par, KPlato::Node *child); void slotEditRelation(KPlato::Relation *rel); void slotAddRelation(KPlato::Node *par, KPlato::Node *child, int linkType); void slotModifyRelation(KPlato::Relation *rel, int linkType); void slotModifyCurrentRelation(); void slotDeleteRelation(); void slotRenameNode(KPlato::Node *node, const QString& name); void slotPopupMenuRequested( const QString& menuname, const QPoint &pos ); void slotPopupMenu( const QString& menuname, const QPoint &pos, KPlato::ViewListItem *item ); void addViewListItem(const KPlato::ViewListItem *item, const KPlato::ViewListItem *parent, int index); void removeViewListItem(const KPlato::ViewListItem *item); void slotOpenReportFile(); void slotSelectionChanged(KPlato::ScheduleManager *sm); void slotUpdateViewInfo(KPlato::ViewListItem *itm); /// Load the workpackages from @p urls into @p project. void loadWorkPackage(KPlato::Project *project, const QList &urls ); + void slotCreateTemplate(); + protected Q_SLOTS: void slotGuiActivated(KPlato::ViewBase *view, bool); void slotViewActivated(KPlato::ViewListItem*, KPlato::ViewListItem*); void slotPlugScheduleActions(); void slotViewSchedule( QAction *act ); void slotScheduleAdded(const KPlato::ScheduleManager*); void slotScheduleRemoved( const KPlato::ScheduleManager*); void slotScheduleSwapped(KPlato::ScheduleManager *from, KPlato::ScheduleManager *to); void slotScheduleCalculated(KPlato::Project *project, KPlato::ScheduleManager *manager); void slotAddScheduleManager(KPlato::Project *project); void slotDeleteScheduleManager(KPlato::Project *project, KPlato::ScheduleManager *sm); void slotMoveScheduleManager(KPlato::ScheduleManager *sm, KPlato::ScheduleManager *parent, int index); void slotCalculateSchedule(KPlato::Project*, KPlato::ScheduleManager*); void slotBaselineSchedule(KPlato::Project *project, KPlato::ScheduleManager *sm); void slotProjectWorktime(); void slotOpenCurrentNode(); void slotOpenNode(KPlato::Node *node); void slotTaskProgress(); void slotOpenProjectDescription(); void slotTaskDescription(); void slotOpenTaskDescription(bool); void slotDocuments(); void slotDeleteTaskList(QList lst); void slotDeleteTask(KPlato::Node *node); void slotDeleteCurrentTask(); void slotIndentTask(); void slotUnindentTask(); void slotMoveTaskUp(); void slotMoveTaskDown(); void slotConnectNode(); void slotDeleteResource(KPlato::Resource *resource); void slotDeleteResourceGroup(KPlato::ResourceGroup *group); void slotDeleteResourceObjects( QObjectList ); void slotCurrentChanged( int ); void slotSelectDefaultView(); void slotInsertResourcesFile(const QString&, const QUrl &projects); void slotInsertFile(); void slotLoadSharedProjects(); void slotWorkPackageLoaded(); void slotMailWorkpackage(KPlato::Node *node, KPlato::Resource *resource = 0); void slotPublishWorkpackages(const QList &nodes, KPlato::Resource *resource, bool mailTo); void slotOpenUrlRequest(KPlato::HtmlView *v, const QUrl &url); void createReportView(const QDomDocument &doc); void saveTaskModule(const QUrl &url, KPlato::Project *project); void removeTaskModule( const QUrl &url ); protected: void guiActivateEvent( bool activated ) override; void updateReadWrite( bool readwrite ) override; QList sortedActionList(); QAction *addScheduleAction( ScheduleManager *sch ); void setLabel( ScheduleManager *sm = 0 ); Task *currentTask() const; Node *currentNode() const; Resource *currentResource(); ResourceGroup *currentResourceGroup(); Calendar *currentCalendar(); void updateView( QWidget *widget ); ViewBase *currentView() const; ViewBase *createIntroductionView(); private Q_SLOTS: void slotActionDestroyed( QObject *o ); void slotViewListItemRemoved(KPlato::ViewListItem *item); void slotViewListItemInserted(KPlato::ViewListItem *item, KPlato::ViewListItem *parent, int index); void slotProjectEditFinished( int result ); void slotTaskEditFinished( int result ); void slotSummaryTaskEditFinished( int result ); void slotEditResourceFinished( int result ); void slotProjectWorktimeFinished( int result ); void slotDefineWBSFinished( int result ); void slotCurrencyConfigFinished( int result ); void slotInsertFileFinished( int result ); void slotAddSubTaskFinished( int result ); void slotAddTaskFinished( int result ); void slotAddSubMilestoneFinished( int result ); void slotAddMilestoneFinished( int result ); void slotTaskProgressFinished( int result ); void slotMilestoneProgressFinished( int result ); void slotTaskDescriptionFinished( int result ); void slotDocumentsFinished(int result); void slotAddRelationFinished( int result ); void slotModifyRelationFinished( int result ); void slotReportDesignFinished( int result ); void slotOpenReportFileFinished( int result ); void slotCreateViewFinished( int result ); void slotLoadSharedProjectsFinished( int result ); void openWorkPackageMergeDialog(); void workPackageMergeDialogFinished( int result ); void slotRemoveCommands(); void initiateViews(); void slotViewScheduleManager(KPlato::ScheduleManager *sm); private: void createViews(); ViewBase *createView( ViewListItem *cat, const QString &type, const QString &tag, const QString &name, const QString &tip, int index = -1 ); QString standardTaskStatusReport() const; private: QSplitter *m_sp; QStackedWidget *m_tab; ViewListWidget *m_viewlist; ViewListItem *m_viewlistItem; // requested popupmenu item //QDockWidget *m_toolbox; int m_viewGrp; int m_defaultFontSize; int m_currentEstimateType; bool m_updateAccountsview; bool m_updateResourceAssignmentView; bool m_updatePertEditor; QLabel *m_estlabel; QToolButton *m_workPackageButton; ViewAdaptor* m_dbus; QActionGroup *m_scheduleActionGroup; QMap m_scheduleActions; QMultiMap m_calculationcommands; QList m_undocommands; bool m_readWrite; int m_defaultView; QList m_visitedViews; QList m_dockers; // ------ File QAction *actionCreateTemplate; QAction *actionCreateNewProject; // ------ Edit QAction *actionCut; QAction *actionCopy; QAction *actionPaste; // ------ View KToggleAction *actionViewSelector; // ------ Insert // ------ Project QAction *actionEditMainProject; // ------ Tools QAction *actionEditStandardWorktime; QAction *actionDefineWBS; QAction *actionInsertFile; QAction *actionCurrencyConfig; QAction *actionLoadSharedProjects; QAction *actionOpenReportFile; // ------ Settings QAction *actionConfigure; // ------ Help QAction *actionIntroduction; // ------ Popup QAction *actionOpenNode; QAction *actionTaskProgress; QAction *actionTaskDescription; QAction *actionDocuments; QAction *actionDeleteTask; QAction *actionIndentTask; QAction *actionUnindentTask; QAction *actionMoveTaskUp; QAction *actionMoveTaskDown; QAction *actionEditResource; QAction *actionEditRelation; QAction *actionDeleteRelation; //Test QAction *actNoInformation; QMap m_reportActionMap; KoPart *m_partpart; }; } //Kplato namespace #endif diff --git a/src/libs/kernel/kptconfigbase.h b/src/libs/kernel/kptconfigbase.h index 09ac00bb..db0bcbc1 100644 --- a/src/libs/kernel/kptconfigbase.h +++ b/src/libs/kernel/kptconfigbase.h @@ -1,92 +1,94 @@ /* This file is part of the KDE project Copyright (C) 2009 Dag Andersen 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 KPTCONFIGBASE_H #define KPTCONFIGBASE_H #include "kpttask.h" namespace KPlato { class Locale; class PLANKERNEL_EXPORT ConfigBase : public QObject { Q_OBJECT public: ConfigBase(); ~ConfigBase() override; void setReadWrite(bool readWrite) { m_readWrite = readWrite; } Task &taskDefaults() const { const_cast( this )->setDefaultValues( *m_taskDefaults ); return *m_taskDefaults; } void setTaskDefaults( Task * ); virtual void setDefaultValues( Project & ) const {} virtual void setDefaultValues( Task & ) {} virtual QPair durationUnitRange() const { return QPair(); } virtual int minimumDurationUnit() const { return Duration::Unit_m; } virtual int maximumDurationUnit() const { return Duration::Unit_Y; } QBrush summaryTaskLevelColor( int level ) const; virtual bool summaryTaskLevelColorsEnabled() const; virtual QBrush summaryTaskDefaultColor() const; virtual QBrush summaryTaskLevelColor_1() const; virtual QBrush summaryTaskLevelColor_2() const; virtual QBrush summaryTaskLevelColor_3() const; virtual QBrush summaryTaskLevelColor_4() const; virtual QBrush taskNormalColor() const; virtual QBrush taskErrorColor() const; virtual QBrush taskCriticalColor() const; virtual QBrush taskFinishedColor() const; virtual QBrush milestoneNormalColor() const; virtual QBrush milestoneErrorColor() const; virtual QBrush milestoneCriticalColor() const; virtual QBrush milestoneFinishedColor() const; const Locale *locale() const; Locale *locale(); virtual QString documentationPath() const { return QString(); } virtual QString contextPath() const { return QString(); } virtual QString contextLanguage() const { return QString(); } virtual bool useLocalTaskModules() const { return true; } virtual QStringList taskModulePaths() const { return QStringList(); } + virtual QStringList projectTemplatePaths() const { return QStringList(); } + static QBrush gradientBrush( const QColor &c ); protected: bool m_readWrite; private: Task *m_taskDefaults; Locale *m_locale; }; } //KPlato namespace #endif // CONFIGBASE_H diff --git a/src/libs/main/KoDocument.cpp b/src/libs/main/KoDocument.cpp index e8d9a75d..105aa248 100644 --- a/src/libs/main/KoDocument.cpp +++ b/src/libs/main/KoDocument.cpp @@ -1,2690 +1,2688 @@ /* This file is part of the KDE project * Copyright (C) 1998, 1999 Torben Weis * Copyright (C) 2000-2005 David Faure * Copyright (C) 2007-2008 Thorsten Zachmann * Copyright (C) 2010-2012 Boudewijn Rempt * Copyright (C) 2011 Inge Wallin * * 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. */ // clazy:excludeall=qstring-arg #include "KoDocument.h" #include "KoMainWindow.h" // XXX: remove #include // XXX: remove #include // XXX: remove #include "KoComponentData.h" #include "KoPart.h" #include "KoEmbeddedDocumentSaver.h" #include "KoFilterManager.h" #include "KoFileDialog.h" #include "KoDocumentInfo.h" #include "KoView.h" #include "KoOdfReadStore.h" #include "KoOdfWriteStore.h" #include "KoXmlNS.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 #ifndef QT_NO_DBUS #include #include #endif // Define the protocol used here for embedded documents' URL // This used to "store" but QUrl didn't like it, // so let's simply make it "tar" ! #define STORE_PROTOCOL "tar" // The internal path is a hack to make QUrl happy and for document children #define INTERNAL_PROTOCOL "intern" #define INTERNAL_PREFIX "intern:/" // Warning, keep it sync in koStore.cc #include #include "KoUndoStackAction.h" #include using namespace std; /********************************************************** * * KoDocument * **********************************************************/ namespace { class DocumentProgressProxy : public KoProgressProxy { public: KoMainWindow *m_mainWindow; DocumentProgressProxy(KoMainWindow *mainWindow) : m_mainWindow(mainWindow) { } ~DocumentProgressProxy() override { // signal that the job is done setValue(-1); } int maximum() const override { return 100; } void setValue(int value) override { if (m_mainWindow) { m_mainWindow->slotProgress(value); } } void setRange(int /*minimum*/, int /*maximum*/) override { } void setFormat(const QString &/*format*/) override { } }; } //static QString KoDocument::newObjectName() { static int s_docIFNumber = 0; QString name; name.setNum(s_docIFNumber++); name.prepend("document_"); return name; } class Q_DECL_HIDDEN KoDocument::Private { public: Private(KoDocument *document, KoPart *part) : document(document), parentPart(part), docInfo(0), // docRdf(0), progressUpdater(0), progressProxy(0), profileStream(0), filterManager(0), specialOutputFlag(0), // default is native format isImporting(false), isExporting(false), password(QString()), modifiedAfterAutosave(false), autosaving(false), shouldCheckAutoSaveFile(true), autoErrorHandlingEnabled(true), backupFile(true), backupPath(QString()), doNotSaveExtDoc(false), storeInternal(false), isLoading(false), undoStack(0), modified(false), readwrite(true), alwaysAllowSaving(false), disregardAutosaveFailure(false) { m_job = 0; m_statJob = 0; m_uploadJob = 0; m_saveOk = false; m_waitForSave = false; m_duringSaveAs = false; m_bTemp = false; m_bAutoDetectedMime = false; confirmNonNativeSave[0] = true; confirmNonNativeSave[1] = true; if (QLocale().measurementSystem() == QLocale::ImperialSystem) { unit = KoUnit::Inch; } else { unit = KoUnit::Centimeter; } } KoDocument *document; KoPart *const parentPart; KoDocumentInfo *docInfo; // KoDocumentRdfBase *docRdf; KoProgressUpdater *progressUpdater; KoProgressProxy *progressProxy; QTextStream *profileStream; QTime profileReferenceTime; KoUnit unit; KoFilterManager *filterManager; // The filter-manager to use when loading/saving [for the options] QByteArray mimeType; // The actual mimetype of the document QByteArray outputMimeType; // The mimetype to use when saving bool confirmNonNativeSave [2]; // used to pop up a dialog when saving for the // first time if the file is in a foreign format // (Save/Save As, Export) int specialOutputFlag; // See KoFileDialog in koMainWindow.cc bool isImporting; bool isExporting; // File --> Import/Export vs File --> Open/Save QString password; // The password used to encrypt an encrypted document QTimer autoSaveTimer; QString lastErrorMessage; // see openFile() int autoSaveDelay; // in seconds, 0 to disable. bool modifiedAfterAutosave; bool autosaving; bool shouldCheckAutoSaveFile; // usually true bool autoErrorHandlingEnabled; // usually true bool backupFile; QString backupPath; bool doNotSaveExtDoc; // makes it possible to save only internally stored child documents bool storeInternal; // Store this doc internally even if url is external bool isLoading; // True while loading (openUrl is async) QList versionInfo; KUndo2Stack *undoStack; // KoGridData gridData; // KoGuidesData guidesData; bool isEmpty; KoPageLayout pageLayout; KIO::FileCopyJob * m_job; KIO::StatJob * m_statJob; KIO::FileCopyJob * m_uploadJob; QUrl m_originalURL; // for saveAs QString m_originalFilePath; // for saveAs bool m_saveOk : 1; bool m_waitForSave : 1; bool m_duringSaveAs : 1; bool m_bTemp: 1; // If @p true, @p m_file is a temporary file that needs to be deleted later. bool m_bAutoDetectedMime : 1; // whether the mimetype in the arguments was detected by the part itself QUrl m_url; // Remote (or local) url - the one displayed to the user. QString m_file; // Local file - the only one the part implementation should deal with. QEventLoop m_eventLoop; bool modified; bool readwrite; bool alwaysAllowSaving; bool disregardAutosaveFailure; bool openFile() { DocumentProgressProxy *progressProxy = 0; if (!document->progressProxy()) { KoMainWindow *mainWindow = 0; if (parentPart->mainWindows().count() > 0) { mainWindow = parentPart->mainWindows()[0]; } progressProxy = new DocumentProgressProxy(mainWindow); document->setProgressProxy(progressProxy); } document->setUrl(m_url); bool ok = document->openFile(); if (progressProxy) { document->setProgressProxy(0); delete progressProxy; } return ok; } bool openLocalFile() { m_bTemp = false; // set the mimetype only if it was not already set (for example, by the host application) if (mimeType.isEmpty()) { // get the mimetype of the file // using findByUrl() to avoid another string -> url conversion QMimeType mime = QMimeDatabase().mimeTypeForUrl(m_url); if (mime.isValid()) { mimeType = mime.name().toLatin1(); m_bAutoDetectedMime = true; } } const bool ret = openFile(); if (ret) { emit document->completed(); } else { emit document->canceled(QString()); } return ret; } void openRemoteFile() { m_bTemp = true; // Use same extension as remote file. This is important for mimetype-determination (e.g. koffice) QString fileName = m_url.fileName(); QFileInfo fileInfo(fileName); QString ext = fileInfo.completeSuffix(); QString extension; if (!ext.isEmpty() && m_url.query().isNull()) // not if the URL has a query, e.g. cgi.pl?something extension = '.'+ext; // keep the '.' QTemporaryFile tempFile(QDir::tempPath() + "/" + qAppName() + QLatin1String("_XXXXXX") + extension); tempFile.setAutoRemove(false); tempFile.open(); m_file = tempFile.fileName(); const QUrl destURL = QUrl::fromLocalFile( m_file ); KIO::JobFlags flags = KIO::DefaultFlags; flags |= KIO::Overwrite; m_job = KIO::file_copy(m_url, destURL, 0600, flags); #ifndef QT_NO_DBUS KJobWidgets::setWindow(m_job, 0); if (m_job->uiDelegate()) { KJobWidgets::setWindow(m_job, parentPart->currentMainwindow()); } #endif QObject::connect(m_job, SIGNAL(result(KJob*)), document, SLOT(_k_slotJobFinished(KJob*))); // clazy:exclude=old-style-connect QObject::connect(m_job, SIGNAL(mimetype(KIO::Job*,QString)), document, SLOT(_k_slotGotMimeType(KIO::Job*,QString))); // clazy:exclude=old-style-connect } // Set m_file correctly for m_url void prepareSaving() { // Local file if ( m_url.isLocalFile() ) { if ( m_bTemp ) // get rid of a possible temp file first { // (happens if previous url was remote) QFile::remove( m_file ); m_bTemp = false; } m_file = m_url.toLocalFile(); } else { // Remote file // We haven't saved yet, or we did but locally - provide a temp file if ( m_file.isEmpty() || !m_bTemp ) { QTemporaryFile tempFile; tempFile.setAutoRemove(false); tempFile.open(); m_file = tempFile.fileName(); m_bTemp = true; } // otherwise, we already had a temp file } } void _k_slotJobFinished( KJob * job ) { Q_ASSERT( job == m_job ); m_job = 0; if (job->error()) emit document->canceled( job->errorString() ); else { if ( openFile() ) { emit document->completed(); } else { emit document->canceled(QString()); } } } void _k_slotStatJobFinished(KJob * job) { Q_ASSERT(job == m_statJob); m_statJob = 0; // this could maybe confuse some apps? So for now we'll just fallback to KIO::get // and error again. Well, maybe this even helps with wrong stat results. if (!job->error()) { const QUrl localUrl = static_cast(job)->mostLocalUrl(); if (localUrl.isLocalFile()) { m_file = localUrl.toLocalFile(); openLocalFile(); return; } } openRemoteFile(); } void _k_slotGotMimeType(KIO::Job *job, const QString &mime) { // kDebug(1000) << mime; Q_ASSERT(job == m_job); Q_UNUSED(job); // set the mimetype only if it was not already set (for example, by the host application) if (mimeType.isEmpty()) { mimeType = mime.toLatin1(); m_bAutoDetectedMime = true; } } void _k_slotUploadFinished( KJob * ) { if (m_uploadJob->error()) { QFile::remove(m_uploadJob->srcUrl().toLocalFile()); m_uploadJob = 0; if (m_duringSaveAs) { document->setUrl(m_originalURL); m_file = m_originalFilePath; } } else { ::org::kde::KDirNotify::emitFilesAdded(QUrl::fromLocalFile(m_url.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash).path())); m_uploadJob = 0; document->setModified( false ); emit document->completed(); m_saveOk = true; } m_duringSaveAs = false; m_originalURL = QUrl(); m_originalFilePath.clear(); if (m_waitForSave) { m_eventLoop.quit(); } } }; KoDocument::KoDocument(KoPart *parent, KUndo2Stack *undoStack) : d(new Private(this, parent)) { Q_ASSERT(parent); d->isEmpty = true; d->filterManager = new KoFilterManager(this, d->progressUpdater); connect(&d->autoSaveTimer, &QTimer::timeout, this, &KoDocument::slotAutoSave); setAutoSave(defaultAutoSave()); setObjectName(newObjectName()); d->docInfo = new KoDocumentInfo(this); d->pageLayout.width = 0; d->pageLayout.height = 0; d->pageLayout.topMargin = 0; d->pageLayout.bottomMargin = 0; d->pageLayout.leftMargin = 0; d->pageLayout.rightMargin = 0; d->undoStack = undoStack; d->undoStack->setParent(this); KConfigGroup cfgGrp(d->parentPart->componentData().config(), "Undo"); d->undoStack->setUndoLimit(cfgGrp.readEntry("UndoLimit", 1000)); connect(d->undoStack, &KUndo2QStack::indexChanged, this, &KoDocument::slotUndoStackIndexChanged); } KoDocument::~KoDocument() { d->autoSaveTimer.disconnect(this); d->autoSaveTimer.stop(); d->parentPart->deleteLater(); delete d->filterManager; delete d; } KoPart *KoDocument::documentPart() const { return d->parentPart; } bool KoDocument::exportDocument(const QUrl &_url) { bool ret; d->isExporting = true; // // Preserve a lot of state here because we need to restore it in order to // be able to fake a File --> Export. Can't do this in saveFile() because, // for a start, KParts has already set url and m_file and because we need // to restore the modified flag etc. and don't want to put a load on anyone // reimplementing saveFile() (Note: importDocument() and exportDocument() // will remain non-virtual). // QUrl oldURL = url(); QString oldFile = localFilePath(); bool wasModified = isModified(); QByteArray oldMimeType = mimeType(); // save... ret = saveAs(_url); // // This is sooooo hacky :( // Hopefully we will restore enough state. // debugMain << "Restoring KoDocument state to before export"; // always restore url & m_file because KParts has changed them // (regardless of failure or success) setUrl(oldURL); setLocalFilePath(oldFile); // on successful export we need to restore modified etc. too // on failed export, mimetype/modified hasn't changed anyway if (ret) { setModified(wasModified); d->mimeType = oldMimeType; } d->isExporting = false; return ret; } bool KoDocument::saveFile() { debugMain << "doc=" << url().url(); // Save it to be able to restore it after a failed save const bool wasModified = isModified(); // The output format is set by koMainWindow, and by openFile QByteArray outputMimeType = d->outputMimeType; if (outputMimeType.isEmpty()) { outputMimeType = d->outputMimeType = nativeFormatMimeType(); debugMain << "Empty output mime type, saving to" << outputMimeType; } QApplication::setOverrideCursor(Qt::WaitCursor); if (backupFile()) { if (url().isLocalFile()) KBackup::backupFile(url().toLocalFile(), d->backupPath); else { KIO::UDSEntry entry; if (KIO::NetAccess::stat(url(), entry, d->parentPart->currentMainwindow())) { // this file exists => backup emit statusBarMessage(i18n("Making backup...")); QUrl backup; if (d->backupPath.isEmpty()) backup = url(); else backup = QUrl::fromLocalFile(d->backupPath + '/' + url().fileName()); backup.setPath(backup.path() + QString::fromLatin1("~")); KFileItem item(entry, url()); Q_ASSERT(item.name() == url().fileName()); KIO::FileCopyJob *job = KIO::file_copy(url(), backup, item.permissions(), KIO::Overwrite | KIO::HideProgressInfo); job->exec(); } } } emit statusBarMessage(i18n("Saving...")); qApp->processEvents(); bool ret = false; bool suppressErrorDialog = false; if (!isNativeFormat(outputMimeType)) { debugMain << "Saving to format" << outputMimeType << "in" << localFilePath(); // Not native format : save using export filter KoFilter::ConversionStatus status = d->filterManager->exportDocument(localFilePath(), outputMimeType); ret = status == KoFilter::OK; suppressErrorDialog = (status == KoFilter::UserCancelled || status == KoFilter::BadConversionGraph); } else { // Native format => normal save Q_ASSERT(!localFilePath().isEmpty()); ret = saveNativeFormat(localFilePath()); } if (ret) { d->undoStack->setClean(); removeAutoSaveFiles(); // Restart the autosave timer // (we don't want to autosave again 2 seconds after a real save) setAutoSave(d->autoSaveDelay); } QApplication::restoreOverrideCursor(); if (!ret) { if (!suppressErrorDialog) { if (errorMessage().isEmpty()) { KMessageBox::error(0, i18n("Could not save\n%1", localFilePath())); } else if (errorMessage() != "USER_CANCELED") { KMessageBox::error(0, i18n("Could not save %1\nReason: %2", localFilePath(), errorMessage())); } } // couldn't save file so this new URL is invalid // FIXME: we should restore the current document's true URL instead of // setting it to nothing otherwise anything that depends on the URL // being correct will not work (i.e. the document will be called // "Untitled" which may not be true) // // Update: now the URL is restored in KoMainWindow but really, this // should still be fixed in KoDocument/KParts (ditto for file). // We still resetURL() here since we may or may not have been called // by KoMainWindow - Clarence resetURL(); // As we did not save, restore the "was modified" status setModified(wasModified); } if (ret) { d->mimeType = outputMimeType; setConfirmNonNativeSave(isExporting(), false); } emit clearStatusBarMessage(); if (ret) { KNotification *notify = new KNotification("DocumentSaved"); notify->setText(i18n("Document %1 saved", url().url())); notify->addContext("url", url().url()); QTimer::singleShot(0, notify, &KNotification::sendEvent); } return ret; } QByteArray KoDocument::mimeType() const { return d->mimeType; } void KoDocument::setMimeType(const QByteArray & mimeType) { d->mimeType = mimeType; } void KoDocument::setOutputMimeType(const QByteArray & mimeType, int specialOutputFlag) { d->outputMimeType = mimeType; d->specialOutputFlag = specialOutputFlag; } QByteArray KoDocument::outputMimeType() const { return d->outputMimeType; } int KoDocument::specialOutputFlag() const { return d->specialOutputFlag; } bool KoDocument::confirmNonNativeSave(const bool exporting) const { // "exporting ? 1 : 0" is different from "exporting" because a bool is // usually implemented like an "int", not "unsigned : 1" return d->confirmNonNativeSave [ exporting ? 1 : 0 ]; } void KoDocument::setConfirmNonNativeSave(const bool exporting, const bool on) { d->confirmNonNativeSave [ exporting ? 1 : 0] = on; } bool KoDocument::saveInBatchMode() const { return d->filterManager->getBatchMode(); } void KoDocument::setSaveInBatchMode(const bool batchMode) { d->filterManager->setBatchMode(batchMode); } bool KoDocument::isImporting() const { return d->isImporting; } bool KoDocument::isExporting() const { return d->isExporting; } void KoDocument::setCheckAutoSaveFile(bool b) { d->shouldCheckAutoSaveFile = b; } void KoDocument::setAutoErrorHandlingEnabled(bool b) { d->autoErrorHandlingEnabled = b; } bool KoDocument::isAutoErrorHandlingEnabled() const { return d->autoErrorHandlingEnabled; } void KoDocument::slotAutoSave() { if (d->modified && d->modifiedAfterAutosave && !d->isLoading) { // Give a warning when trying to autosave an encrypted file when no password is known (should not happen) if (d->specialOutputFlag == SaveEncrypted && d->password.isNull()) { // That advice should also fix this error from occurring again emit statusBarMessage(i18n("The password of this encrypted document is not known. Autosave aborted! Please save your work manually.")); } else { connect(this, &KoDocument::sigProgress, d->parentPart->currentMainwindow(), &KoMainWindow::slotProgress); emit statusBarMessage(i18n("Autosaving...")); d->autosaving = true; bool ret = saveNativeFormat(autoSaveFile(localFilePath())); setModified(true); if (ret) { d->modifiedAfterAutosave = false; d->autoSaveTimer.stop(); // until the next change } d->autosaving = false; emit clearStatusBarMessage(); disconnect(this, &KoDocument::sigProgress, d->parentPart->currentMainwindow(), &KoMainWindow::slotProgress); if (!ret && !d->disregardAutosaveFailure) { emit statusBarMessage(i18n("Error during autosave! Partition full?")); } } } } void KoDocument::setReadWrite(bool readwrite) { d->readwrite = readwrite; setAutoSave(d->autoSaveDelay); // XXX: this doesn't belong in KoDocument foreach(KoView *view, d->parentPart->views()) { view->updateReadWrite(readwrite); } foreach(KoMainWindow *mainWindow, d->parentPart->mainWindows()) { mainWindow->setReadWrite(readwrite); } } void KoDocument::setAutoSave(int delay) { d->autoSaveDelay = delay; if (isReadWrite() && d->autoSaveDelay > 0) d->autoSaveTimer.start(d->autoSaveDelay * 1000); else d->autoSaveTimer.stop(); } KoDocumentInfo *KoDocument::documentInfo() const { return d->docInfo; } /* KoDocumentRdfBase *KoDocument::documentRdf() const { return d->docRdf; } void KoDocument::setDocumentRdf(KoDocumentRdfBase *rdfDocument) { delete d->docRdf; d->docRdf = rdfDocument; } */ bool KoDocument::isModified() const { return d->modified; } bool KoDocument::saveNativeFormat(const QString & file) { d->lastErrorMessage.clear(); KoStore::Backend backend = KoStore::Auto; if (d->specialOutputFlag == SaveAsDirectoryStore) { backend = KoStore::Directory; debugMain << "Saving as uncompressed XML, using directory store."; } #ifdef QCA2 else if (d->specialOutputFlag == SaveEncrypted) { backend = KoStore::Encrypted; debugMain << "Saving using encrypted backend."; } #endif else if (d->specialOutputFlag == SaveAsFlatXML) { debugMain << "Saving as a flat XML file."; QFile f(file); if (f.open(QIODevice::WriteOnly | QIODevice::Text)) { bool success = saveToStream(&f); f.close(); return success; } else return false; } debugMain << "KoDocument::saveNativeFormat nativeFormatMimeType=" << nativeFormatMimeType(); // OLD: bool oasis = d->specialOutputFlag == SaveAsOASIS; // OLD: QCString mimeType = oasis ? nativeOasisMimeType() : nativeFormatMimeType(); QByteArray mimeType = d->outputMimeType; debugMain << "KoDocument::savingTo mimeType=" << mimeType; QByteArray nativeOasisMime = nativeOasisMimeType(); bool oasis = !mimeType.isEmpty() && (mimeType == nativeOasisMime || mimeType == nativeOasisMime + "-template" || mimeType.startsWith("application/vnd.oasis.opendocument")); // TODO: use std::auto_ptr or create store on stack [needs API fixing], // to remove all the 'delete store' in all the branches KoStore *store = KoStore::createStore(file, KoStore::Write, mimeType, backend); if (d->specialOutputFlag == SaveEncrypted && !d->password.isNull()) store->setPassword(d->password); if (store->bad()) { d->lastErrorMessage = i18n("Could not create the file for saving"); // more details needed? delete store; return false; } if (oasis) { return saveNativeFormatODF(store, mimeType); } else { return saveNativeFormatCalligra(store); } } bool KoDocument::saveNativeFormatODF(KoStore *store, const QByteArray &mimeType) { debugMain << "Saving to OASIS format"; // Tell KoStore not to touch the file names KoOdfWriteStore odfStore(store); KoXmlWriter *manifestWriter = odfStore.manifestWriter(mimeType); KoEmbeddedDocumentSaver embeddedSaver; SavingContext documentContext(odfStore, embeddedSaver); if (!saveOdf(documentContext)) { debugMain << "saveOdf failed"; odfStore.closeManifestWriter(false); delete store; return false; } // Save embedded objects if (!embeddedSaver.saveEmbeddedDocuments(documentContext)) { debugMain << "save embedded documents failed"; odfStore.closeManifestWriter(false); delete store; return false; } if (store->open("meta.xml")) { if (!d->docInfo->saveOasis(store) || !store->close()) { odfStore.closeManifestWriter(false); delete store; return false; } manifestWriter->addManifestEntry("meta.xml", "text/xml"); } else { d->lastErrorMessage = i18n("Not able to write '%1'. Partition full?", QString("meta.xml")); odfStore.closeManifestWriter(false); delete store; return false; } /* if (d->docRdf && !d->docRdf->saveOasis(store, manifestWriter)) { d->lastErrorMessage = i18n("Not able to write RDF metadata. Partition full?"); odfStore.closeManifestWriter(false); delete store; return false; } */ if (store->open("Thumbnails/thumbnail.png")) { if (!saveOasisPreview(store, manifestWriter) || !store->close()) { d->lastErrorMessage = i18n("Error while trying to write '%1'. Partition full?", QString("Thumbnails/thumbnail.png")); odfStore.closeManifestWriter(false); delete store; return false; } // No manifest entry! } else { d->lastErrorMessage = i18n("Not able to write '%1'. Partition full?", QString("Thumbnails/thumbnail.png")); odfStore.closeManifestWriter(false); delete store; return false; } if (!d->versionInfo.isEmpty()) { if (store->open("VersionList.xml")) { KoStoreDevice dev(store); KoXmlWriter *xmlWriter = KoOdfWriteStore::createOasisXmlWriter(&dev, "VL:version-list"); for (int i = 0; i < d->versionInfo.size(); ++i) { KoVersionInfo *version = &d->versionInfo[i]; xmlWriter->startElement("VL:version-entry"); xmlWriter->addAttribute("VL:title", version->title); xmlWriter->addAttribute("VL:comment", version->comment); xmlWriter->addAttribute("VL:creator", version->saved_by); xmlWriter->addAttribute("dc:date-time", version->date.toString(Qt::ISODate)); xmlWriter->endElement(); } xmlWriter->endElement(); // root element xmlWriter->endDocument(); delete xmlWriter; store->close(); manifestWriter->addManifestEntry("VersionList.xml", "text/xml"); for (int i = 0; i < d->versionInfo.size(); ++i) { KoVersionInfo *version = &d->versionInfo[i]; store->addDataToFile(version->data, "Versions/" + version->title); } } else { d->lastErrorMessage = i18n("Not able to write '%1'. Partition full?", QString("VersionList.xml")); odfStore.closeManifestWriter(false); delete store; return false; } } // Write out manifest file if (!odfStore.closeManifestWriter()) { d->lastErrorMessage = i18n("Error while trying to write '%1'. Partition full?", QString("META-INF/manifest.xml")); delete store; return false; } // Remember the given password, if necessary if (store->isEncrypted() && !d->isExporting) d->password = store->password(); delete store; return true; } bool KoDocument::saveNativeFormatCalligra(KoStore *store) { debugMain << "Saving root"; if (store->open("root")) { KoStoreDevice dev(store); if (!saveToStream(&dev) || !store->close()) { debugMain << "saveToStream failed"; delete store; return false; } } else { d->lastErrorMessage = i18n("Not able to write '%1'. Partition full?", QString("maindoc.xml")); delete store; return false; } if (store->open("documentinfo.xml")) { QDomDocument doc = KoDocument::createDomDocument("document-info" /*DTD name*/, "document-info" /*tag name*/, "1.1"); doc = d->docInfo->save(doc); KoStoreDevice dev(store); QByteArray s = doc.toByteArray(); // this is already Utf8! (void)dev.write(s.data(), s.size()); (void)store->close(); } if (store->open("preview.png")) { // ### TODO: missing error checking (The partition could be full!) savePreview(store); (void)store->close(); } if (!completeSaving(store)) { delete store; return false; } debugMain << "Saving done of url:" << url().url(); if (!store->finalize()) { delete store; return false; } // Success delete store; return true; } bool KoDocument::saveToStream(QIODevice *dev) { QDomDocument doc = saveXML(); // Save to buffer QByteArray s = doc.toByteArray(); // utf8 already dev->open(QIODevice::WriteOnly); int nwritten = dev->write(s.data(), s.size()); if (nwritten != (int)s.size()) warnMain << "wrote " << nwritten << "- expected" << s.size(); return nwritten == (int)s.size(); } QString KoDocument::checkImageMimeTypes(const QString &mimeType, const QUrl &url) const { if (!url.isLocalFile()) return mimeType; if (url.toLocalFile().endsWith(".kpp")) return "image/png"; QStringList imageMimeTypes; imageMimeTypes << "image/jpeg" << "image/x-psd" << "image/photoshop" << "image/x-photoshop" << "image/x-vnd.adobe.photoshop" << "image/vnd.adobe.photoshop" << "image/x-portable-pixmap" << "image/x-portable-graymap" << "image/x-portable-bitmap" << "application/pdf" << "image/x-exr" << "image/x-xcf" << "image/x-eps" << "image/png" << "image/bmp" << "image/x-xpixmap" << "image/gif" << "image/x-xbitmap" << "image/tiff" << "image/jp2"; if (!imageMimeTypes.contains(mimeType)) return mimeType; QFile f(url.toLocalFile()); f.open(QIODevice::ReadOnly); QByteArray ba = f.read(qMin(f.size(), (qint64)512)); // should be enough for images QMimeType mime = QMimeDatabase().mimeTypeForData(ba); f.close(); return mime.name(); } // Called for embedded documents bool KoDocument::saveToStore(KoStore *_store, const QString & _path) { debugMain << "Saving document to store" << _path; _store->pushDirectory(); // Use the path as the internal url if (_path.startsWith(STORE_PROTOCOL)) setUrl(QUrl(_path)); else // ugly hack to pass a relative URI setUrl(QUrl(INTERNAL_PREFIX + _path)); // In the current directory we're the king :-) if (_store->open("root")) { KoStoreDevice dev(_store); if (!saveToStream(&dev)) { _store->close(); return false; } if (!_store->close()) return false; } if (!completeSaving(_store)) return false; // Now that we're done leave the directory again _store->popDirectory(); debugMain << "Saved document to store"; return true; } bool KoDocument::saveOasisPreview(KoStore *store, KoXmlWriter *manifestWriter) { const QPixmap pix = generatePreview(QSize(128, 128)); if (pix.isNull()) return true; //no thumbnail to save, but the process succeeded QImage preview(pix.toImage().convertToFormat(QImage::Format_ARGB32, Qt::ColorOnly)); if (preview.isNull()) return false; //thumbnail to save, but the process failed // ### TODO: freedesktop.org Thumbnail specification (date...) KoStoreDevice io(store); if (!io.open(QIODevice::WriteOnly)) return false; if (! preview.save(&io, "PNG", 0)) return false; io.close(); manifestWriter->addManifestEntry("Thumbnails/thumbnail.png", "image/png"); return true; } bool KoDocument::savePreview(KoStore *store) { QPixmap pix = generatePreview(QSize(256, 256)); const QImage preview(pix.toImage().convertToFormat(QImage::Format_ARGB32, Qt::ColorOnly)); KoStoreDevice io(store); if (!io.open(QIODevice::WriteOnly)) return false; if (! preview.save(&io, "PNG")) // ### TODO What is -9 in quality terms? return false; io.close(); return true; } QPixmap KoDocument::generatePreview(const QSize& size) { qreal docWidth, docHeight; int pixmapSize = qMax(size.width(), size.height()); if (d->pageLayout.width > 1.0) { docWidth = d->pageLayout.width / 72 * KoDpi::dpiX(); docHeight = d->pageLayout.height / 72 * KoDpi::dpiY(); } else { // If we don't have a page layout, just draw the top left hand corner docWidth = 500.0; docHeight = 500.0; } qreal ratio = docWidth / docHeight; int previewWidth, previewHeight; if (ratio > 1.0) { previewWidth = (int) pixmapSize; previewHeight = (int)(pixmapSize / ratio); } else { previewWidth = (int)(pixmapSize * ratio); previewHeight = (int) pixmapSize; } QPixmap pix((int)docWidth, (int)docHeight); pix.fill(QColor(245, 245, 245)); QRect rc(0, 0, pix.width(), pix.height()); QPainter p; p.begin(&pix); paintContent(p, rc); p.end(); return pix.scaled(QSize(previewWidth, previewHeight), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); } QString KoDocument::autoSaveFile(const QString & path) const { QString retval; // Using the extension allows to avoid relying on the mime magic when opening QMimeType mime = QMimeDatabase().mimeTypeForName(nativeFormatMimeType()); if (! mime.isValid()) { qFatal("It seems your installation is broken/incomplete because we failed to load the native mimetype \"%s\".", nativeFormatMimeType().constData()); } const QString extension = mime.preferredSuffix(); if (path.isEmpty()) { // Never saved? #ifdef Q_OS_WIN // On Windows, use the temp location (https://bugs.kde.org/show_bug.cgi?id=314921) retval = QString("%1/.%2-%3-%4-autosave%5").arg(QDir::tempPath()).arg(d->parentPart->componentData().componentName()).arg(QApplication::applicationPid()).arg(objectName()).arg(extension); #else // On Linux, use a temp file in $HOME then. Mark it with the pid so two instances don't overwrite each other's autosave file retval = QString("%1/.%2-%3-%4-autosave%5").arg(QDir::homePath()).arg(d->parentPart->componentData().componentName()).arg(QApplication::applicationPid()).arg(objectName()).arg(extension); #endif } else { QUrl url = QUrl::fromLocalFile(path); Q_ASSERT(url.isLocalFile()); QString dir = QFileInfo(url.toLocalFile()).absolutePath(); QString filename = url.fileName(); retval = QString("%1.%2-autosave%3").arg(dir).arg(filename).arg(extension); } return retval; } void KoDocument::setDisregardAutosaveFailure(bool disregardFailure) { d->disregardAutosaveFailure = disregardFailure; } bool KoDocument::importDocument(const QUrl &_url) { bool ret; debugMain << "url=" << _url.url(); d->isImporting = true; // open... ret = openUrl(_url); // reset url & m_file (kindly? set by KoParts::openUrl()) to simulate a // File --> Import if (ret) { debugMain << "success, resetting url"; resetURL(); setTitleModified(); } d->isImporting = false; return ret; } bool KoDocument::openUrl(const QUrl &_url) { debugMain << "url=" << _url.url(); d->lastErrorMessage.clear(); // Reimplemented, to add a check for autosave files and to improve error reporting if (!_url.isValid()) { d->lastErrorMessage = i18n("Malformed URL\n%1", _url.url()); // ## used anywhere ? return false; } abortLoad(); QUrl url(_url); bool autosaveOpened = false; d->isLoading = true; if (url.isLocalFile() && d->shouldCheckAutoSaveFile) { QString file = url.toLocalFile(); QString asf = autoSaveFile(file); if (QFile::exists(asf)) { //debugMain <<"asf=" << asf; // ## TODO compare timestamps ? int res = KMessageBox::warningYesNoCancel(0, i18n("An autosaved file exists for this document.\nDo you want to open it instead?")); switch (res) { case KMessageBox::Yes : url.setPath(asf); autosaveOpened = true; break; case KMessageBox::No : QFile::remove(asf); break; default: // Cancel d->isLoading = false; return false; } } } bool ret = openUrlInternal(url); if (autosaveOpened) { resetURL(); // Force save to act like 'Save As' setReadWrite(true); // enable save button setModified(true); } - else { + else if (ret ) { d->parentPart->addRecentURLToAllMainWindows(projectName(), _url); - if (ret) { - // Detect readonly local-files; remote files are assumed to be writable, unless we add a KIO::stat here (async). - KFileItem file(url, mimeType(), KFileItem::Unknown); - setReadWrite(file.isWritable()); - } + // Detect readonly local-files; remote files are assumed to be writable, unless we add a KIO::stat here (async). + KFileItem file(url, mimeType(), KFileItem::Unknown); + setReadWrite(file.isWritable()); } return ret; } // It seems that people have started to save .docx files as .doc and // similar for xls and ppt. So let's make a small replacement table // here and see if we can open the files anyway. static const struct MimetypeReplacement { const char *typeFromName; // If the mime type from the name is this... const char *typeFromContents; // ...and findByFileContents() reports this type... const char *useThisType; // ...then use this type for real. } replacementMimetypes[] = { // doc / docx { "application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "application/vnd.openxmlformats-officedocument.wordprocessingml.document" }, { "application/msword", "application/zip", "application/vnd.openxmlformats-officedocument.wordprocessingml.document" }, { "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "application/msword", "application/msword" }, { "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "application/x-ole-storage", "application/msword" }, // xls / xlsx { "application/vnd.ms-excel", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" }, { "application/vnd.ms-excel", "application/zip", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" }, { "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "application/vnd.ms-excel", "application/vnd.ms-excel" }, { "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "application/x-ole-storage", "application/vnd.ms-excel" }, // ppt / pptx { "application/vnd.ms-powerpoint", "application/vnd.openxmlformats-officedocument.presentationml.presentation", "application/vnd.openxmlformats-officedocument.presentationml.presentation" }, { "application/vnd.ms-powerpoint", "application/zip", "application/vnd.openxmlformats-officedocument.presentationml.presentation" }, { "application/vnd.openxmlformats-officedocument.presentationml.presentation", "application/vnd.ms-powerpoint", "application/vnd.ms-powerpoint" }, { "application/vnd.openxmlformats-officedocument.presentationml.presentation", "application/x-ole-storage", "application/vnd.ms-powerpoint" } }; bool KoDocument::openFile() { //debugMain <<"for" << localFilePath(); if (!QFile::exists(localFilePath())) { QApplication::restoreOverrideCursor(); if (d->autoErrorHandlingEnabled) // Maybe offer to create a new document with that name ? KMessageBox::error(0, i18n("The file %1 does not exist.", localFilePath())); d->isLoading = false; return false; } QApplication::setOverrideCursor(Qt::WaitCursor); d->specialOutputFlag = 0; QByteArray _native_format = nativeFormatMimeType(); QUrl u = QUrl::fromLocalFile(localFilePath()); QString typeName = mimeType(); if (typeName.isEmpty()) { typeName = QMimeDatabase().mimeTypeForUrl(u).name(); } // for images, always check content. typeName = checkImageMimeTypes(typeName, u); // Sometimes it seems that arguments().mimeType() contains a much // too generic mime type. In that case, let's try some educated // guesses based on what we know about file extension. // // FIXME: Should we just ignore this and always call // KMimeType::findByUrl()? David Faure says that it's // impossible for findByUrl() to fail to initiate the // mimetype for "*.doc" to application/msword. This hints // that we should do that. But why does it happen like // this at all? if (typeName == "application/zip") { QString filename = u.fileName(); // None of doc, xls or ppt are really zip files. But docx, // xlsx and pptx are. This miscategorization seems to only // crop up when there is a, say, docx file saved as doc. The // conversion to the docx mimetype will happen below. if (filename.endsWith(".doc")) typeName = "application/msword"; else if (filename.endsWith(".xls")) typeName = "application/vnd.ms-excel"; else if (filename.endsWith(".ppt")) typeName = "application/vnd.ms-powerpoint"; // Potentially more guesses here... } else if (typeName == "application/x-ole-storage") { QString filename = u.fileName(); // None of docx, xlsx or pptx are really OLE files. But doc, // xls and ppt are. This miscategorization seems to only crop // up when there is a, say, doc file saved as docx. The // conversion to the doc mimetype will happen below. if (filename.endsWith(".docx")) typeName = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"; else if (filename.endsWith(".xlsx")) typeName = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; else if (filename.endsWith(".pptx")) typeName = "application/vnd.openxmlformats-officedocument.presentationml.presentation"; // Potentially more guesses here... } //debugMain << "mimetypes 3:" << typeName; // In some cases docx files are saved as doc and similar. We have // a small hardcoded table for those cases. Check if this is // applicable here. for (uint i = 0; i < sizeof(replacementMimetypes) / sizeof(struct MimetypeReplacement); ++i) { const MimetypeReplacement *replacement = &replacementMimetypes[i]; if (typeName == replacement->typeFromName) { //debugMain << "found potential replacement target:" << typeName; // QT5TODO: this needs a new look with the different behaviour of QMimeDatabase QString typeFromContents = QMimeDatabase().mimeTypeForUrl(u).name(); //debugMain << "found potential replacement:" << typeFromContents; if (typeFromContents == replacement->typeFromContents) { typeName = replacement->useThisType; //debugMain << "So really use this:" << typeName; break; } } } //debugMain << "mimetypes 4:" << typeName; // Allow to open backup files, don't keep the mimetype application/x-trash. if (typeName == "application/x-trash") { QString path = u.path(); QMimeDatabase db; QMimeType mime = db.mimeTypeForName(typeName); const QStringList patterns = mime.isValid() ? mime.globPatterns() : QStringList(); // Find the extension that makes it a backup file, and remove it for (QStringList::ConstIterator it = patterns.begin(); it != patterns.end(); ++it) { QString ext = *it; if (!ext.isEmpty() && ext[0] == '*') { ext.remove(0, 1); if (path.endsWith(ext)) { path.chop(ext.length()); break; } } } typeName = db.mimeTypeForFile(path, QMimeDatabase::MatchExtension).name(); } // Special case for flat XML files (e.g. using directory store) if (u.fileName() == "maindoc.xml" || u.fileName() == "content.xml" || typeName == "inode/directory") { typeName = _native_format; // Hmm, what if it's from another app? ### Check mimetype d->specialOutputFlag = SaveAsDirectoryStore; debugMain << "loading" << u.fileName() << ", using directory store for" << localFilePath() << "; typeName=" << typeName; } debugMain << localFilePath() << "type:" << typeName; QString importedFile = localFilePath(); // create the main progress monitoring object for loading, this can // contain subtasks for filtering and loading KoProgressProxy *progressProxy = 0; if (d->progressProxy) { progressProxy = d->progressProxy; } d->progressUpdater = new KoProgressUpdater(progressProxy, KoProgressUpdater::Unthreaded, d->profileStream); d->progressUpdater->setReferenceTime(d->profileReferenceTime); d->progressUpdater->start(100, i18n("Opening Document")); setupOpenFileSubProgress(); if (!isNativeFormat(typeName.toLatin1())) { KoFilter::ConversionStatus status; importedFile = d->filterManager->importDocument(localFilePath(), typeName, status); if (status != KoFilter::OK) { QApplication::restoreOverrideCursor(); QString msg; switch (status) { case KoFilter::OK: break; case KoFilter::FilterCreationError: msg = i18n("Could not create the filter plugin"); break; case KoFilter::CreationError: msg = i18n("Could not create the output document"); break; case KoFilter::FileNotFound: msg = i18n("File not found"); break; case KoFilter::StorageCreationError: msg = i18n("Cannot create storage"); break; case KoFilter::BadMimeType: msg = i18n("Bad MIME type"); break; case KoFilter::EmbeddedDocError: msg = i18n("Error in embedded document"); break; case KoFilter::WrongFormat: msg = i18n("Format not recognized"); break; case KoFilter::NotImplemented: msg = i18n("Not implemented"); break; case KoFilter::ParsingError: msg = i18n("Parsing error"); break; case KoFilter::PasswordProtected: msg = i18n("Document is password protected"); break; case KoFilter::InvalidFormat: msg = i18n("Invalid file format"); break; case KoFilter::InternalError: case KoFilter::UnexpectedEOF: case KoFilter::UnexpectedOpcode: case KoFilter::StupidError: // ?? what is this ?? case KoFilter::UsageError: msg = i18n("Internal error"); break; case KoFilter::OutOfMemory: msg = i18n("Out of memory"); break; case KoFilter::FilterEntryNull: msg = i18n("Empty Filter Plugin"); break; case KoFilter::NoDocumentCreated: msg = i18n("Trying to load into the wrong kind of document"); break; case KoFilter::DownloadFailed: msg = i18n("Failed to download remote file"); break; case KoFilter::UserCancelled: case KoFilter::BadConversionGraph: // intentionally we do not prompt the error message here break; default: msg = i18n("Unknown error"); break; } if (d->autoErrorHandlingEnabled && !msg.isEmpty()) { QString errorMsg(i18n("Could not open %2.\nReason: %1.\n%3", msg, prettyPathOrUrl(), errorMessage())); KMessageBox::error(0, errorMsg); } d->isLoading = false; delete d->progressUpdater; d->progressUpdater = 0; return false; } d->isEmpty = false; debugMain << "importedFile" << importedFile << "status:" << static_cast(status); } QApplication::restoreOverrideCursor(); bool ok = true; if (!importedFile.isEmpty()) { // Something to load (tmp or native file) ? // The filter, if any, has been applied. It's all native format now. if (!loadNativeFormat(importedFile)) { ok = false; if (d->autoErrorHandlingEnabled) { showLoadingErrorDialog(); } } } if (importedFile != localFilePath()) { // We opened a temporary file (result of an import filter) // Set document URL to empty - we don't want to save in /tmp ! // But only if in readwrite mode (no saving problem otherwise) // -- // But this isn't true at all. If this is the result of an // import, then importedFile=temporary_file.kwd and // file/m_url=foreignformat.ext so m_url is correct! // So don't resetURL() or else the caption won't be set when // foreign files are opened (an annoying bug). // - Clarence // #if 0 if (isReadWrite()) resetURL(); #endif // remove temp file - uncomment this to debug import filters if (!importedFile.isEmpty()) { #ifndef NDEBUG if (!getenv("CALLIGRA_DEBUG_FILTERS")) #endif QFile::remove(importedFile); } } if (ok) { setMimeTypeAfterLoading(typeName); KNotification *notify = new KNotification("DocumentLoaded"); notify->setText(i18n("Document %1 loaded", url().url())); notify->addContext("url", url().url()); QTimer::singleShot(0, notify, &KNotification::sendEvent); } if (progressUpdater()) { QPointer updater = progressUpdater()->startSubtask(1, "clear undo stack"); updater->setProgress(0); undoStack()->clear(); updater->setProgress(100); } delete d->progressUpdater; d->progressUpdater = 0; d->isLoading = false; return ok; } KoProgressUpdater *KoDocument::progressUpdater() const { return d->progressUpdater; } void KoDocument::setProgressProxy(KoProgressProxy *progressProxy) { d->progressProxy = progressProxy; } KoProgressProxy* KoDocument::progressProxy() const { if (!d->progressProxy) { KoMainWindow *mainWindow = 0; if (d->parentPart->mainwindowCount() > 0) { mainWindow = d->parentPart->mainWindows()[0]; } d->progressProxy = new DocumentProgressProxy(mainWindow); } return d->progressProxy; } // shared between openFile and koMainWindow's "create new empty document" code void KoDocument::setMimeTypeAfterLoading(const QString& mimeType) { d->mimeType = mimeType.toLatin1(); d->outputMimeType = d->mimeType; const bool needConfirm = !isNativeFormat(d->mimeType); setConfirmNonNativeSave(false, needConfirm); setConfirmNonNativeSave(true, needConfirm); } // The caller must call store->close() if loadAndParse returns true. bool KoDocument::oldLoadAndParse(KoStore *store, const QString& filename, KoXmlDocument& doc) { //debugMain <<"Trying to open" << filename; if (!store->open(filename)) { warnMain << "Entry " << filename << " not found!"; d->lastErrorMessage = i18n("Could not find %1", filename); return false; } // Error variables for QDomDocument::setContent QString errorMsg; int errorLine, errorColumn; bool ok = doc.setContent(store->device(), &errorMsg, &errorLine, &errorColumn); store->close(); if (!ok) { errorMain << "Parsing error in " << filename << "! Aborting!" << endl << " In line: " << errorLine << ", column: " << errorColumn << endl << " Error message: " << errorMsg << endl; d->lastErrorMessage = i18n("Parsing error in %1 at line %2, column %3\nError message: %4" , filename , errorLine, errorColumn , QCoreApplication::translate("QXml", errorMsg.toUtf8(), 0)); return false; } debugMain << "File" << filename << " loaded and parsed"; return true; } bool KoDocument::loadNativeFormat(const QString & file_) { QString file = file_; QFileInfo fileInfo(file); if (!fileInfo.exists()) { // check duplicated from openUrl, but this is useful for templates d->lastErrorMessage = i18n("The file %1 does not exist.", file); return false; } if (!fileInfo.isFile()) { file += "/content.xml"; QFileInfo fileInfo2(file); if (!fileInfo2.exists() || !fileInfo2.isFile()) { d->lastErrorMessage = i18n("%1 is not a file." , file_); return false; } } QApplication::setOverrideCursor(Qt::WaitCursor); debugMain << file; QFile in; bool isRawXML = false; if (d->specialOutputFlag != SaveAsDirectoryStore) { // Don't try to open a directory ;) in.setFileName(file); if (!in.open(QIODevice::ReadOnly)) { QApplication::restoreOverrideCursor(); d->lastErrorMessage = i18n("Could not open the file for reading (check read permissions)."); return false; } char buf[6]; buf[5] = 0; int pos = 0; do { if (in.read(buf + pos , 1) < 1) { QApplication::restoreOverrideCursor(); in.close(); d->lastErrorMessage = i18n("Could not read the beginning of the file."); return false; } if (QChar(buf[pos]).isSpace()) continue; pos++; } while (pos < 5); isRawXML = (qstrnicmp(buf, "lastErrorMessage = i18n("parsing error in the main document at line %1, column %2\nError message: %3", errorLine, errorColumn, i18n(errorMsg.toUtf8())); res = false; } QApplication::restoreOverrideCursor(); in.close(); d->isEmpty = false; return res; } else { // It's a calligra store (tar.gz, zip, directory, etc.) in.close(); return loadNativeFormatFromStore(file); } } bool KoDocument::loadNativeFormatFromStore(const QString& file) { KoStore::Backend backend = (d->specialOutputFlag == SaveAsDirectoryStore) ? KoStore::Directory : KoStore::Auto; KoStore *store = KoStore::createStore(file, KoStore::Read, "", backend); if (store->bad()) { d->lastErrorMessage = i18n("Not a valid Calligra file: %1", file); delete store; QApplication::restoreOverrideCursor(); return false; } // Remember that the file was encrypted if (d->specialOutputFlag == 0 && store->isEncrypted() && !d->isImporting) d->specialOutputFlag = SaveEncrypted; const bool success = loadNativeFormatFromStoreInternal(store); // Retrieve the password after loading the file, only then is it guaranteed to exist if (success && store->isEncrypted() && !d->isImporting) d->password = store->password(); delete store; return success; } bool KoDocument::loadNativeFormatFromStore(QByteArray &data) { bool succes; KoStore::Backend backend = (d->specialOutputFlag == SaveAsDirectoryStore) ? KoStore::Directory : KoStore::Auto; QBuffer buffer(&data); KoStore *store = KoStore::createStore(&buffer, KoStore::Read, "", backend); if (store->bad()) { delete store; return false; } // Remember that the file was encrypted if (d->specialOutputFlag == 0 && store->isEncrypted() && !d->isImporting) d->specialOutputFlag = SaveEncrypted; succes = loadNativeFormatFromStoreInternal(store); // Retrieve the password after loading the file, only then is it guaranteed to exist if (succes && store->isEncrypted() && !d->isImporting) d->password = store->password(); delete store; return succes; } bool KoDocument::loadNativeFormatFromStoreInternal(KoStore *store) { bool oasis = true; /* if (oasis && store->hasFile("manifest.rdf") && d->docRdf) { d->docRdf->loadOasis(store); } */ // OASIS/OOo file format? if (store->hasFile("content.xml")) { // We could check the 'mimetype' file, but let's skip that and be tolerant. if (!loadOasisFromStore(store)) { QApplication::restoreOverrideCursor(); return false; } } else if (store->hasFile("root") || store->hasFile("maindoc.xml")) { // Fallback to "old" file format (maindoc.xml) oasis = false; KoXmlDocument doc = KoXmlDocument(true); bool ok = oldLoadAndParse(store, "root", doc); if (ok) ok = loadXML(doc, store); if (!ok) { QApplication::restoreOverrideCursor(); return false; } } else { errorMain << "ERROR: No maindoc.xml" << endl; d->lastErrorMessage = i18n("Invalid document: no file 'maindoc.xml'."); QApplication::restoreOverrideCursor(); return false; } if (oasis && store->hasFile("meta.xml")) { KoXmlDocument metaDoc; KoOdfReadStore oasisStore(store); if (oasisStore.loadAndParse("meta.xml", metaDoc, d->lastErrorMessage)) { d->docInfo->loadOasis(metaDoc); } } else if (!oasis && store->hasFile("documentinfo.xml")) { KoXmlDocument doc = KoXmlDocument(true); if (oldLoadAndParse(store, "documentinfo.xml", doc)) { d->docInfo->load(doc); } } else { //kDebug( 30003 ) <<"cannot open document info"; delete d->docInfo; d->docInfo = new KoDocumentInfo(this); } if (oasis && store->hasFile("VersionList.xml")) { KNotification *notify = new KNotification("DocumentHasVersions"); notify->setText(i18n("Document %1 contains several versions. Go to File->Versions to open an old version.", store->urlOfStore().url())); notify->addContext("url", store->urlOfStore().url()); QTimer::singleShot(0, notify, &KNotification::sendEvent); KoXmlDocument versionInfo; KoOdfReadStore oasisStore(store); if (oasisStore.loadAndParse("VersionList.xml", versionInfo, d->lastErrorMessage)) { KoXmlNode list = KoXml::namedItemNS(versionInfo, KoXmlNS::VL, "version-list"); KoXmlElement e; forEachElement(e, list) { if (e.localName() == "version-entry" && e.namespaceURI() == KoXmlNS::VL) { KoVersionInfo version; version.comment = e.attribute("comment"); version.title = e.attribute("title"); version.saved_by = e.attribute("creator"); version.date = QDateTime::fromString(e.attribute("date-time"), Qt::ISODate); store->extractFile("Versions/" + version.title, version.data); d->versionInfo.append(version); } } } } bool res = completeLoading(store); QApplication::restoreOverrideCursor(); d->isEmpty = false; return res; } // For embedded documents bool KoDocument::loadFromStore(KoStore *_store, const QString& url) { if (_store->open(url)) { KoXmlDocument doc = KoXmlDocument(true); doc.setContent(_store->device()); if (!loadXML(doc, _store)) { _store->close(); return false; } _store->close(); } else { qWarning() << "couldn't open " << url; } _store->pushDirectory(); // Store as document URL if (url.startsWith(STORE_PROTOCOL)) { setUrl(QUrl::fromUserInput(url)); } else { setUrl(QUrl(INTERNAL_PREFIX + url)); _store->enterDirectory(url); } bool result = completeLoading(_store); // Restore the "old" path _store->popDirectory(); return result; } bool KoDocument::loadOasisFromStore(KoStore *store) { KoOdfReadStore odfStore(store); if (! odfStore.loadAndParse(d->lastErrorMessage)) { return false; } return loadOdf(odfStore); } bool KoDocument::addVersion(const QString& comment) { debugMain << "Saving the new version...."; KoStore::Backend backend = KoStore::Auto; if (d->specialOutputFlag != 0) return false; QByteArray mimeType = d->outputMimeType; QByteArray nativeOasisMime = nativeOasisMimeType(); bool oasis = !mimeType.isEmpty() && (mimeType == nativeOasisMime || mimeType == nativeOasisMime + "-template"); if (!oasis) return false; // TODO: use std::auto_ptr or create store on stack [needs API fixing], // to remove all the 'delete store' in all the branches QByteArray data; QBuffer buffer(&data); KoStore *store = KoStore::createStore(&buffer/*file*/, KoStore::Write, mimeType, backend); if (store->bad()) { delete store; return false; } debugMain << "Saving to OASIS format"; KoOdfWriteStore odfStore(store); KoXmlWriter *manifestWriter = odfStore.manifestWriter(mimeType); Q_UNUSED(manifestWriter); // XXX why? KoEmbeddedDocumentSaver embeddedSaver; SavingContext documentContext(odfStore, embeddedSaver); if (!saveOdf(documentContext)) { debugMain << "saveOdf failed"; delete store; return false; } // Save embedded objects if (!embeddedSaver.saveEmbeddedDocuments(documentContext)) { debugMain << "save embedded documents failed"; delete store; return false; } // Write out manifest file if (!odfStore.closeManifestWriter()) { d->lastErrorMessage = i18n("Error while trying to write '%1'. Partition full?", QString("META-INF/manifest.xml")); delete store; return false; } if (!store->finalize()) { delete store; return false; } delete store; KoVersionInfo version; version.comment = comment; version.title = "Version" + QString::number(d->versionInfo.count() + 1); version.saved_by = documentInfo()->authorInfo("creator"); version.date = QDateTime::currentDateTime(); version.data = data; d->versionInfo.append(version); save(); //finally save the document + the new version return true; } bool KoDocument::isStoredExtern() const { return !storeInternal() && hasExternURL(); } void KoDocument::setModified() { d->modified = true; } void KoDocument::setModified(bool mod) { if (isAutosaving()) // ignore setModified calls due to autosaving return; if ( !d->readwrite && d->modified ) { qCritical(/*1000*/) << "Can't set a read-only document to 'modified' !" << endl; return; } //debugMain<<" url:" << url.path(); //debugMain<<" mod="<The document '%1' has been modified.

Do you want to save it?

", name)); switch (res) { case KMessageBox::Yes : save(); // NOTE: External files always in native format. ###TODO: Handle non-native format setModified(false); // Now when queryClose() is called by closeEvent it won't do anything. break; case KMessageBox::No : removeAutoSaveFiles(); setModified(false); // Now when queryClose() is called by closeEvent it won't do anything. break; default : // case KMessageBox::Cancel : return res; // cancels the rest of the files } return res; } QString KoDocument::prettyPathOrUrl() const { QString _url(url().toDisplayString()); #ifdef Q_WS_WIN if (url().isLocalFile()) { _url = QDir::convertSeparators(_url); } #endif return _url; } // Note: We do not: Get caption from document info (title(), in about page) QString KoDocument::caption() const { QString c = url().fileName(); if (!c.isEmpty() && c.endsWith(".plan")) { c.remove(c.lastIndexOf(".plan"), 5); } return c; } void KoDocument::setTitleModified() { emit titleModified(caption(), isModified()); } bool KoDocument::completeLoading(KoStore*) { return true; } bool KoDocument::completeSaving(KoStore*) { return true; } QDomDocument KoDocument::createDomDocument(const QString& tagName, const QString& version) const { return createDomDocument(d->parentPart->componentData().componentName(), tagName, version); } //static QDomDocument KoDocument::createDomDocument(const QString& appName, const QString& tagName, const QString& version) { QDomImplementation impl; QString url = QString("http://www.calligra.org/DTD/%1-%2.dtd").arg(appName).arg(version); QDomDocumentType dtype = impl.createDocumentType(tagName, QString("-//KDE//DTD %1 %2//EN").arg(appName).arg(version), url); // The namespace URN doesn't need to include the version number. QString namespaceURN = QString("http://www.calligra.org/DTD/%1").arg(appName); QDomDocument doc = impl.createDocument(namespaceURN, tagName, dtype); doc.insertBefore(doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\""), doc.documentElement()); return doc; } QDomDocument KoDocument::saveXML() { errorMain << "not implemented" << endl; d->lastErrorMessage = i18n("Internal error: saveXML not implemented"); return QDomDocument(); } bool KoDocument::isNativeFormat(const QByteArray& mimetype) const { if (mimetype == nativeFormatMimeType()) return true; return extraNativeMimeTypes().contains(mimetype); } int KoDocument::supportedSpecialFormats() const { // Apps which support special output flags can add reimplement and add to this. // E.g. this is how did "saving in the 1.1 format". // SaveAsDirectoryStore is a given since it's implemented by KoDocument itself. // SaveEncrypted is implemented in KoDocument as well, if QCA2 was found. #ifdef QCA2 return SaveAsDirectoryStore | SaveEncrypted; #else return SaveAsDirectoryStore; #endif } void KoDocument::setErrorMessage(const QString& errMsg) { d->lastErrorMessage = errMsg; } QString KoDocument::errorMessage() const { return d->lastErrorMessage; } void KoDocument::showLoadingErrorDialog() { if (errorMessage().isEmpty()) { KMessageBox::error(0, i18n("Could not open\n%1", localFilePath())); } else if (errorMessage() != "USER_CANCELED") { KMessageBox::error(0, i18n("Could not open %1\nReason: %2", localFilePath(), errorMessage())); } } bool KoDocument::isAutosaving() const { return d->autosaving; } bool KoDocument::isLoading() const { return d->isLoading; } void KoDocument::removeAutoSaveFiles() { // Eliminate any auto-save file QString asf = autoSaveFile(localFilePath()); // the one in the current dir if (QFile::exists(asf)) QFile::remove(asf); asf = autoSaveFile(QString()); // and the one in $HOME if (QFile::exists(asf)) QFile::remove(asf); } void KoDocument::setBackupFile(bool _b) { if (d->backupFile != _b) { d->backupFile = _b; emit backupFileChanged(_b); } } bool KoDocument::backupFile()const { return d->backupFile; } void KoDocument::setBackupPath(const QString & _path) { d->backupPath = _path; } QString KoDocument::backupPath()const { return d->backupPath; } bool KoDocument::storeInternal() const { return d->storeInternal; } void KoDocument::setStoreInternal(bool i) { d->storeInternal = i; //debugMain<<"="<storeInternal<<" doc:"<pageLayout; } void KoDocument::setPageLayout(const KoPageLayout &pageLayout) { d->pageLayout = pageLayout; } KoUnit KoDocument::unit() const { return d->unit; } void KoDocument::setUnit(const KoUnit &unit) { if (d->unit != unit) { d->unit = unit; emit unitChanged(unit); } } void KoDocument::saveUnitOdf(KoXmlWriter *settingsWriter) const { settingsWriter->addConfigItem("unit", unit().symbol()); } void KoDocument::initEmpty() { setEmpty(); setModified(false); } QList & KoDocument::versionList() { return d->versionInfo; } KUndo2Stack *KoDocument::undoStack() { return d->undoStack; } void KoDocument::addCommand(KUndo2Command *command) { if (command) d->undoStack->push(command); } void KoDocument::beginMacro(const KUndo2MagicString & text) { d->undoStack->beginMacro(text); } void KoDocument::endMacro() { d->undoStack->endMacro(); } void KoDocument::slotUndoStackIndexChanged(int idx) { // even if the document was already modified, call setModified to re-start autosave timer setModified(idx != d->undoStack->cleanIndex()); } void KoDocument::setProfileStream(QTextStream *profilestream) { d->profileStream = profilestream; } void KoDocument::setProfileReferenceTime(const QTime& referenceTime) { d->profileReferenceTime = referenceTime; } void KoDocument::clearUndoHistory() { d->undoStack->clear(); } /* KoGridData &KoDocument::gridData() { return d->gridData; } KoGuidesData &KoDocument::guidesData() { return d->guidesData; } */ bool KoDocument::isEmpty() const { return d->isEmpty; } void KoDocument::setEmpty() { d->isEmpty = true; } // static int KoDocument::defaultAutoSave() { return 300; } void KoDocument::resetURL() { setUrl(QUrl()); setLocalFilePath(QString()); } int KoDocument::pageCount() const { return 1; } void KoDocument::setupOpenFileSubProgress() {} KoDocumentInfoDlg *KoDocument::createDocumentInfoDialog(QWidget *parent, KoDocumentInfo *docInfo) const { KoDocumentInfoDlg *dlg = new KoDocumentInfoDlg(parent, docInfo); KoMainWindow *mainwin = dynamic_cast(parent); if (mainwin) { connect(dlg, &KoDocumentInfoDlg::saveRequested, mainwin, &KoMainWindow::slotFileSave); } return dlg; } bool KoDocument::isReadWrite() const { return d->readwrite; } QUrl KoDocument::url() const { return d->m_url; } bool KoDocument::closeUrl(bool promptToSave) { abortLoad(); //just in case if (promptToSave) { if ( d->document->isReadWrite() && d->document->isModified()) { if (!queryClose()) return false; } } // Not modified => ok and delete temp file. d->mimeType = QByteArray(); if ( d->m_bTemp ) { QFile::remove( d->m_file ); d->m_bTemp = false; } // It always succeeds for a read-only part, // but the return value exists for reimplementations // (e.g. pressing cancel for a modified read-write part) return true; } bool KoDocument::saveAs( const QUrl &kurl ) { if (!kurl.isValid()) { qCritical(/*1000*/) << "saveAs: Malformed URL " << kurl.url() << endl; return false; } d->m_duringSaveAs = true; d->m_originalURL = d->m_url; d->m_originalFilePath = d->m_file; d->m_url = kurl; // Store where to upload in saveToURL d->prepareSaving(); bool result = save(); // Save local file and upload local file if (!result) { d->m_url = d->m_originalURL; d->m_file = d->m_originalFilePath; d->m_duringSaveAs = false; d->m_originalURL = QUrl(); d->m_originalFilePath.clear(); } return result; } bool KoDocument::save() { d->m_saveOk = false; if ( d->m_file.isEmpty() ) // document was created empty d->prepareSaving(); DocumentProgressProxy *progressProxy = 0; if (!d->document->progressProxy()) { KoMainWindow *mainWindow = 0; if (d->parentPart->mainwindowCount() > 0) { mainWindow = d->parentPart->mainWindows()[0]; } progressProxy = new DocumentProgressProxy(mainWindow); d->document->setProgressProxy(progressProxy); } d->document->setUrl(url()); // THIS IS WRONG! KoDocument::saveFile should move here, and whoever subclassed KoDocument to // reimplement saveFile should now subclass KoPart. bool ok = d->document->saveFile(); if (progressProxy) { d->document->setProgressProxy(0); delete progressProxy; } if (ok) { return saveToUrl(); } else { emit canceled(QString()); } return false; } bool KoDocument::waitSaveComplete() { if (!d->m_uploadJob) return d->m_saveOk; d->m_waitForSave = true; d->m_eventLoop.exec(QEventLoop::ExcludeUserInputEvents); d->m_waitForSave = false; return d->m_saveOk; } void KoDocument::abortLoad() { if ( d->m_statJob ) { //kDebug(1000) << "Aborting job" << d->m_statJob; d->m_statJob->kill(); d->m_statJob = 0; } if ( d->m_job ) { //kDebug(1000) << "Aborting job" << d->m_job; d->m_job->kill(); d->m_job = 0; } } void KoDocument::setUrl(const QUrl &url) { d->m_url = url; } QString KoDocument::localFilePath() const { return d->m_file; } void KoDocument::setLocalFilePath( const QString &localFilePath ) { d->m_file = localFilePath; } bool KoDocument::queryClose() { if ( !d->document->isReadWrite() || !d->document->isModified() ) return true; QString docName = url().fileName(); if (docName.isEmpty()) docName = i18n( "Untitled" ); int res = KMessageBox::warningYesNoCancel( 0, i18n( "The document \"%1\" has been modified.\n" "Do you want to save your changes or discard them?" , docName ), i18n( "Close Document" ), KStandardGuiItem::save(), KStandardGuiItem::discard() ); bool abortClose=false; bool handled=false; switch(res) { case KMessageBox::Yes : if (!handled) { if (d->m_url.isEmpty()) { KoMainWindow *mainWindow = 0; if (d->parentPart->mainWindows().count() > 0) { mainWindow = d->parentPart->mainWindows()[0]; } KoFileDialog dialog(mainWindow, KoFileDialog::SaveFile, "SaveDocument"); QUrl url = QUrl::fromLocalFile(dialog.filename()); if (url.isEmpty()) return false; saveAs( url ); } else { save(); } } else if (abortClose) return false; return waitSaveComplete(); case KMessageBox::No : return true; default : // case KMessageBox::Cancel : return false; } } bool KoDocument::saveToUrl() { if ( d->m_url.isLocalFile() ) { d->document->setModified( false ); emit completed(); // if m_url is a local file there won't be a temp file -> nothing to remove Q_ASSERT( !d->m_bTemp ); d->m_saveOk = true; d->m_duringSaveAs = false; d->m_originalURL = QUrl(); d->m_originalFilePath.clear(); return true; // Nothing to do } #ifndef Q_OS_WIN else { if (d->m_uploadJob) { QFile::remove(d->m_uploadJob->srcUrl().toLocalFile()); d->m_uploadJob->kill(); d->m_uploadJob = 0; } QTemporaryFile *tempFile = new QTemporaryFile(); tempFile->open(); QString uploadFile = tempFile->fileName(); delete tempFile; QUrl uploadUrl; uploadUrl.setPath( uploadFile ); // Create hardlink if (::link(QFile::encodeName(d->m_file), QFile::encodeName(uploadFile)) != 0) { // Uh oh, some error happened. return false; } d->m_uploadJob = KIO::file_move( uploadUrl, d->m_url, -1, KIO::Overwrite ); #ifndef QT_NO_DBUS KJobWidgets::setWindow(d->m_uploadJob, 0); #endif connect( d->m_uploadJob, SIGNAL(result(KJob*)), this, SLOT(_k_slotUploadFinished(KJob*)) ); // clazy:exclude=old-style-connect return true; } #else return false; #endif } bool KoDocument::openUrlInternal(const QUrl &url) { if ( !url.isValid() ) return false; if (d->m_bAutoDetectedMime) { d->mimeType = QByteArray(); d->m_bAutoDetectedMime = false; } QByteArray mimetype = d->mimeType; if ( !closeUrl() ) return false; d->mimeType = mimetype; setUrl(url); d->m_file.clear(); if (d->m_url.isLocalFile()) { d->m_file = d->m_url.toLocalFile(); return d->openLocalFile(); } else { d->openRemoteFile(); return true; } } // have to include this because of Q_PRIVATE_SLOT #include diff --git a/src/libs/main/KoMainWindow.cpp b/src/libs/main/KoMainWindow.cpp index 27e514bc..d7a2ce68 100644 --- a/src/libs/main/KoMainWindow.cpp +++ b/src/libs/main/KoMainWindow.cpp @@ -1,2174 +1,2196 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright (C) 2000-2006 David Faure Copyright (C) 2007, 2009 Thomas zander Copyright (C) 2010 Benjamin Port 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. */ // clazy:excludeall=qstring-arg #include "KoMainWindow.h" #include "KoView.h" #include "KoDocument.h" #include "KoFilterManager.h" #include "KoDocumentInfo.h" #include "KoDocumentInfoDlg.h" #include "KoFileDialog.h" #include "KoDockFactoryBase.h" #include "KoDockWidgetTitleBar.h" #include "KoPrintJob.h" #include "KoDocumentEntry.h" #include "KoPart.h" #include #include #include "KoApplication.h" #include #include "KoResourcePaths.h" #include "KoComponentData.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_KACTIVITIES #include #endif // // qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "MainDebug.h" class KoMainWindowPrivate { public: KoMainWindowPrivate(const QByteArray &_nativeMimeType, const KoComponentData &componentData_, KoMainWindow *w) : componentData(componentData_) { nativeMimeType = _nativeMimeType; parent = w; rootDocument = 0; rootPart = 0; partToOpen = 0; mainWindowGuiIsBuilt = false; forQuit = false; activePart = 0; activeView = 0; firstTime = true; progress = 0; showDocumentInfo = 0; saveAction = 0; saveActionAs = 0; printAction = 0; printActionPreview = 0; sendFileAction = 0; exportPdf = 0; closeFile = 0; reloadFile = 0; importFile = 0; exportFile = 0; #if 0 encryptDocument = 0; #ifndef NDEBUG uncompressToDir = 0; #endif #endif isImporting = false; isExporting = false; windowSizeDirty = false; lastExportSpecialOutputFlag = 0; readOnly = false; dockWidgetMenu = 0; deferredClosingEvent = 0; #ifdef HAVE_KACTIVITIES activityResource = 0; #endif m_helpMenu = 0; // PartManger m_activeWidget = 0; m_activePart = 0; noCleanup = false; openingDocument = false; printPreviewJob = nullptr; } ~KoMainWindowPrivate() { qDeleteAll(toolbarList); } void applyDefaultSettings(QPrinter &printer) { QString title = rootDocument->documentInfo()->aboutInfo("title"); if (title.isEmpty()) { title = rootDocument->url().fileName(); // strip off the native extension (I don't want foobar.kwd.ps when printing into a file) QMimeType mime = QMimeDatabase().mimeTypeForName(rootDocument->outputMimeType()); if (mime.isValid()) { const QString extension = mime.preferredSuffix(); if (title.endsWith(extension)) title.chop(extension.length()); } } if (title.isEmpty()) { // #139905 title = i18n("%1 unsaved document (%2)", parent->componentData().componentDisplayName(), QLocale().toString(QDate::currentDate(), QLocale::ShortFormat)); } printer.setDocName(title); } QByteArray nativeMimeType; KoMainWindow *parent; KoDocument *rootDocument; QList rootViews; // PartManager QPointer rootPart; QPointer partToOpen; QPointer activePart; QPointer m_activePart; QPointer m_registeredPart; KoView *activeView; QWidget *m_activeWidget; QPointer progress; QMutex progressMutex; QList toolbarList; bool mainWindowGuiIsBuilt; bool forQuit; bool firstTime; bool windowSizeDirty; bool readOnly; QAction *showDocumentInfo; QAction *saveAction; QAction *saveActionAs; QAction *printAction; QAction *printActionPreview; QAction *sendFileAction; QAction *exportPdf; QAction *closeFile; QAction *reloadFile; QAction *importFile; QAction *exportFile; #if 0 QAction *encryptDocument; #ifndef NDEBUG QAction *uncompressToDir; #endif #endif KToggleAction *toggleDockers; KToggleAction *toggleDockerTitleBars; KRecentFilesAction *recent; bool isImporting; bool isExporting; QUrl lastExportUrl; QByteArray lastExportedFormat; int lastExportSpecialOutputFlag; QMap dockWidgetsMap; KActionMenu *dockWidgetMenu; QMap dockWidgetVisibilityMap; QList dockWidgets; QByteArray m_dockerStateBeforeHiding; QCloseEvent *deferredClosingEvent; #ifdef HAVE_KACTIVITIES KActivities::ResourceInstance *activityResource; #endif KoComponentData componentData; KHelpMenu *m_helpMenu; bool noCleanup; bool openingDocument; KoPrintJob *printPreviewJob; QAction *configureAction; }; KoMainWindow::KoMainWindow(const QByteArray &nativeMimeType, const KoComponentData &componentData) : KXmlGuiWindow() , d(new KoMainWindowPrivate(nativeMimeType, componentData, this)) { #ifdef Q_OS_MAC setUnifiedTitleAndToolBarOnMac(true); #endif setStandardToolBarMenuEnabled(true); setTabPosition(Qt::AllDockWidgetAreas, QTabWidget::North); connect(this, &KoMainWindow::restoringDone, this, &KoMainWindow::forceDockTabFonts); // PartManager // End QString doc; const QStringList allFiles = KoResourcePaths::findAllResources("data", "calligraplan/calligraplan_shell.rc"); setXMLFile(findMostRecentXMLFile(allFiles, doc)); setLocalXMLFile(KoResourcePaths::locateLocal("data", "calligraplan/calligraplan_shell.rc")); actionCollection()->addAction(KStandardAction::New, "file_new", this, SLOT(slotFileNew())); actionCollection()->addAction(KStandardAction::Open, "file_open", this, SLOT(slotFileOpen())); d->recent = KStandardAction::openRecent(this, SLOT(slotFileOpenRecent(QUrl)), actionCollection()); connect(d->recent, &KRecentFilesAction::recentListCleared, this, &KoMainWindow::saveRecentFiles); d->saveAction = actionCollection()->addAction(KStandardAction::Save, "file_save", this, SLOT(slotFileSave())); d->saveActionAs = actionCollection()->addAction(KStandardAction::SaveAs, "file_save_as", this, SLOT(slotFileSaveAs())); d->printAction = actionCollection()->addAction(KStandardAction::Print, "file_print", this, SLOT(slotFilePrint())); d->printActionPreview = actionCollection()->addAction(KStandardAction::PrintPreview, "file_print_preview", this, SLOT(slotFilePrintPreview())); d->exportPdf = new QAction(i18n("Print to PDF..."), this); d->exportPdf->setIcon(koIcon("application-pdf")); actionCollection()->addAction("file_export_pdf", d->exportPdf); connect(d->exportPdf, &QAction::triggered, this, static_cast(&KoMainWindow::exportToPdf)); d->sendFileAction = actionCollection()->addAction(KStandardAction::Mail, "file_send_file", this, SLOT(slotEmailFile())); d->closeFile = actionCollection()->addAction(KStandardAction::Close, "file_close", this, SLOT(slotFileClose())); actionCollection()->addAction(KStandardAction::Quit, "file_quit", this, SLOT(slotFileQuit())); d->reloadFile = new QAction(i18n("Reload"), this); actionCollection()->addAction("file_reload_file", d->reloadFile); connect(d->reloadFile, &QAction::triggered, this, &KoMainWindow::slotReloadFile); d->importFile = new QAction(koIcon("document-import"), i18n("Import..."), this); actionCollection()->addAction("file_import_file", d->importFile); connect(d->importFile, &QAction::triggered, this, &KoMainWindow::slotImportFile); d->exportFile = new QAction(koIcon("document-export"), i18n("E&xport..."), this); actionCollection()->addAction("file_export_file", d->exportFile); connect(d->exportFile, &QAction::triggered, this, &KoMainWindow::slotExportFile); #if 0 // encryption not supported d->encryptDocument = new QAction(i18n("En&crypt Document"), this); actionCollection()->addAction("file_encrypt_doc", d->encryptDocument); connect(d->encryptDocument, SIGNAL(triggered(bool)), this, SLOT(slotEncryptDocument())); #ifndef NDEBUG d->uncompressToDir = new QAction(i18n("&Uncompress to Directory"), this); actionCollection()->addAction("file_uncompress_doc", d->uncompressToDir); connect(d->uncompressToDir, SIGNAL(triggered(bool)), this, SLOT(slotUncompressToDir())); #endif #endif QAction *actionNewView = new QAction(koIcon("window-new"), i18n("&New View"), this); actionCollection()->addAction("view_newview", actionNewView); connect(actionNewView, &QAction::triggered, this, &KoMainWindow::newView); /* The following entry opens the document information dialog. Since the action is named so it intends to show data this entry should not have a trailing ellipses (...). */ d->showDocumentInfo = new QAction(koIcon("document-properties"), i18n("Document Information"), this); actionCollection()->addAction("file_documentinfo", d->showDocumentInfo); connect(d->showDocumentInfo, &QAction::triggered, this, &KoMainWindow::slotDocumentInfo); KStandardAction::keyBindings(this, SLOT(slotConfigureKeys()), actionCollection()); KStandardAction::configureToolbars(this, SLOT(slotConfigureToolbars()), actionCollection()); d->showDocumentInfo->setEnabled(false); d->saveActionAs->setEnabled(false); d->reloadFile->setEnabled(false); d->importFile->setEnabled(true); // always enabled like File --> Open d->exportFile->setEnabled(false); d->saveAction->setEnabled(false); d->printAction->setEnabled(false); d->printActionPreview->setEnabled(false); d->sendFileAction->setEnabled(false); d->exportPdf->setEnabled(false); d->closeFile->setEnabled(false); #if 0 d->encryptDocument->setEnabled(false); #ifndef NDEBUG d->uncompressToDir->setEnabled(false); #endif #endif KToggleAction *fullscreenAction = new KToggleAction(koIcon("view-fullscreen"), i18n("Full Screen Mode"), this); actionCollection()->addAction("view_fullscreen", fullscreenAction); actionCollection()->setDefaultShortcut(fullscreenAction, QKeySequence::FullScreen); connect(fullscreenAction, &QAction::toggled, this, &KoMainWindow::viewFullscreen); d->toggleDockers = new KToggleAction(i18n("Show Dockers"), this); d->toggleDockers->setChecked(true); actionCollection()->addAction("view_toggledockers", d->toggleDockers); connect(d->toggleDockers, &QAction::toggled, this, &KoMainWindow::toggleDockersVisibility); d->toggleDockerTitleBars = new KToggleAction(i18nc("@action:inmenu", "Show Docker Titlebars"), this); KConfigGroup configGroupInterface = KSharedConfig::openConfig()->group("Interface"); d->toggleDockerTitleBars->setChecked(configGroupInterface.readEntry("ShowDockerTitleBars", true)); d->toggleDockerTitleBars->setVisible(false); actionCollection()->addAction("view_toggledockertitlebars", d->toggleDockerTitleBars); connect(d->toggleDockerTitleBars, &QAction::toggled, this, &KoMainWindow::showDockerTitleBars); d->dockWidgetMenu = new KActionMenu(i18n("Dockers"), this); actionCollection()->addAction("settings_dockers_menu", d->dockWidgetMenu); d->dockWidgetMenu->setVisible(false); d->dockWidgetMenu->setDelayed(false); d->configureAction = new QAction(koIcon("configure"), i18n("Configure Plan..."), this); actionCollection()->addAction("configure", d->configureAction ); connect(d->configureAction, &QAction::triggered, this, &KoMainWindow::slotConfigure ); // Load list of recent files KSharedConfigPtr configPtr = componentData.config(); d->recent->loadEntries(configPtr->group("RecentFiles")); createMainwindowGUI(); d->mainWindowGuiIsBuilt = true; // we first figure out some good default size and restore the x,y position. See bug 285804Z. KConfigGroup cfg( KSharedConfig::openConfig(), "MainWindow"); QByteArray geom = QByteArray::fromBase64(cfg.readEntry("ko_geometry", QByteArray())); if (!restoreGeometry(geom)) { const int scnum = QApplication::desktop()->screenNumber(parentWidget()); QRect desk = QApplication::desktop()->availableGeometry(scnum); // if the desktop is virtual then use virtual screen size if (QApplication::desktop()->isVirtualDesktop()) { desk = QApplication::desktop()->availableGeometry(QApplication::desktop()->screen()); desk = QApplication::desktop()->availableGeometry(QApplication::desktop()->screen(scnum)); } quint32 x = desk.x(); quint32 y = desk.y(); quint32 w = 0; quint32 h = 0; // Default size -- maximize on small screens, something useful on big screens const int deskWidth = desk.width(); if (deskWidth > 1024) { // a nice width, and slightly less than total available // height to componensate for the window decs w = (deskWidth / 3) * 2; h = (desk.height() / 3) * 2; } else { w = desk.width(); h = desk.height(); } x += (desk.width() - w) / 2; y += (desk.height() - h) / 2; move(x,y); setGeometry(geometry().x(), geometry().y(), w, h); } restoreState(QByteArray::fromBase64(cfg.readEntry("ko_windowstate", QByteArray()))); } void KoMainWindow::setNoCleanup(bool noCleanup) { d->noCleanup = noCleanup; } KoMainWindow::~KoMainWindow() { KConfigGroup cfg( KSharedConfig::openConfig(), "MainWindow"); cfg.writeEntry("ko_geometry", saveGeometry().toBase64()); cfg.writeEntry("ko_windowstate", saveState().toBase64()); // The doc and view might still exist (this is the case when closing the window) if (d->rootPart) d->rootPart->removeMainWindow(this); if (d->partToOpen) { d->partToOpen->removeMainWindow(this); delete d->partToOpen; } // safety first ;) setActivePart(0, 0); if (d->rootViews.indexOf(d->activeView) == -1) { delete d->activeView; d->activeView = 0; } while (!d->rootViews.isEmpty()) { delete d->rootViews.takeFirst(); } if(d->noCleanup) return; // We have to check if this was a root document. // This has to be checked from queryClose, too :) if (d->rootPart && d->rootPart->viewCount() == 0) { //debugMain <<"Destructor. No more views, deleting old doc" << d->rootDocument; delete d->rootDocument; // FIXME: Why delete here and not only in KoPart? } delete d; } void KoMainWindow::setRootDocument(KoDocument *doc, KoPart *part, bool deletePrevious) { if (d->rootDocument == doc) return; if (d->partToOpen && d->partToOpen->document() != doc) { d->partToOpen->removeMainWindow(this); - if (deletePrevious) delete d->partToOpen; + if (deletePrevious) { + delete d->partToOpen; + } } d->partToOpen = 0; //debugMain <<"KoMainWindow::setRootDocument this =" << this <<" doc =" << doc; QList oldRootViews = d->rootViews; d->rootViews.clear(); KoDocument *oldRootDoc = d->rootDocument; KoPart *oldRootPart = d->rootPart; if (oldRootDoc) { oldRootDoc->disconnect(this); oldRootPart->removeMainWindow(this); // Hide all dockwidgets and remember their old state d->dockWidgetVisibilityMap.clear(); foreach(QDockWidget* dockWidget, d->dockWidgetsMap) { d->dockWidgetVisibilityMap.insert(dockWidget, dockWidget->isVisible()); dockWidget->setVisible(false); } d->toggleDockerTitleBars->setVisible(false); d->dockWidgetMenu->setVisible(false); } d->rootDocument = doc; // XXX remove this after the splitting if (!part && doc) { d->rootPart = doc->documentPart(); } else { d->rootPart = part; } if (doc) { d->toggleDockerTitleBars->setVisible(true); d->dockWidgetMenu->setVisible(true); d->m_registeredPart = d->rootPart.data(); KoView *view = d->rootPart->createView(doc, this); setCentralWidget(view); d->rootViews.append(view); view->show(); view->setFocus(); // The addMainWindow has been done already if using openUrl if (!d->rootPart->mainWindows().contains(this)) { d->rootPart->addMainWindow(this); } } bool enable = d->rootDocument != 0 ? true : false; d->showDocumentInfo->setEnabled(enable); d->saveAction->setEnabled(enable); d->saveActionAs->setEnabled(enable); d->importFile->setEnabled(enable); d->exportFile->setEnabled(enable); #if 0 d->encryptDocument->setEnabled(enable); #ifndef NDEBUG d->uncompressToDir->setEnabled(enable); #endif #endif d->printAction->setEnabled(enable); d->printActionPreview->setEnabled(enable); d->sendFileAction->setEnabled(enable); d->exportPdf->setEnabled(enable); d->closeFile->setEnabled(enable); updateCaption(); setActivePart(d->rootPart, doc ? d->rootViews.first() : 0); emit restoringDone(); while(!oldRootViews.isEmpty()) { delete oldRootViews.takeFirst(); } if (oldRootPart && oldRootPart->viewCount() == 0) { //debugMain <<"No more views, deleting old doc" << oldRootDoc; oldRootDoc->clearUndoHistory(); if(deletePrevious) delete oldRootDoc; } if (doc && !d->dockWidgetVisibilityMap.isEmpty()) { foreach(QDockWidget* dockWidget, d->dockWidgetsMap) { dockWidget->setVisible(d->dockWidgetVisibilityMap.value(dockWidget)); } } if (!d->rootDocument) { statusBar()->setVisible(false); } else { #ifdef Q_OS_MAC statusBar()->setMaximumHeight(28); #endif connect(d->rootDocument, &KoDocument::titleModified, this, &KoMainWindow::slotDocumentTitleModified); } } void KoMainWindow::updateReloadFileAction(KoDocument *doc) { d->reloadFile->setEnabled(doc && !doc->url().isEmpty()); } void KoMainWindow::setReadWrite(bool readwrite) { d->saveAction->setEnabled(readwrite); d->importFile->setEnabled(readwrite); d->readOnly = !readwrite; updateCaption(); } void KoMainWindow::addRecentURL(const QString &projectName, const QUrl &url) { debugMain << "url=" << url.toDisplayString(); // Add entry to recent documents list // (call coming from KoDocument because it must work with cmd line, template dlg, file/open, etc.) if (!url.isEmpty()) { bool ok = true; if (url.isLocalFile()) { QString path = url.adjusted(QUrl::StripTrailingSlash).toLocalFile(); const QStringList tmpDirs = QStandardPaths::standardLocations(QStandardPaths::TempLocation); foreach (const QString &tmpDir, tmpDirs) { if (path.startsWith(tmpDir)) { ok = false; // it's in the tmp resource break; } } if (ok) { KRecentDocument::add(QUrl::fromLocalFile(path)); KRecentDirs::add(":OpenDialog", QFileInfo(path).dir().canonicalPath()); } } else { KRecentDocument::add(url.adjusted(QUrl::StripTrailingSlash)); } if (ok) { d->recent->addUrl(url, projectName); } saveRecentFiles(); #ifdef HAVE_KACTIVITIES if (!d->activityResource) { d->activityResource = new KActivities::ResourceInstance(winId(), this); } d->activityResource->setUri(url); #endif } } void KoMainWindow::saveRecentFiles() { // Save list of recent files KSharedConfigPtr config = componentData().config(); debugMain << this << " Saving recent files list into config. componentData()=" << componentData().componentName(); d->recent->saveEntries(config->group("RecentFiles")); config->sync(); // Tell all windows to reload their list, after saving // Doesn't work multi-process, but it's a start foreach(KMainWindow* window, KMainWindow::memberList()) static_cast(window)->reloadRecentFileList(); } void KoMainWindow::reloadRecentFileList() { KSharedConfigPtr config = componentData().config(); d->recent->loadEntries(config->group("RecentFiles")); } KoPart* KoMainWindow::createPart() const { KoDocumentEntry entry = KoDocumentEntry::queryByMimeType(d->nativeMimeType); QString errorMsg; KoPart *part = entry.createKoPart(&errorMsg); if (!part || !errorMsg.isEmpty()) { return 0; } return part; } void KoMainWindow::updateCaption() { debugMain; if (!d->rootDocument) { updateCaption(QString(), false); } else { QString caption( d->rootDocument->caption() ); if (d->readOnly) { caption += ' ' + i18n("(write protected)"); } updateCaption(caption, d->rootDocument->isModified()); if (!rootDocument()->url().fileName().isEmpty()) d->saveAction->setToolTip(i18n("Save as %1", d->rootDocument->url().fileName())); else d->saveAction->setToolTip(i18n("Save")); } } void KoMainWindow::updateCaption(const QString & caption, bool mod) { debugMain << caption << "," << mod; #ifdef PLAN_ALPHA setCaption(QString("ALPHA %1: %2").arg(PLAN_ALPHA).arg(caption), mod); return; #endif #ifdef PLAN_BETA setCaption(QString("BETA %1: %2").arg(PLAN_BETA).arg(caption), mod); return; #endif #ifdef PLAN_RC setCaption(QString("RELEASE CANDIDATE %1: %2").arg(PLAN_RC).arg(caption), mod); return; #endif setCaption(caption, mod); } KoDocument *KoMainWindow::rootDocument() const { return d->rootDocument; } KoView *KoMainWindow::rootView() const { if (d->rootViews.indexOf(d->activeView) != -1) return d->activeView; return d->rootViews.first(); } bool KoMainWindow::openDocument(const QUrl &url) { + if (url.fileName().endsWith(".plant")) { + KMessageBox::error(0, xi18nc("@info", "Cannot open a template file." + "If you want to modify the template, create a new project using this template" + " and save it using File->Create Project Template....")); + return false; + } + if (url.fileName().endsWith(".plant")) { + KMessageBox::error(0, xi18nc("@info", "Cannot open a template file:%1", url.fileName())); + return false; + } if (!KIO::NetAccess::exists(url, KIO::NetAccess::SourceSide, 0)) { KMessageBox::error(0, i18n("The file %1 does not exist.", url.url())); d->recent->removeUrl(url); //remove the file from the recent-opened-file-list saveRecentFiles(); return false; } return openDocumentInternal(url); } bool KoMainWindow::openDocument(KoPart *newPart, const QUrl &url) { + if (url.fileName().endsWith(".plant")) { + KMessageBox::error(0, xi18nc("@info", "Cannot open a template file." + "If you want to modify the template, create a new project using this template" + " and save it using File->Create Project Template....")); + return false; + } if (!newPart) { return openDocument(url); } // the part always has a document; the document doesn't know about the part. KoDocument *newdoc = newPart->document(); if (!KIO::NetAccess::exists(url, KIO::NetAccess::SourceSide, 0)) { newdoc->initEmpty(); //create an empty document setRootDocument(newdoc, newPart); newdoc->setUrl(url); QMimeType mime = QMimeDatabase().mimeTypeForUrl(url); QString mimetype = (!mime.isValid() || mime.isDefault()) ? newdoc->nativeFormatMimeType() : mime.name(); newdoc->setMimeTypeAfterLoading(mimetype); updateCaption(); return true; } return openDocumentInternal(url, newPart, newdoc); } bool KoMainWindow::openDocumentInternal(const QUrl &url, KoPart *newpart, KoDocument *newdoc) { debugMain << newpart << newdoc << url.url(); - - if (!newpart) + bool keepPart = true; + if (!newpart) { newpart = createPart(); - + keepPart = false; + } if (!newpart) return false; if (!newdoc) newdoc = newpart->document(); KFileItem file(url, newdoc->mimeType(), KFileItem::Unknown); if (!file.isWritable()) { newdoc->setReadWrite(false); } d->firstTime = true; connect(newdoc, &KoDocument::sigProgress, this, &KoMainWindow::slotProgress); connect(newdoc, &KoDocument::completed, this, &KoMainWindow::slotLoadCompleted); connect(newdoc, &KoDocument::canceled, this, &KoMainWindow::slotLoadCanceled); d->openingDocument = true; newpart->addMainWindow(this); // used by openUrl bool openRet = (!isImporting()) ? newdoc->openUrl(url) : newdoc->importDocument(url); if (!openRet) { - newpart->removeMainWindow(this); - delete newdoc; - delete newpart; - d->openingDocument = false; + if (!keepPart) { + newpart->removeMainWindow(this); + delete newdoc; + delete newpart; + newpart = nullptr; + d->openingDocument = false; + } return false; } updateReloadFileAction(newdoc); return true; } // Separate from openDocument to handle async loading (remote URLs) void KoMainWindow::slotLoadCompleted() { debugMain; KoDocument *newdoc = qobject_cast(sender()); KoPart *newpart = newdoc->documentPart(); if (d->rootDocument && d->rootDocument->isEmpty()) { // Replace current empty document setRootDocument(newdoc); } else if (d->rootDocument && !d->rootDocument->isEmpty()) { // Open in a new main window // (Note : could create the main window first and the doc next for this // particular case, that would give a better user feedback...) KoMainWindow *s = newpart->createMainWindow(); s->show(); newpart->removeMainWindow(this); s->setRootDocument(newdoc, newpart); } else { // We had no document, set the new one setRootDocument(newdoc); } slotProgress(-1); disconnect(newdoc, &KoDocument::sigProgress, this, &KoMainWindow::slotProgress); disconnect(newdoc, &KoDocument::completed, this, &KoMainWindow::slotLoadCompleted); disconnect(newdoc, &KoDocument::canceled, this, &KoMainWindow::slotLoadCanceled); d->openingDocument = false; emit loadCompleted(); } void KoMainWindow::slotLoadCanceled(const QString & errMsg) { debugMain; if (!errMsg.isEmpty()) // empty when canceled by user KMessageBox::error(this, errMsg); // ... can't delete the document, it's the one who emitted the signal... KoDocument* doc = qobject_cast(sender()); Q_ASSERT(doc); disconnect(doc, &KoDocument::sigProgress, this, &KoMainWindow::slotProgress); disconnect(doc, &KoDocument::completed, this, &KoMainWindow::slotLoadCompleted); disconnect(doc, &KoDocument::canceled, this, &KoMainWindow::slotLoadCanceled); d->openingDocument = false; emit loadCanceled(); } void KoMainWindow::slotSaveCanceled(const QString &errMsg) { debugMain; if (!errMsg.isEmpty()) // empty when canceled by user KMessageBox::error(this, errMsg); slotSaveCompleted(); } void KoMainWindow::slotSaveCompleted() { debugMain; KoDocument* doc = qobject_cast(sender()); Q_ASSERT(doc); disconnect(doc, &KoDocument::sigProgress, this, &KoMainWindow::slotProgress); disconnect(doc, &KoDocument::completed, this, &KoMainWindow::slotSaveCompleted); disconnect(doc, &KoDocument::canceled, this, &KoMainWindow::slotSaveCanceled); if (d->deferredClosingEvent) { KXmlGuiWindow::closeEvent(d->deferredClosingEvent); } } // returns true if we should save, false otherwise. bool KoMainWindow::exportConfirmation(const QByteArray &outputFormat) { KConfigGroup group = KSharedConfig::openConfig()->group(d->rootPart->componentData().componentName()); if (!group.readEntry("WantExportConfirmation", true)) { return true; } QMimeType mime = QMimeDatabase().mimeTypeForName(outputFormat); QString comment = mime.isValid() ? mime.comment() : i18n("%1 (unknown file type)", QString::fromLatin1(outputFormat)); // Warn the user int ret; if (!isExporting()) { // File --> Save ret = KMessageBox::warningContinueCancel ( this, i18n("Saving as a %1 may result in some loss of formatting." "

Do you still want to save in this format?", QString("%1").arg(comment)), // in case we want to remove the bold later i18n("Confirm Save"), KStandardGuiItem::save(), KStandardGuiItem::cancel(), "NonNativeSaveConfirmation" ); } else { // File --> Export ret = KMessageBox::warningContinueCancel ( this, i18n("Exporting as a %1 may result in some loss of formatting." "

Do you still want to export to this format?", QString("%1").arg(comment)), // in case we want to remove the bold later i18n("Confirm Export"), KGuiItem(i18n("Export")), KStandardGuiItem::cancel(), "NonNativeExportConfirmation" // different to the one used for Save (above) ); } return (ret == KMessageBox::Continue); } bool KoMainWindow::saveDocument(bool saveas, bool silent, int specialOutputFlag) { if (!d->rootDocument || !d->rootPart) { return true; } bool reset_url; if (d->rootDocument->url().isEmpty()) { emit saveDialogShown(); reset_url = true; saveas = true; } else { reset_url = false; } connect(d->rootDocument, &KoDocument::sigProgress, this, &KoMainWindow::slotProgress); connect(d->rootDocument, &KoDocument::completed, this, &KoMainWindow::slotSaveCompleted); connect(d->rootDocument, &KoDocument::canceled, this, &KoMainWindow::slotSaveCanceled); QUrl oldURL = d->rootDocument->url(); QString oldFile = d->rootDocument->localFilePath(); QByteArray _native_format = d->rootDocument->nativeFormatMimeType(); QByteArray oldOutputFormat = d->rootDocument->outputMimeType(); int oldSpecialOutputFlag = d->rootDocument->specialOutputFlag(); QUrl suggestedURL = d->rootDocument->url(); QStringList mimeFilter; QMimeType mime = QMimeDatabase().mimeTypeForName(_native_format); if (!mime.isValid()) // QT5TODO: find if there is no better way to get an object for the default type mime = QMimeDatabase().mimeTypeForName(QStringLiteral("application/octet-stream")); if (specialOutputFlag) mimeFilter = mime.globPatterns(); else mimeFilter = KoFilterManager::mimeFilter(_native_format, KoFilterManager::Export, d->rootDocument->extraNativeMimeTypes()); if (oldOutputFormat.isEmpty() && !d->rootDocument->url().isEmpty()) { // Not been saved yet, but there is a default url so open dialog with this url if (suggestedURL.path() == suggestedURL.fileName()) { // only a filename has been given, so add the default dir KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); QString path = group.readEntry("SaveDocument"); path += '/' + suggestedURL.fileName(); suggestedURL.setPath(path); suggestedURL.setScheme("file"); } saveas = true; debugMain << "newly created doc, default file name:" << d->rootDocument->url() << "save to:" << suggestedURL; } else if (!mimeFilter.contains(oldOutputFormat) && !isExporting()) { debugMain << "no export filter for" << oldOutputFormat; // --- don't setOutputMimeType in case the user cancels the Save As // dialog and then tries to just plain Save --- // suggest a different filename extension (yes, we fortunately don't all live in a world of magic :)) QString suggestedFilename = suggestedURL.fileName(); if (!suggestedFilename.isEmpty()) { // ".kra" looks strange for a name int c = suggestedFilename.lastIndexOf('.'); const QString ext = mime.preferredSuffix(); if (!ext.isEmpty()) { if (c < 0) suggestedFilename += ext; else suggestedFilename = suggestedFilename.left(c) + ext; } else { // current filename extension wrong anyway if (c > 0) { // this assumes that a . signifies an extension, not just a . suggestedFilename = suggestedFilename.left(c); } } suggestedURL = suggestedURL.adjusted(QUrl::RemoveFilename); suggestedURL.setPath(suggestedURL.path() + suggestedFilename); } // force the user to choose outputMimeType saveas = true; } bool ret = false; if (d->rootDocument->url().isEmpty() || saveas) { // if you're just File/Save As'ing to change filter options you // don't want to be reminded about overwriting files etc. bool justChangingFilterOptions = false; KoFileDialog dialog(this, KoFileDialog::SaveFile, "SaveDocument"); dialog.setCaption(i18n("untitled")); dialog.setDefaultDir((isExporting() && !d->lastExportUrl.isEmpty()) ? d->lastExportUrl.toLocalFile() : suggestedURL.toLocalFile()); dialog.setMimeTypeFilters(mimeFilter); QUrl newURL = QUrl::fromUserInput(dialog.filename()); if (newURL.isLocalFile()) { QString fn = newURL.toLocalFile(); if (QFileInfo(fn).completeSuffix().isEmpty()) { QMimeType mime = QMimeDatabase().mimeTypeForName(_native_format); fn.append(mime.preferredSuffix()); newURL = QUrl::fromLocalFile(fn); } } QByteArray outputFormat = _native_format; if (!specialOutputFlag) { QMimeType mime = QMimeDatabase().mimeTypeForUrl(newURL); outputFormat = mime.name().toLatin1(); } if (!isExporting()) justChangingFilterOptions = (newURL == d->rootDocument->url()) && (outputFormat == d->rootDocument->mimeType()) && (specialOutputFlag == oldSpecialOutputFlag); else justChangingFilterOptions = (newURL == d->lastExportUrl) && (outputFormat == d->lastExportedFormat) && (specialOutputFlag == d->lastExportSpecialOutputFlag); bool bOk = true; if (newURL.isEmpty()) { bOk = false; } // adjust URL before doing checks on whether the file exists. if (specialOutputFlag) { QString fileName = newURL.fileName(); if ( specialOutputFlag== KoDocument::SaveAsDirectoryStore) { // Do nothing } #if 0 else if (specialOutputFlag == KoDocument::SaveEncrypted) { int dot = fileName.lastIndexOf('.'); QString ext = mime.preferredSuffix(); if (!ext.isEmpty()) { if (dot < 0) fileName += ext; else fileName = fileName.left(dot) + ext; } else { // current filename extension wrong anyway if (dot > 0) fileName = fileName.left(dot); } newURL = newURL.adjusted(QUrl::RemoveFilename); newURL.setPath(newURL.path() + fileName); } #endif } if (bOk) { bool wantToSave = true; // don't change this line unless you know what you're doing :) if (!justChangingFilterOptions || d->rootDocument->confirmNonNativeSave(isExporting())) { if (!d->rootDocument->isNativeFormat(outputFormat)) wantToSave = exportConfirmation(outputFormat); } if (wantToSave) { // // Note: // If the user is stupid enough to Export to the current URL, // we do _not_ change this operation into a Save As. Reasons // follow: // // 1. A check like "isExporting() && oldURL == newURL" // doesn't _always_ work on case-insensitive filesystems // and inconsistent behaviour is bad. // 2. It is probably not a good idea to change d->rootDocument->mimeType // and friends because the next time the user File/Save's, // (not Save As) they won't be expecting that they are // using their File/Export settings // // As a bad side-effect of this, the modified flag will not // be updated and it is possible that what is currently on // their screen is not what is stored on disk (through loss // of formatting). But if you are dumb enough to change // mimetype but not the filename, then arguably, _you_ are // the "bug" :) // // - Clarence // d->rootDocument->setOutputMimeType(outputFormat, specialOutputFlag); if (!isExporting()) { // Save As ret = d->rootDocument->saveAs(newURL); if (ret) { debugMain << "Successful Save As!"; addRecentURL(d->rootDocument->projectName(), newURL); setReadWrite(true); } else { debugMain << "Failed Save As!"; d->rootDocument->setUrl(oldURL); d->rootDocument->setLocalFilePath(oldFile); d->rootDocument->setOutputMimeType(oldOutputFormat, oldSpecialOutputFlag); } } else { // Export ret = d->rootDocument->exportDocument(newURL); if (ret) { // a few file dialog convenience things d->lastExportUrl = newURL; d->lastExportedFormat = outputFormat; d->lastExportSpecialOutputFlag = specialOutputFlag; } // always restore output format d->rootDocument->setOutputMimeType(oldOutputFormat, oldSpecialOutputFlag); } if (silent) // don't let the document change the window caption d->rootDocument->setTitleModified(); } // if (wantToSave) { else ret = false; } // if (bOk) { else ret = false; } else { // saving bool needConfirm = d->rootDocument->confirmNonNativeSave(false) && !d->rootDocument->isNativeFormat(oldOutputFormat); if (!needConfirm || (needConfirm && exportConfirmation(oldOutputFormat /* not so old :) */)) ) { // be sure d->rootDocument has the correct outputMimeType! if (isExporting() || d->rootDocument->isModified() || d->rootDocument->alwaysAllowSaving()) { ret = d->rootDocument->save(); } if (!ret) { debugMain << "Failed Save!"; d->rootDocument->setUrl(oldURL); d->rootDocument->setLocalFilePath(oldFile); } } else ret = false; } if (!ret && reset_url) d->rootDocument->resetURL(); //clean the suggested filename as the save dialog was rejected updateReloadFileAction(d->rootDocument); updateCaption(); return ret; } void KoMainWindow::closeEvent(QCloseEvent *e) { // If we are in the process of opening a new document, rootDocument() may not have been set yet, // so we must prevent closing to avoid crash. if(d->openingDocument || (rootDocument() && rootDocument()->isLoading())) { e->setAccepted(false); return; } if (queryClose()) { d->deferredClosingEvent = e; if (!d->m_dockerStateBeforeHiding.isEmpty()) { restoreState(d->m_dockerStateBeforeHiding); } statusBar()->setVisible(true); menuBar()->setVisible(true); saveWindowSettings(); if(d->noCleanup) return; setRootDocument(0); if (!d->dockWidgetVisibilityMap.isEmpty()) { // re-enable dockers for persistency foreach(QDockWidget* dockWidget, d->dockWidgetsMap) dockWidget->setVisible(d->dockWidgetVisibilityMap.value(dockWidget)); } } else { e->setAccepted(false); } } void KoMainWindow::saveWindowSettings() { KSharedConfigPtr config = componentData().config(); if (d->windowSizeDirty ) { // Save window size into the config file of our componentData // TODO: check if this is ever read again, seems lost over the years debugMain; KConfigGroup mainWindowConfigGroup = config->group("MainWindow"); KWindowConfig::saveWindowSize(windowHandle(), mainWindowConfigGroup); config->sync(); d->windowSizeDirty = false; } if ( rootDocument() && d->rootPart) { // Save toolbar position into the config file of the app, under the doc's component name KConfigGroup group = KSharedConfig::openConfig()->group(d->rootPart->componentData().componentName()); //debugMain <<"KoMainWindow::closeEvent -> saveMainWindowSettings rootdoc's componentData=" << d->rootPart->componentData().componentName(); saveMainWindowSettings(group); // Save collapsable state of dock widgets for (QMap::const_iterator i = d->dockWidgetsMap.constBegin(); i != d->dockWidgetsMap.constEnd(); ++i) { if (i.value()->widget()) { KConfigGroup dockGroup = group.group(QString("DockWidget ") + i.key()); dockGroup.writeEntry("Collapsed", i.value()->widget()->isHidden()); dockGroup.writeEntry("Locked", i.value()->property("Locked").toBool()); dockGroup.writeEntry("DockArea", (int) dockWidgetArea(i.value())); } } } KSharedConfig::openConfig()->sync(); resetAutoSaveSettings(); // Don't let KMainWindow override the good stuff we wrote down } void KoMainWindow::resizeEvent(QResizeEvent * e) { d->windowSizeDirty = true; KXmlGuiWindow::resizeEvent(e); } bool KoMainWindow::queryClose() { if (rootDocument() == 0) return true; //debugMain <<"KoMainWindow::queryClose() viewcount=" << rootDocument()->viewCount() // << " mainWindowCount=" << rootDocument()->mainWindowCount() << endl; if (!d->forQuit && d->rootPart && d->rootPart->mainwindowCount() > 1) // there are more open, and we are closing just one, so no problem for closing return true; // main doc + internally stored child documents if (d->rootDocument->isModified()) { QString name; if (rootDocument()->documentInfo()) { name = rootDocument()->documentInfo()->aboutInfo("title"); } if (name.isEmpty()) name = rootDocument()->url().fileName(); if (name.isEmpty()) name = i18n("Untitled"); int res = KMessageBox::warningYesNoCancel(this, i18n("

The document '%1' has been modified.

Do you want to save it?

", name), QString(), KStandardGuiItem::save(), KStandardGuiItem::discard()); switch (res) { case KMessageBox::Yes : { bool isNative = (d->rootDocument->outputMimeType() == d->rootDocument->nativeFormatMimeType()); if (!saveDocument(!isNative)) return false; break; } case KMessageBox::No : rootDocument()->removeAutoSaveFiles(); rootDocument()->setModified(false); // Now when queryClose() is called by closeEvent it won't do anything. break; default : // case KMessageBox::Cancel : return false; } } return true; } // Helper method for slotFileNew and slotFileClose void KoMainWindow::chooseNewDocument(InitDocFlags initDocFlags) { KoDocument* doc = rootDocument(); KoPart *newpart = createPart(); KoDocument *newdoc = newpart->document(); if (!newdoc) return; disconnect(newdoc, &KoDocument::sigProgress, this, &KoMainWindow::slotProgress); if ((!doc && initDocFlags == InitDocFileNew) || (doc && !doc->isEmpty())) { KoMainWindow *s = newpart->createMainWindow(); s->show(); newpart->addMainWindow(s); newpart->showStartUpWidget(s); return; } if (doc) { setRootDocument(0); if(d->rootDocument) d->rootDocument->clearUndoHistory(); delete d->rootDocument; d->rootDocument = 0; } newpart->addMainWindow(this); newpart->showStartUpWidget(this); } void KoMainWindow::slotFileNew() { chooseNewDocument(InitDocFileNew); } void KoMainWindow::slotFileOpen() { QUrl url; if (!isImporting()) { KoFileDialog dialog(this, KoFileDialog::OpenFile, "OpenDocument"); dialog.setCaption(i18n("Open Document")); dialog.setDefaultDir(qApp->applicationName().contains("karbon") ? QStandardPaths::writableLocation(QStandardPaths::PicturesLocation) : QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)); dialog.setMimeTypeFilters(koApp->mimeFilter(KoFilterManager::Import)); dialog.setHideNameFilterDetailsOption(); url = QUrl::fromUserInput(dialog.filename()); } else { KoFileDialog dialog(this, KoFileDialog::ImportFile, "OpenDocument"); dialog.setCaption(i18n("Import Document")); dialog.setDefaultDir(qApp->applicationName().contains("karbon") ? QStandardPaths::writableLocation(QStandardPaths::PicturesLocation) : QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)); dialog.setMimeTypeFilters(koApp->mimeFilter(KoFilterManager::Import)); dialog.setHideNameFilterDetailsOption(); url = QUrl::fromUserInput(dialog.filename()); } if (url.isEmpty()) return; (void) openDocument(url); } void KoMainWindow::slotFileOpenRecent(const QUrl & url, KoPart *part) { // Create a copy, because the original QUrl in the map of recent files in // KRecentFilesAction may get deleted. (void) openDocument(part, QUrl(url)); } void KoMainWindow::slotFileSave() { if (saveDocument()) emit documentSaved(); } void KoMainWindow::slotFileSaveAs() { if (saveDocument(true)) emit documentSaved(); } void KoMainWindow::slotEncryptDocument() { if (saveDocument(false, false, KoDocument::SaveEncrypted)) emit documentSaved(); } void KoMainWindow::slotUncompressToDir() { if (saveDocument(true, false, KoDocument::SaveAsDirectoryStore)) emit documentSaved(); } void KoMainWindow::slotDocumentInfo() { if (!rootDocument()) return; KoDocumentInfo *docInfo = rootDocument()->documentInfo(); if (!docInfo) return; KoDocumentInfoDlg *dlg = d->rootDocument->createDocumentInfoDialog(this, docInfo); if (dlg->exec()) { if (dlg->isDocumentSaved()) { rootDocument()->setModified(false); } else { rootDocument()->setModified(true); } rootDocument()->setTitleModified(); } delete dlg; } void KoMainWindow::slotFileClose() { if (queryClose()) { saveWindowSettings(); setRootDocument(0); // don't delete this main window when deleting the document if(d->rootDocument) d->rootDocument->clearUndoHistory(); delete d->rootDocument; d->rootDocument = 0; chooseNewDocument(InitDocFileClose); } } void KoMainWindow::slotFileQuit() { close(); } void KoMainWindow::slotFilePrint() { if (!rootView()) return; KoPrintJob *printJob = rootView()->createPrintJob(); if (printJob == 0) return; d->applyDefaultSettings(printJob->printer()); QPrintDialog *printDialog = rootView()->createPrintDialog( printJob, this ); if (printDialog && printDialog->exec() == QDialog::Accepted) printJob->startPrinting(KoPrintJob::DeleteWhenDone); else delete printJob; delete printDialog; } void KoMainWindow::slotFilePrintPreview() { if (!rootView()) return; KoPrintJob *printJob = rootView()->createPrintJob(); if (printJob == 0) return; /* Sets the startPrinting() slot to be blocking. The Qt print-preview dialog requires the printing to be completely blocking and only return when the full document has been printed. By default the KoPrintingDialog is non-blocking and multithreading, setting blocking to true will allow it to be used in the preview dialog */ printJob->setProperty("blocking", true); QPrintPreviewDialog *preview = new QPrintPreviewDialog(&printJob->printer(), this); printJob->setParent(preview); // will take care of deleting the job d->printPreviewJob = printJob; connect(preview, SIGNAL(paintRequested(QPrinter*)), this, SLOT(slotPrintPreviewPaintRequest(QPrinter*))); // clazy:exclude=old-style-connect preview->exec(); delete preview; d->printPreviewJob = nullptr; } void KoMainWindow::slotPrintPreviewPaintRequest(QPrinter *printer) { KoPageLayout pl = rootView()->pageLayout(); pl.updatePageLayout(printer); rootView()->setPageLayout(pl); d->printPreviewJob->startPrinting(); } KoPrintJob* KoMainWindow::exportToPdf() { return exportToPdf(QString()); } KoPrintJob* KoMainWindow::exportToPdf(const QString &_pdfFileName) { if (!rootView()) return 0; KoPageLayout pageLayout; pageLayout = rootView()->pageLayout(); QString pdfFileName = _pdfFileName; if (pdfFileName.isEmpty()) { KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); QString defaultDir = group.readEntry("SavePdfDialog"); if (defaultDir.isEmpty()) defaultDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); QUrl startUrl = QUrl::fromLocalFile(defaultDir); KoDocument* pDoc = rootDocument(); /** if document has a file name, take file name and replace extension with .pdf */ if (pDoc && pDoc->url().isValid()) { startUrl = pDoc->url(); QString fileName = startUrl.fileName(); fileName = fileName.replace( QRegExp( "\\.\\w{2,5}$", Qt::CaseInsensitive ), ".pdf" ); startUrl = startUrl.adjusted(QUrl::RemoveFilename); startUrl.setPath(startUrl.path() + fileName ); } QPointer layoutDlg(new KoPageLayoutDialog(this, pageLayout)); layoutDlg->setWindowModality(Qt::WindowModal); if (layoutDlg->exec() != QDialog::Accepted || !layoutDlg) { delete layoutDlg; return 0; } pageLayout = layoutDlg->pageLayout(); delete layoutDlg; KoFileDialog dialog(this, KoFileDialog::SaveFile, "SaveDocument"); dialog.setCaption(i18n("Export as PDF")); dialog.setDefaultDir(startUrl.toLocalFile()); dialog.setMimeTypeFilters(QStringList() << "application/pdf"); QUrl url = QUrl::fromUserInput(dialog.filename()); pdfFileName = url.toLocalFile(); if (pdfFileName.isEmpty()) return 0; } KoPrintJob *printJob = rootView()->createPdfPrintJob(); if (printJob == 0) return 0; if (isHidden()) { printJob->setProperty("noprogressdialog", true); } d->applyDefaultSettings(printJob->printer()); // TODO for remote files we have to first save locally and then upload. printJob->printer().setOutputFileName(pdfFileName); printJob->printer().setColorMode(QPrinter::Color); if (pageLayout.format == KoPageFormat::CustomSize) { printJob->printer().setPaperSize(QSizeF(pageLayout.width, pageLayout.height), QPrinter::Millimeter); } else { printJob->printer().setPaperSize(KoPageFormat::printerPageSize(pageLayout.format)); } switch (pageLayout.orientation) { case KoPageFormat::Portrait: printJob->printer().setOrientation(QPrinter::Portrait); break; case KoPageFormat::Landscape: printJob->printer().setOrientation(QPrinter::Landscape); break; } printJob->printer().setPageMargins(pageLayout.leftMargin, pageLayout.topMargin, pageLayout.rightMargin, pageLayout.bottomMargin, QPrinter::Millimeter); //before printing check if the printer can handle printing if (!printJob->canPrint()) { KMessageBox::error(this, i18n("Cannot export to the specified file")); } printJob->startPrinting(KoPrintJob::DeleteWhenDone); rootView()->setPageLayout(pageLayout); return printJob; } void KoMainWindow::slotConfigureKeys() { QAction* undoAction=0; QAction* redoAction=0; QString oldUndoText; QString oldRedoText; if(currentView()) { //The undo/redo action text is "undo" + command, replace by simple text while inside editor undoAction = currentView()->actionCollection()->action("edit_undo"); redoAction = currentView()->actionCollection()->action("edit_redo"); oldUndoText = undoAction->text(); oldRedoText = redoAction->text(); undoAction->setText(i18n("Undo")); redoAction->setText(i18n("Redo")); } guiFactory()->configureShortcuts(); if(currentView()) { undoAction->setText(oldUndoText); redoAction->setText(oldRedoText); } emit keyBindingsChanged(); } void KoMainWindow::slotConfigureToolbars() { if (rootDocument()) { KConfigGroup componentConfigGroup = KSharedConfig::openConfig()->group(d->rootPart->componentData().componentName()); saveMainWindowSettings(componentConfigGroup); } KEditToolBar edit(factory(), this); connect(&edit, &KEditToolBar::newToolBarConfig, this, &KoMainWindow::slotNewToolbarConfig); (void) edit.exec(); } void KoMainWindow::slotNewToolbarConfig() { if (rootDocument()) { KConfigGroup componentConfigGroup = KSharedConfig::openConfig()->group(d->rootPart->componentData().componentName()); applyMainWindowSettings(componentConfigGroup); } KXMLGUIFactory *factory = guiFactory(); Q_UNUSED(factory); // Check if there's an active view if (!d->activeView) return; plugActionList("toolbarlist", d->toolbarList); } void KoMainWindow::slotToolbarToggled(bool toggle) { //debugMain <<"KoMainWindow::slotToolbarToggled" << sender()->name() <<" toggle=" << true; // The action (sender) and the toolbar have the same name KToolBar * bar = toolBar(sender()->objectName()); if (bar) { if (toggle) bar->show(); else bar->hide(); if (rootDocument()) { KConfigGroup componentConfigGroup = KSharedConfig::openConfig()->group(d->rootPart->componentData().componentName()); saveMainWindowSettings(componentConfigGroup); } } else warnMain << "slotToolbarToggled : Toolbar " << sender()->objectName() << " not found!"; } bool KoMainWindow::toolbarIsVisible(const char *tbName) { QWidget *tb = toolBar(tbName); return !tb->isHidden(); } void KoMainWindow::showToolbar(const char * tbName, bool shown) { QWidget * tb = toolBar(tbName); if (!tb) { warnMain << "KoMainWindow: toolbar " << tbName << " not found."; return; } if (shown) tb->show(); else tb->hide(); // Update the action appropriately foreach(QAction* action, d->toolbarList) { if (action->objectName() != tbName) { //debugMain <<"KoMainWindow::showToolbar setChecked" << shown; static_cast(action)->setChecked(shown); break; } } } void KoMainWindow::viewFullscreen(bool fullScreen) { if (fullScreen) { window()->setWindowState(window()->windowState() | Qt::WindowFullScreen); // set } else { window()->setWindowState(window()->windowState() & ~Qt::WindowFullScreen); // reset } } void KoMainWindow::slotProgress(int value) { QMutexLocker locker(&d->progressMutex); debugMain << value; if (value <= -1 || value >= 100) { if (d->progress) { statusBar()->removeWidget(d->progress); delete d->progress; d->progress = 0; } d->firstTime = true; return; } if (d->firstTime || !d->progress) { // The statusbar might not even be created yet. // So check for that first, and create it if necessary QStatusBar *bar = findChild(); if (!bar) { statusBar()->show(); QApplication::sendPostedEvents(this, QEvent::ChildAdded); } if (d->progress) { statusBar()->removeWidget(d->progress); delete d->progress; d->progress = 0; } d->progress = new QProgressBar(statusBar()); d->progress->setMaximumHeight(statusBar()->fontMetrics().height()); d->progress->setRange(0, 100); statusBar()->addPermanentWidget(d->progress); d->progress->show(); d->firstTime = false; } if (!d->progress.isNull()) { d->progress->setValue(value); } locker.unlock(); qApp->processEvents(); } void KoMainWindow::setMaxRecentItems(uint _number) { d->recent->setMaxItems(_number); } void KoMainWindow::slotEmailFile() { if (!rootDocument()) return; // Subject = Document file name // Attachment = The current file // Message Body = The current document in HTML export? <-- This may be an option. QString theSubject; QStringList urls; QString fileURL; if (rootDocument()->url().isEmpty() || rootDocument()->isModified()) { //Save the file as a temporary file bool const tmp_modified = rootDocument()->isModified(); QUrl const tmp_url = rootDocument()->url(); QByteArray const tmp_mimetype = rootDocument()->outputMimeType(); // a little open, close, delete dance to make sure we have a nice filename // to use, but won't block windows from creating a new file with this name. QTemporaryFile *tmpfile = new QTemporaryFile(); tmpfile->open(); QString fileName = tmpfile->fileName(); tmpfile->close(); delete tmpfile; QUrl u = QUrl::fromLocalFile(fileName); rootDocument()->setUrl(u); rootDocument()->setModified(true); rootDocument()->setOutputMimeType(rootDocument()->nativeFormatMimeType()); saveDocument(false, true); fileURL = fileName; theSubject = i18n("Document"); urls.append(fileURL); rootDocument()->setUrl(tmp_url); rootDocument()->setModified(tmp_modified); rootDocument()->setOutputMimeType(tmp_mimetype); } else { fileURL = rootDocument()->url().url(); theSubject = i18n("Document - %1", rootDocument()->url().fileName()); urls.append(fileURL); } debugMain << "(" << fileURL << ")"; if (!fileURL.isEmpty()) { KToolInvocation::invokeMailer(QString(), QString(), QString(), theSubject, QString(), //body QString(), urls); // attachments } } void KoMainWindow::slotReloadFile() { KoDocument* pDoc = rootDocument(); if (!pDoc || pDoc->url().isEmpty() || !pDoc->isModified()) return; bool bOk = KMessageBox::questionYesNo(this, i18n("You will lose all changes made since your last save\n" "Do you want to continue?"), i18n("Warning")) == KMessageBox::Yes; if (!bOk) return; QUrl url = pDoc->url(); if (!pDoc->isEmpty()) { saveWindowSettings(); setRootDocument(0); // don't delete this main window when deleting the document if(d->rootDocument) d->rootDocument->clearUndoHistory(); delete d->rootDocument; d->rootDocument = 0; } openDocument(url); return; } void KoMainWindow::slotImportFile() { debugMain; d->isImporting = true; slotFileOpen(); d->isImporting = false; } void KoMainWindow::slotExportFile() { debugMain; d->isExporting = true; slotFileSaveAs(); d->isExporting = false; } bool KoMainWindow::isImporting() const { return d->isImporting; } bool KoMainWindow::isExporting() const { return d->isExporting; } void KoMainWindow::setPartToOpen(KoPart *part) { d->partToOpen = part; } KoComponentData KoMainWindow::componentData() const { return d->componentData; } QDockWidget* KoMainWindow::createDockWidget(KoDockFactoryBase* factory) { QDockWidget* dockWidget = 0; if (!d->dockWidgetsMap.contains(factory->id())) { dockWidget = factory->createDockWidget(); // It is quite possible that a dock factory cannot create the dock; don't // do anything in that case. if (!dockWidget) return 0; d->dockWidgets.push_back(dockWidget); KoDockWidgetTitleBar *titleBar = 0; // Check if the dock widget is supposed to be collapsable if (!dockWidget->titleBarWidget()) { titleBar = new KoDockWidgetTitleBar(dockWidget); dockWidget->setTitleBarWidget(titleBar); titleBar->setCollapsable(factory->isCollapsable()); } dockWidget->setObjectName(factory->id()); dockWidget->setParent(this); if (dockWidget->widget() && dockWidget->widget()->layout()) dockWidget->widget()->layout()->setContentsMargins(1, 1, 1, 1); Qt::DockWidgetArea side = Qt::RightDockWidgetArea; bool visible = true; switch (factory->defaultDockPosition()) { case KoDockFactoryBase::DockTornOff: dockWidget->setFloating(true); // position nicely? break; case KoDockFactoryBase::DockTop: side = Qt::TopDockWidgetArea; break; case KoDockFactoryBase::DockLeft: side = Qt::LeftDockWidgetArea; break; case KoDockFactoryBase::DockBottom: side = Qt::BottomDockWidgetArea; break; case KoDockFactoryBase::DockRight: side = Qt::RightDockWidgetArea; break; case KoDockFactoryBase::DockMinimized: default: side = Qt::RightDockWidgetArea; visible = false; } if (rootDocument()) { KConfigGroup group = KSharedConfig::openConfig()->group(d->rootPart->componentData().componentName()).group("DockWidget " + factory->id()); side = static_cast(group.readEntry("DockArea", static_cast(side))); if (side == Qt::NoDockWidgetArea) side = Qt::RightDockWidgetArea; } addDockWidget(side, dockWidget); if (dockWidget->features() & QDockWidget::DockWidgetClosable) { d->dockWidgetMenu->addAction(dockWidget->toggleViewAction()); if (!visible) dockWidget->hide(); } bool collapsed = factory->defaultCollapsed(); bool locked = false; if (rootDocument()) { KConfigGroup group = KSharedConfig::openConfig()->group(d->rootPart->componentData().componentName()).group("DockWidget " + factory->id()); collapsed = group.readEntry("Collapsed", collapsed); locked = group.readEntry("Locked", locked); } if (titleBar && collapsed) titleBar->setCollapsed(true); if (titleBar && locked) titleBar->setLocked(true); if (titleBar) { KConfigGroup configGroupInterface = KSharedConfig::openConfig()->group("Interface"); titleBar->setVisible(configGroupInterface.readEntry("ShowDockerTitleBars", true)); } d->dockWidgetsMap.insert(factory->id(), dockWidget); } else { dockWidget = d->dockWidgetsMap[ factory->id()]; } #ifdef Q_OS_MAC dockWidget->setAttribute(Qt::WA_MacSmallSize, true); #endif dockWidget->setFont(KoDockRegistry::dockFont()); connect(dockWidget, &QDockWidget::dockLocationChanged, this, &KoMainWindow::forceDockTabFonts); return dockWidget; } void KoMainWindow::forceDockTabFonts() { QObjectList chis = children(); for (int i = 0; i < chis.size(); ++i) { if (chis.at(i)->inherits("QTabBar")) { ((QTabBar *)chis.at(i))->setFont(KoDockRegistry::dockFont()); } } } QList KoMainWindow::dockWidgets() const { return d->dockWidgetsMap.values(); } /*QList KoMainWindow::canvasObservers() const { QList observers; foreach(QDockWidget *docker, dockWidgets()) { KoCanvasObserverBase *observer = dynamic_cast(docker); if (observer) { observers << observer; } } return observers; }*/ void KoMainWindow::toggleDockersVisibility(bool visible) { if (!visible) { d->m_dockerStateBeforeHiding = saveState(); foreach(QObject* widget, children()) { if (widget->inherits("QDockWidget")) { QDockWidget* dw = static_cast(widget); if (dw->isVisible()) { dw->hide(); } } } } else { restoreState(d->m_dockerStateBeforeHiding); } } KRecentFilesAction *KoMainWindow::recentAction() const { return d->recent; } KoView* KoMainWindow::currentView() const { // XXX if (d->activeView) { return d->activeView; } else if (!d->rootViews.isEmpty()) { return d->rootViews.first(); } return 0; } void KoMainWindow::newView() { Q_ASSERT((d != 0 && d->activeView && d->activePart && d->activeView->koDocument())); KoMainWindow *mainWindow = d->activePart->createMainWindow(); mainWindow->setRootDocument(d->activeView->koDocument(), d->activePart); mainWindow->show(); } void KoMainWindow::createMainwindowGUI() { if ( isHelpMenuEnabled() && !d->m_helpMenu ) { d->m_helpMenu = new KHelpMenu( this, componentData().aboutData(), true ); KActionCollection *actions = actionCollection(); QAction *helpContentsAction = d->m_helpMenu->action(KHelpMenu::menuHelpContents); QAction *whatsThisAction = d->m_helpMenu->action(KHelpMenu::menuWhatsThis); QAction *reportBugAction = d->m_helpMenu->action(KHelpMenu::menuReportBug); QAction *switchLanguageAction = d->m_helpMenu->action(KHelpMenu::menuSwitchLanguage); QAction *aboutAppAction = d->m_helpMenu->action(KHelpMenu::menuAboutApp); QAction *aboutKdeAction = d->m_helpMenu->action(KHelpMenu::menuAboutKDE); if (helpContentsAction) { actions->addAction(helpContentsAction->objectName(), helpContentsAction); } if (whatsThisAction) { actions->addAction(whatsThisAction->objectName(), whatsThisAction); } if (reportBugAction) { actions->addAction(reportBugAction->objectName(), reportBugAction); } if (switchLanguageAction) { actions->addAction(switchLanguageAction->objectName(), switchLanguageAction); } if (aboutAppAction) { actions->addAction(aboutAppAction->objectName(), aboutAppAction); } if (aboutKdeAction) { actions->addAction(aboutKdeAction->objectName(), aboutKdeAction); } } QString f = xmlFile(); setXMLFile( QStandardPaths::locate(QStandardPaths::ConfigLocation, QStringLiteral("ui/ui_standards.rc")) ); if ( !f.isEmpty() ) setXMLFile( f, true ); else { QString auto_file( componentData().componentName() + "ui.rc" ); setXMLFile( auto_file, true ); } guiFactory()->addClient( this ); } // PartManager void KoMainWindow::removePart( KoPart *part ) { if (d->m_registeredPart.data() != part) { return; } d->m_registeredPart = 0; if ( part == d->m_activePart ) { setActivePart(0, 0); } } void KoMainWindow::setActivePart(KoPart *part, QWidget *widget ) { if (part && d->m_registeredPart.data() != part) { warnMain << "trying to activate a non-registered part!" << part->objectName(); return; // don't allow someone call setActivePart with a part we don't know about } // don't activate twice if ( d->m_activePart && part && d->m_activePart == part && (!widget || d->m_activeWidget == widget) ) return; KoPart *oldActivePart = d->m_activePart; QWidget *oldActiveWidget = d->m_activeWidget; d->m_activePart = part; d->m_activeWidget = widget; if (oldActivePart) { KoPart *savedActivePart = part; QWidget *savedActiveWidget = widget; if ( oldActiveWidget ) { disconnect( oldActiveWidget, &QObject::destroyed, this, &KoMainWindow::slotWidgetDestroyed ); } d->m_activePart = savedActivePart; d->m_activeWidget = savedActiveWidget; } if (d->m_activePart && d->m_activeWidget ) { connect( d->m_activeWidget, &QObject::destroyed, this, &KoMainWindow::slotWidgetDestroyed ); } // Set the new active instance in KGlobal // KGlobal::setActiveComponent(d->m_activePart ? d->m_activePart->componentData() : KGlobal::mainComponent()); // old slot called from part manager KoPart *newPart = static_cast(d->m_activePart.data()); if (d->activePart && d->activePart == newPart) { //debugMain <<"no need to change the GUI"; return; } KXMLGUIFactory *factory = guiFactory(); if (d->activeView) { factory->removeClient(d->activeView); unplugActionList("toolbarlist"); qDeleteAll(d->toolbarList); d->toolbarList.clear(); } if (!d->mainWindowGuiIsBuilt) { createMainwindowGUI(); } if (newPart && d->m_activeWidget && d->m_activeWidget->inherits("KoView")) { d->activeView = qobject_cast(d->m_activeWidget); d->activeView->actionCollection()->addAction("view_newview", actionCollection()->action("view_newview")); d->activePart = newPart; //debugMain <<"new active part is" << d->activePart; factory->addClient(d->activeView); // Position and show toolbars according to user's preference setAutoSaveSettings(newPart->componentData().componentName(), false); KConfigGroup configGroupInterface = KSharedConfig::openConfig()->group("Interface"); const bool showDockerTitleBar = configGroupInterface.readEntry("ShowDockerTitleBars", true); foreach (QDockWidget *wdg, d->dockWidgets) { if ((wdg->features() & QDockWidget::DockWidgetClosable) == 0) { if (wdg->titleBarWidget()) { wdg->titleBarWidget()->setVisible(showDockerTitleBar); } wdg->setVisible(true); } } // Create and plug toolbar list for Settings menu foreach(QWidget* it, factory->containers("ToolBar")) { KToolBar * toolBar = ::qobject_cast(it); if (toolBar) { KToggleAction * act = new KToggleAction(i18n("Show %1 Toolbar", toolBar->windowTitle()), this); actionCollection()->addAction(toolBar->objectName().toUtf8(), act); act->setCheckedState(KGuiItem(i18n("Hide %1 Toolbar", toolBar->windowTitle()))); connect(act, &QAction::toggled, this, &KoMainWindow::slotToolbarToggled); act->setChecked(!toolBar->isHidden()); d->toolbarList.append(act); } else warnMain << "Toolbar list contains a " << it->metaObject()->className() << " which is not a toolbar!"; } plugActionList("toolbarlist", d->toolbarList); } else { d->activeView = 0; d->activePart = 0; } if (d->activeView) { d->activeView->guiActivateEvent(true); } } void KoMainWindow::slotWidgetDestroyed() { debugMain; if ( static_cast( sender() ) == d->m_activeWidget ) setActivePart(0, 0); //do not remove the part because if the part's widget dies, then the //part will delete itself anyway, invoking removePart() in its destructor } void KoMainWindow::slotDocumentTitleModified(const QString &caption, bool mod) { updateCaption(caption, mod); updateReloadFileAction(d->rootDocument); } void KoMainWindow::showDockerTitleBars(bool show) { foreach (QDockWidget *dock, dockWidgets()) { if (dock->titleBarWidget()) { dock->titleBarWidget()->setVisible(show); } } KConfigGroup configGroupInterface = KSharedConfig::openConfig()->group("Interface"); configGroupInterface.writeEntry("ShowDockerTitleBars", show); } void KoMainWindow::slotConfigure() { emit configure(this); } diff --git a/src/libs/main/KoMainWindow.h b/src/libs/main/KoMainWindow.h index 5398bb74..8c8aa432 100644 --- a/src/libs/main/KoMainWindow.h +++ b/src/libs/main/KoMainWindow.h @@ -1,463 +1,463 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright (C) 2000-2004 David Faure 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 KOMAINWINDOW_H #define KOMAINWINDOW_H #include "komain_export.h" #include //#include //#include class KoMainWindowPrivate; class KoDocument; class KoPart; class KoView; class KoPrintJob; class KoDockFactoryBase; class KRecentFilesAction; class KoComponentData; class QDockWidget; class QPrinter; struct KoPageLayout; // Calligra class but not in main module class QUrl; /** * @brief Main window for a Calligra application * * This class is used to represent a main window * of a Calligra component. Each main window contains * a menubar and some toolbars. * * @note This class does NOT need to be subclassed in your application. */ class KOMAIN_EXPORT KoMainWindow : public KXmlGuiWindow//, public KoCanvasSupervisor { Q_OBJECT public: /** * Constructor. * * Initializes a Calligra main window (with its basic GUI etc.). */ explicit KoMainWindow(const QByteArray &nativeMimeType, const KoComponentData &instance); /** * Destructor. */ ~KoMainWindow() override; // If noCleanup is set, KoMainWindow will not delete the root document // or part manager on destruction. void setNoCleanup(bool noCleanup); /** * Called when a document is assigned to this mainwindow. * This creates a view for this document, makes it the active part, etc. */ void setRootDocument(KoDocument *doc, KoPart *part = 0, bool deletePrevious = true); /** * This is used to handle the document used at start up before it actually * added as root document. */ void setPartToOpen(KoPart *part); /** * Update caption from document info - call when document info * (title in the about page) changes. */ void updateCaption(); KoComponentData componentData() const; void setComponentData(const KoComponentData &componentData); /** * Retrieves the document that is displayed in the mainwindow. */ KoDocument *rootDocument() const; KoView *rootView() const; /** * The application should call this to show or hide a toolbar. * It also takes care of the corresponding action in the settings menu. */ void showToolbar(const char * tbName, bool shown); /** * @return TRUE if the toolbar @p tbName is visible */ bool toolbarIsVisible(const char *tbName); /** * Sets the maximum number of recent documents entries. */ void setMaxRecentItems(uint _number); /** * The document opened a URL -> store into recent documents list. */ void addRecentURL(const QString &projectName, const QUrl &url); /** * Load the desired document and show it. * @param url the URL to open * * @return TRUE on success. */ bool openDocument(const QUrl &url); /** * Load the URL into this document (and make it root doc after loading) * * Special method for KoApplication::start, don't use. */ bool openDocument(KoPart *newPart, const QUrl &url); /** * Reloads the recent documents list. */ void reloadRecentFileList(); /** * Updates the window caption based on the document info and path. */ void updateCaption(const QString & caption, bool mod); void updateReloadFileAction(KoDocument *doc); void setReadWrite(bool readwrite); /** * Returns the dockwidget specified by the @p factory. If the dock widget doesn't exist yet it's created. * Add a "view_palette_action_menu" action to your view menu if you want to use closable dock widgets. * @param factory the factory used to create the dock widget if needed * @return the dock widget specified by @p factory (may be 0) */ QDockWidget* createDockWidget(KoDockFactoryBase* factory); /// Return the list of dock widgets belonging to this main window. QList dockWidgets() const; // QList canvasObservers() const; + /** + * Create a new empty document. + */ + KoPart* createPart() const; + Q_SIGNALS: /** * This signal is emitted if the document has been saved successfully. */ void documentSaved(); /// This signals is emitted before the save dialog is shown void saveDialogShown(); /// This signal is emitted right after the docker states have been successfully restored from config void restoringDone(); /// This signal is emitted when this windows has finished loading of a /// document. The document may be opened in another window in the end. /// In this case, the signal means there is no link between the window /// and the document anymore. void loadCompleted(); /// This signal is emitted when this windows has canceled loading of a document. void loadCanceled(); /// This signal is emitted when the color theme changes void themeChanged(); /// This signal is emitted when the shortcut key configuration has changed void keyBindingsChanged(); void configure(KoMainWindow *mw); public Q_SLOTS: /** * Slot for eMailing the document using KMail * * This is a very simple extension that will allow any document * that is currently being edited to be emailed using KMail. */ void slotEmailFile(); /** * Slot for opening a new document. * * If the current document is empty, the new document replaces it. * If not, a new mainwindow will be opened for showing the document. */ void slotFileNew(); /** * Slot for opening a saved file. * * If the current document is empty, the opened document replaces it. * If not a new mainwindow will be opened for showing the opened file. */ void slotFileOpen(); /** * Slot for opening a file among the recently opened files. * * If the current document is empty, the opened document replaces it. * If not a new mainwindow will be opened for showing the opened file. */ void slotFileOpenRecent(const QUrl &, KoPart *part=nullptr); /** * Saves the current document with the current name. */ void slotFileSave(); /** * Saves the current document with a new name. */ void slotFileSaveAs(); /** * Prints the actual document. */ void slotFilePrint(); void slotFilePrintPreview(); KoPrintJob* exportToPdf(const QString &pdfFileName); KoPrintJob* exportToPdf(); /** * Show a dialog with author and document information. */ void slotDocumentInfo(); /** * Closes the document. */ void slotFileClose(); /** * Closes the mainwindow. */ void slotFileQuit(); /** * Configure key bindings. */ void slotConfigureKeys(); /** * Configure toolbars. */ void slotConfigureToolbars(); /** * Post toolbar config. * (Plug action lists back in, etc.) */ void slotNewToolbarConfig(); /** * Shows or hides a toolbar */ void slotToolbarToggled(bool toggle); /** * Toggle full screen on/off. */ void viewFullscreen(bool fullScreen); /** * Toggle docker titlebars on/off. */ void showDockerTitleBars(bool show); /** * Reload file */ void slotReloadFile(); /** * File --> Import * * This will call slotFileOpen(). To differentiate this from an ordinary * call to slotFileOpen() call isImporting(). */ void slotImportFile(); /** * File --> Export * * This will call slotFileSaveAs(). To differentiate this from an ordinary * call to slotFileSaveAs() call isExporting(). */ void slotExportFile(); void slotEncryptDocument(); void slotUncompressToDir(); void slotProgress(int value); /** * Hide the dockers */ void toggleDockersVisibility(bool visible); /** * Saves the document, asking for a filename if necessary. * * @param saveas if set to TRUE the user is always prompted for a filename * * @param silent if set to TRUE rootDocument()->setTitleModified will not be called. * * @param specialOutputFlag set to enums defined in KoDocument if save to special output format * * @return TRUE on success, false on error or cancel * (don't display anything in this case, the error dialog box is also implemented here * but restore the original URL in slotFileSaveAs) */ bool saveDocument(bool saveas = false, bool silent = false, int specialOutputFlag = 0); void slotConfigure(); private: /** * This setting indicates who is calling chooseNewDocument. * Usually the app will want to * - show the template dialog with 'everything' if InitDocAppStarting, InitDocFileClose or InitDocEmbedded * - show the template dialog with 'templates only' if InitDocFileNew * - create an empty document with default settings if InitDocEmpty */ enum InitDocFlags { /*InitDocAppStarting, */ InitDocFileNew, InitDocFileClose /*, InitDocEmbedded, InitDocEmpty*/ }; /// Helper method for slotFileNew and slotFileClose void chooseNewDocument(InitDocFlags initDocFlags); - /** - * Create a new empty document. - */ - KoPart* createPart() const; - void closeEvent(QCloseEvent * e) override; void resizeEvent(QResizeEvent * e) override; /** * Ask user about saving changes to the document upon exit. */ bool queryClose() override; bool openDocumentInternal(const QUrl &url, KoPart *newpart = 0, KoDocument *newdoc = 0); /** * Returns whether or not the current slotFileSave[As]() or saveDocument() * call is actually an export operation (like File --> Export). * * If this is true, you must call KoDocument::export() instead of * KoDocument::save() or KoDocument::saveAs(), in any reimplementation of * saveDocument(). */ bool isExporting() const; /** * Returns whether or not the current slotFileOpen() or openDocument() * call is actually an import operation (like File --> Import). * * If this is true, you must call KoDocument::import() instead of * KoDocument::openUrl(), in any reimplementation of openDocument() or * openDocumentInternal(). */ bool isImporting() const; KRecentFilesAction *recentAction() const; private Q_SLOTS: /** * Save the list of recent files. */ void saveRecentFiles(); void slotLoadCompleted(); void slotLoadCanceled(const QString &); void slotSaveCompleted(); void slotSaveCanceled(const QString &); void forceDockTabFonts(); /** * Slot to create a new view for the currently activate @ref #koDocument. */ virtual void newView(); void slotPrintPreviewPaintRequest(QPrinter *printer); // --------------------- PartManager private: friend class KoPart; /** * Removes a part from the manager (this does not delete the object) . * * Sets the active part to 0 if @p part is the activePart() . */ virtual void removePart( KoPart *part ); /** * Sets the active part. * * The active part receives activation events. * * @p widget can be used to specify which widget was responsible for the activation. * This is important if you have multiple views for a document/part , like in KOffice . */ virtual void setActivePart(KoPart *part, QWidget *widget); private Q_SLOTS: /** * @internal */ void slotWidgetDestroyed(); void slotDocumentTitleModified(const QString &caption, bool mod); // --------------------- PartManager private: void createMainwindowGUI(); /** * Asks the user if they really want to save the document. * Called only if outputFormat != nativeFormat. * * @return true if the document should be saved */ bool exportConfirmation(const QByteArray &outputFormat); void saveWindowSettings(); // retrieve the current KoView KoView* currentView() const; private: KoMainWindowPrivate * const d; }; #endif diff --git a/src/libs/main/KoPart.cpp b/src/libs/main/KoPart.cpp index 8257bea0..bd15be43 100644 --- a/src/libs/main/KoPart.cpp +++ b/src/libs/main/KoPart.cpp @@ -1,288 +1,293 @@ /* This file is part of the KDE project * Copyright (C) 1998, 1999 Torben Weis * Copyright (C) 2000-2005 David Faure * Copyright (C) 2007-2008 Thorsten Zachmann * Copyright (C) 2010-2012 Boudewijn Rempt * Copyright (C) 2011 Inge Wallin * * 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. */ // clazy:excludeall=qstring-arg #include "KoPart.h" #include "KoApplication.h" #include "KoMainWindow.h" #include "KoDocument.h" #include "KoView.h" #include "KoFilterManager.h" #include //#include //#include #include #include #include #include #include #include #include #include #include #include #ifndef QT_NO_DBUS #include #include "KoPartAdaptor.h" #endif class Q_DECL_HIDDEN KoPart::Private { public: Private(const KoComponentData &componentData_, KoPart *_parent) : parent(_parent) , document(0) , componentData(componentData_) { } ~Private() { /// FIXME ok, so this is obviously bad to leave like this // For now, this is undeleted, but only to avoid an odd double // delete condition. Until that's discovered, we'll need this // to avoid crashes in Gemini //delete canvasItem; } KoPart *parent; QList views; QList mainWindows; QPointer document; QList documents; QString templatesResourcePath; KoComponentData componentData; }; KoPart::KoPart(const KoComponentData &componentData, QObject *parent) : QObject(parent) , d(new Private(componentData, this)) { #ifndef QT_NO_DBUS new KoPartAdaptor(this); QDBusConnection::sessionBus().registerObject('/' + objectName(), this); #endif } KoPart::~KoPart() { // Tell our views that the document is already destroyed and // that they shouldn't try to access it. foreach(KoView *view, views()) { view->setDocumentDeleted(); } while (!d->mainWindows.isEmpty()) { delete d->mainWindows.takeFirst(); } // normally document is deleted in KoMainWindow (why?) // but if not, we delete it here delete d->document; delete d; } KoComponentData KoPart::componentData() const { return d->componentData; } void KoPart::setDocument(KoDocument *document) { Q_ASSERT(document); d->document = document; } KoDocument *KoPart::document() const { return d->document; } KoView *KoPart::createView(KoDocument *document, QWidget *parent) { KoView *view = createViewInstance(document, parent); addView(view, document); if (!d->documents.contains(document)) { d->documents.append(document); } return view; } void KoPart::addView(KoView *view, KoDocument *document) { if (!view) return; if (!d->views.contains(view)) { d->views.append(view); } if (!d->documents.contains(document)) { d->documents.append(document); } view->updateReadWrite(document->isReadWrite()); if (d->views.size() == 1) { KoApplication *app = qobject_cast(qApp); if (0 != app) { emit app->documentOpened('/'+objectName()); } } } void KoPart::removeView(KoView *view) { d->views.removeAll(view); if (d->views.isEmpty()) { KoApplication *app = qobject_cast(qApp); if (0 != app) { emit app->documentClosed('/'+objectName()); } } } QList KoPart::views() const { return d->views; } int KoPart::viewCount() const { return d->views.count(); } QGraphicsItem *KoPart::createCanvasItem(KoDocument *document) { Q_UNUSED(document) return 0; /* KoView *view = createView(document); QGraphicsProxyWidget *proxy = new QGraphicsProxyWidget(); QWidget *canvasController = view->findChild(); proxy->setWidget(canvasController); return proxy;*/ } void KoPart::addMainWindow(KoMainWindow *mainWindow) { if (d->mainWindows.indexOf(mainWindow) == -1) { debugMain <<"mainWindow" << (void*)mainWindow <<"added to doc" << this; d->mainWindows.append(mainWindow); connect(mainWindow, &KoMainWindow::configure, this, &KoPart::configure); } } void KoPart::removeMainWindow(KoMainWindow *mainWindow) { debugMain <<"mainWindow" << (void*)mainWindow <<"removed from doc" << this; if (mainWindow) { d->mainWindows.removeAll(mainWindow); disconnect(mainWindow, &KoMainWindow::configure, this, &KoPart::configure); } } const QList& KoPart::mainWindows() const { return d->mainWindows; } int KoPart::mainwindowCount() const { return d->mainWindows.count(); } KoMainWindow *KoPart::currentMainwindow() const { QWidget *widget = qApp->activeWindow(); KoMainWindow *mainWindow = qobject_cast(widget); while (!mainWindow && widget) { widget = widget->parentWidget(); mainWindow = qobject_cast(widget); } if (!mainWindow && mainWindows().size() > 0) { mainWindow = mainWindows().first(); } return mainWindow; } void KoPart::openExistingFile(const QUrl &url) { QApplication::setOverrideCursor(Qt::BusyCursor); d->document->openUrl(url); d->document->setModified(false); QApplication::restoreOverrideCursor(); } void KoPart::openTemplate(const QUrl &url) { QApplication::setOverrideCursor(Qt::BusyCursor); bool ok = d->document->loadNativeFormat(url.toLocalFile()); d->document->setModified(false); d->document->undoStack()->clear(); if (ok) { QString mimeType = QMimeDatabase().mimeTypeForUrl(url).name(); // in case this is a open document template remove the -template from the end mimeType.remove( QRegExp( "-template$" ) ); d->document->setMimeTypeAfterLoading(mimeType); d->document->resetURL(); d->document->setEmpty(); } else { d->document->showLoadingErrorDialog(); d->document->initEmpty(); } QApplication::restoreOverrideCursor(); } +bool KoPart::openProjectTemplate(const QUrl &url) +{ + return false; +} + void KoPart::addRecentURLToAllMainWindows(const QString &projectName, const QUrl &url) { // Add to recent actions list in our mainWindows foreach(KoMainWindow *mainWindow, d->mainWindows) { mainWindow->addRecentURL(projectName, url); } } void KoPart::setTemplatesResourcePath(const QString &templatesResourcePath) { Q_ASSERT(!templatesResourcePath.isEmpty()); Q_ASSERT(templatesResourcePath.endsWith(QLatin1Char('/'))); d->templatesResourcePath = templatesResourcePath; } QString KoPart::templatesResourcePath() const { return d->templatesResourcePath; } diff --git a/src/libs/main/KoPart.h b/src/libs/main/KoPart.h index 037c7e2e..79212f1f 100644 --- a/src/libs/main/KoPart.h +++ b/src/libs/main/KoPart.h @@ -1,211 +1,219 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright (C) 2000-2005 David Faure Copyright (C) 2007 Thorsten Zachmann Copyright (C) 2010 Boudewijn Rempt 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 KOPART_H #define KOPART_H #include #include #include "komain_export.h" #include class KoDocument; class KoView; class KoComponentData; class KoOpenPane; class QGraphicsItem; /** * Override this class in your application. It's the main entry point that * should provide the document, the view and the component data to the calligra * system. * * There is/will be a single KoPart instance for an application that will manage * the list of documents, views and mainwindows. * * It hasn't got much to do with kparts anymore. */ class KOMAIN_EXPORT KoPart : public QObject { Q_OBJECT public: /** * Constructor. * * @param componentData data about the component * @param parent may be another KoDocument, or anything else. * Usually passed by KPluginFactory::create. */ explicit KoPart(const KoComponentData &componentData, QObject *parent); /** * Destructor. * * The destructor does not delete any attached KoView objects and it does not * delete the attached widget as returned by widget(). */ ~KoPart() override; /** * @return The componentData ( KoComponentData ) for this GUI client. You set the componentdata * in your subclass: setComponentData(AppFactory::componentData()); in the constructor */ KoComponentData componentData() const; /** * @param document the document this part manages */ void setDocument(KoDocument *document); /** * @return the document this part loads and saves to and makes views for */ KoDocument *document() const; // ----------------- mainwindow management ----------------- /** * Create a new main window, but does not add it to the current set of managed main windows. */ virtual KoMainWindow *createMainWindow() = 0; /** * Appends the mainwindow to the list of mainwindows which show this * document as their root document. * * This method is automatically called from KoMainWindow::setRootDocument, * so you do not need to call it. */ virtual void addMainWindow(KoMainWindow *mainWindow); /** * Removes the mainwindow from the list. */ virtual void removeMainWindow(KoMainWindow *mainWindow); /** * @return the list of main windows. */ const QList& mainWindows() const; /** * @return the number of shells for the main window */ int mainwindowCount() const; void addRecentURLToAllMainWindows(const QString &projectName, const QUrl &url); KoMainWindow *currentMainwindow() const; + /** + * This loads a project template. + * Re-implement if needed, this implementation just returns false. + * @param url the template to load + * @return true on success + */ + virtual bool openProjectTemplate(const QUrl &url); + public Q_SLOTS: /** * This slot loads an existing file and deletes the start up widget. * @param url the file to load */ virtual void openExistingFile(const QUrl &url); virtual void configure(KoMainWindow *mw) { Q_UNUSED(mw); } protected Q_SLOTS: /** * This slot loads a template and deletes the start up widget. * @param url the template to load */ virtual void openTemplate(const QUrl &url); public: /** * Create a new view for the document. */ KoView *createView(KoDocument *document, QWidget *parent = 0); /** * Adds a view to the document. If the part doesn't know yet about * the document, it is registered. * * This calls KoView::updateReadWrite to tell the new view * whether the document is readonly or not. */ virtual void addView(KoView *view, KoDocument *document); /** * Removes a view of the document. */ virtual void removeView(KoView *view); /** * @return a list of views this document is displayed in */ QList views() const; /** * @return number of views this document is displayed in */ int viewCount() const; /** * Template resource path used. This is used by the start up widget to show * the correct templates. */ QString templatesResourcePath() const; /** * Creates and shows the start up widget. * @param parent the KoMainWindow used as parent for the widget. * @param alwaysShow always show the widget even if the user has configured it to not show. */ virtual void showStartUpWidget(KoMainWindow *parent) = 0; protected: /** * Set the templates resource path used. This is used by the start up widget to show * the correct templates. */ void setTemplatesResourcePath(const QString &templatesResourcePath); virtual KoView *createViewInstance(KoDocument *document, QWidget *parent) = 0; /** * Override this to create a QGraphicsItem that does not rely * on proxying a KoCanvasController. */ virtual QGraphicsItem *createCanvasItem(KoDocument *document); private: Q_DISABLE_COPY(KoPart) class Private; Private *const d; }; #endif diff --git a/src/libs/ui/CMakeLists.txt b/src/libs/ui/CMakeLists.txt index b7cd1d19..a60de33b 100644 --- a/src/libs/ui/CMakeLists.txt +++ b/src/libs/ui/CMakeLists.txt @@ -1,225 +1,222 @@ include_directories( ${PLANKERNEL_INCLUDES} ${PLANMODELS_INCLUDES} ${PLANMAIN_INCLUDES} ${PLANWIDGETS_INCLUDES} ${KDEPIMLIBS_INCLUDE_DIR} ) #add_subdirectory( tests ) ########### KPlato private library ############### if (PLAN_USE_KREPORT) message(STATUS "-- Building plan with reports capability") add_subdirectory(reports/items) set(planreports_LIB_SRC reports/reportview.cpp reports/reportdata.cpp reports/reportsourceeditor.cpp reports/reportscripts.cpp ) set(planreports_ui_LIB_SRCS reports/reportsourceeditor.ui reports/reportnavigator.ui reports/reportsectionswidget.ui reports/reportgroupsectionswidget.ui reports/reporttoolswidget.ui ) endif() set(planui_LIB_SRCS ${planreports_LIB_SRC} RelationEditorDialog.cpp TasksEditController.cpp TasksEditDialog.cpp RichTextWidget.cpp - welcome/WelcomeView.cpp reportsgenerator/ReportsGeneratorView.cpp kptganttitemdelegate.cpp kptworkpackagesendpanel.cpp kptworkpackagesenddialog.cpp kptdocumentseditor.cpp kptdocumentspanel.cpp kptdocumentsdialog.cpp kptitemviewsettup.cpp kptsplitterview.cpp kptrelationeditor.cpp kptdependencyeditor.cpp kptusedefforteditor.cpp kpttaskstatusview.cpp kptcalendareditor.cpp kptviewbase.cpp kptaccountseditor.cpp kptperteditor.cpp kptpertresult.cpp kpttaskeditor.cpp kptresourceeditor.cpp kptscheduleeditor.cpp kptsummarytaskdialog.cpp kptsummarytaskgeneralpanel.cpp kptresourceappointmentsview.cpp kptaccountsviewconfigdialog.cpp kptaccountsview.cpp kpttaskcostpanel.cpp kptmilestoneprogresspanel.cpp kptmilestoneprogressdialog.cpp kpttaskdialog.cpp kptmainprojectdialog.cpp kptmainprojectpanel.cpp kptganttview.cpp gantt/DateTimeTimeLine.cpp gantt/DateTimeGrid.cpp kptrelationdialog.cpp kptrequestresourcespanel.cpp kptresourcedialog.cpp kptstandardworktimedialog.cpp kptintervaledit.cpp kpttaskgeneralpanel.cpp kpttaskprogresspanel.cpp kpttaskprogressdialog.cpp kpttaskdescriptiondialog.cpp kptwbsdefinitiondialog.cpp kptwbsdefinitionpanel.cpp kptresourceassignmentview.cpp kptresourceallocationeditor.cpp kptworkpackagemergedialog.cpp kptrecalculatedialog.cpp kpthtmlview.cpp locale/localemon.cpp kptlocaleconfigmoneydialog.cpp ResourceAllocationView.cpp performance/KPlatoChart.cpp performance/PerformanceStatusBase.cpp performance/ProjectStatusView.cpp performance/PerformanceStatusView.cpp performance/PerformanceTableView.cpp ) ki18n_wrap_ui(planui_LIB_SRCS ${planreports_ui_LIB_SRCS} RelationEditorDialog.ui - welcome/WelcomeView.ui - kptresourceappointmentsdisplayoptions.ui kptganttchartdisplayoptions.ui kptprintingheaderfooter.ui kptganttprintingoptions.ui kptworkpackagesendpanel.ui kptdocumentspanel.ui performance/PerformanceStatus.ui performance/PerformanceStatusViewSettingsPanel.ui kptcpmwidget.ui kptitemviewsettings.ui kptpertresult.ui standardworktimedialogbase.ui kptwbsdefinitionpanelbase.ui kptaccountsviewconfigurepanelbase.ui kptintervaleditbase.ui kpttaskcostpanelbase.ui kpttaskdescriptionpanelbase.ui kptsummarytaskgeneralpanelbase.ui kptmilestoneprogresspanelbase.ui resourcedialogbase.ui kptmainprojectpanelbase.ui relationpanel.ui kpttaskgeneralpanelbase.ui kpttaskprogresspanelbase.ui kptperteditor.ui kptresourceassignmentview.ui kpttaskstatusviewsettingspanel.ui kptworkpackagemergepanel.ui kptrecalculatedialog.ui kptscheduleeditor.ui locale/localemon.ui ) add_library(planui SHARED ${planui_LIB_SRCS}) generate_export_header(planui) target_link_libraries(planui PUBLIC planmain planmodels KF5::KHtml PRIVATE KChart KF5::ItemViews KF5::IconThemes KF5::Archive KF5::TextWidgets KF5::KIOCore KF5::KIOFileWidgets KF5::KIOWidgets KF5::KIONTLM ) if (PLAN_USE_KREPORT) target_link_libraries(planui PUBLIC KReport PRIVATE KPropertyWidgets) endif() if(KF5AkonadiContact_FOUND) target_link_libraries(planui PRIVATE KF5::AkonadiContact) endif() set_target_properties(planui PROPERTIES VERSION ${GENERIC_PLAN_LIB_VERSION} SOVERSION ${GENERIC_PLAN_LIB_SOVERSION} ) install(TARGETS planui ${INSTALL_TARGETS_DEFAULT_ARGS}) install( FILES AccountsEditorUi.rc AccountsEditorUi_readonly.rc AccountsViewUi.rc CalendarEditorUi.rc CalendarEditorUi_readonly.rc TaskEditorUi.rc TaskEditorUi_readonly.rc DependencyEditorUi.rc DependencyEditorUi_readonly.rc ResourceEditorUi.rc ResourceEditorUi_readonly.rc ResourceAppointmentsViewUi.rc ScheduleEditorUi.rc ScheduleEditorUi_readonly.rc GanttViewUi.rc WorkPackageViewUi.rc WorkPackageViewUi_readonly.rc reportsgenerator/ReportsGeneratorViewUi.rc reportsgenerator/ReportsGeneratorViewUi_readonly.rc performance/PerformanceStatusViewUi.rc performance/ProjectStatusViewUi.rc PertResultUi.rc TaskViewUi.rc TaskStatusViewUi.rc DESTINATION ${KXMLGUI_INSTALL_DIR}/calligraplan ) # reports files install(FILES reportsgenerator/templates/EmptyTemplate.odt reportsgenerator/templates/ProjectPerformanceCost.odt reportsgenerator/templates/ProjectPerformanceEffort.odt reportsgenerator/templates/TaskStatus.odt DESTINATION ${DATA_INSTALL_DIR}/calligraplan/reports ) diff --git a/src/libs/ui/welcome/WelcomeView.cpp b/src/welcome/WelcomeView.cpp similarity index 74% rename from src/libs/ui/welcome/WelcomeView.cpp rename to src/welcome/WelcomeView.cpp index ae8e3e95..8236e171 100644 --- a/src/libs/ui/welcome/WelcomeView.cpp +++ b/src/welcome/WelcomeView.cpp @@ -1,341 +1,404 @@ /* This file is part of the KDE project Copyright (C) 2017 Dag Andersen 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. */ // clazy:excludeall=qstring-arg #include "WelcomeView.h" +#include #include "kptcommand.h" #include "kptdebug.h" #include "Help.h" +#include "kptpart.h" +#include "kptmaindocument.h" +#include #include #include #include #include #include #include #include #include #include #include #include #include #include const QLoggingCategory &PLANWELCOME_LOG() { static const QLoggingCategory category("calligra.plan.welcome"); return category; } -#define debugWelcome qCDebug(PLANWELCOME_LOG) +#define debugWelcome qCDebug(PLANWELCOME_LOG)< actions); }; +} + +using namespace KPlato; RecentFilesModel::RecentFilesModel(QObject *parent) : QStandardItemModel(parent) { } Qt::ItemFlags RecentFilesModel::flags(const QModelIndex &idx) const { Qt::ItemFlags f = (QStandardItemModel::flags(idx) & ~Qt::ItemIsEditable); return f; } QVariant RecentFilesModel::data(const QModelIndex &idx, int role) const { switch(role) { case Qt::DecorationRole: return QIcon::fromTheme(QStringLiteral("document-open")); case Qt::FontRole: { break; } default: break; } return QStandardItemModel::data(idx, role); } void RecentFilesModel::populate(const QList actions) { clear(); setColumnCount(1); setRowCount(actions.count()); QModelIndex idx = index(rowCount()-1, 0); for (const QAction *a : actions) { // KRecentFilesAction format: [] // so we split it up and remove the [] QString s = a->text(); QString name = s.left(s.indexOf('[')); QString file = s.mid(s.indexOf('[')+1); file = file.left(file.lastIndexOf(']')); QString t = QString("%1\n%2").arg(name, file); setData(idx, t, Qt::EditRole); setData(idx, file, Qt::UserRole+1); idx = idx.sibling(idx.row()-1, idx.column()); } } //----------------------------------- WelcomeView::WelcomeView(KoPart *part, KoDocument *doc, QWidget *parent) : ViewBase(part, doc, parent) , m_projectdialog(0) , m_filedialog(0) { - widget.setupUi(this); - widget.recentProjects->setBackgroundRole(QPalette::Midlight); + ui.setupUi(this); + ui.recentProjects->setBackgroundRole(QPalette::Midlight); + ui.projectTemplates->setBackgroundRole(QPalette::Midlight); - Help::add(widget.newProjectBtn, + Help::add(ui.newProjectBtn, xi18nc("@info:whatsthis", "Create a new project" "" "Creates a new project with default values defined in" " Settings." "Opens the project dialog" " so you can define project specific properties like" " Project Name," " Target Start" " and - End times." "More..." "", Help::page("Creating_a_Project"))); - Help::add(widget.createResourceFileBtn, + Help::add(ui.createResourceFileBtn, xi18nc("@info:whatsthis", "Shared resources" "" "Create a shared resources file." "This enables you to only create your resources once," " you just refer to your resources file when you create a new project." "These resources can then be shared between projects" " to avoid overbooking resources across projects." "Shared resources must be defined in a separate file." "More..." "", Help::page("Managing_Resources"))); - Help::add(widget.recentProjects, + Help::add(ui.recentProjects, xi18nc("@info:whatsthis", "Recent Projects" "" "A list of the 10 most recent project files opened." "" "This enables you to quickly open projects you have worked on recently." "")); - Help::add(widget.introductionBtn, + Help::add(ui.introductionBtn, xi18nc("@info:whatsthis", "Introduction to <application>Plan</application>" "" "These introductory pages gives you hints and tips on what" " you can use Plan for, and how to use it." "")); - Help::add(widget.contextHelp, + Help::add(ui.contextHelp, xi18nc("@info:whatsthis", "Context help" "" "Help is available many places using What's This." "It is activated using the menu entry Help->What's this?" " or the keyboard shortcut Shift+F1." "" "In dialogs it is available via the ? in the dialog title bar." "" "If you see More... in the text," " pressing it will display more information from online resources in your browser." "", Help::page("Context_Help"))); m_model = new RecentFilesModel(this); - widget.recentProjects->setModel(m_model); - widget.recentProjects->setSelectionMode(QAbstractItemView::SingleSelection); + ui.recentProjects->setModel(m_model); + ui.recentProjects->setSelectionMode(QAbstractItemView::SingleSelection); + + setProjectTemplatesModel(); + setupGui(); - connect(widget.newProjectBtn, &QAbstractButton::clicked, this, &WelcomeView::slotNewProject); - connect(widget.createResourceFileBtn, &QAbstractButton::clicked, this, &WelcomeView::slotCreateResourceFile); - connect(widget.openProjectBtn, &QAbstractButton::clicked, this, &WelcomeView::slotOpenProject); - connect(widget.introductionBtn, &QAbstractButton::clicked, this, &WelcomeView::showIntroduction); + connect(ui.newProjectBtn, &QAbstractButton::clicked, this, &WelcomeView::slotNewProject); + connect(ui.createResourceFileBtn, &QAbstractButton::clicked, this, &WelcomeView::slotCreateResourceFile); + connect(ui.openProjectBtn, &QAbstractButton::clicked, this, &WelcomeView::slotOpenProject); + connect(ui.introductionBtn, &QAbstractButton::clicked, this, &WelcomeView::showIntroduction); + + connect(ui.recentProjects, &QAbstractItemView::activated, this, &WelcomeView::slotRecentFileSelected); + + connect(ui.projectTemplates, &QAbstractItemView::activated, this, &WelcomeView::slotOpenProjectTemplate); - connect(widget.recentProjects, &QAbstractItemView::activated, this, &WelcomeView::slotRecentFileSelected); + connect(mainWindow(), &KoMainWindow::loadCompleted, this, &WelcomeView::finished); } WelcomeView::~WelcomeView() { debugWelcome; } +Project *WelcomeView::project() const +{ + return koDocument() ? koDocument()->project() : nullptr; +} + void WelcomeView::setRecentFiles(const QList &actions) { m_model->populate(actions); } void WelcomeView::updateReadWrite(bool /*readwrite */) { } void WelcomeView::setGuiActive(bool activate) { debugPlan<isEmpty() ? koDocument()->documentPart() : nullptr; if (url.isValid()) { emit recentProject(url, part); - emit finished(); } } } void WelcomeView::slotContextMenuRequested(const QModelIndex &/*index*/, const QPoint& /*pos */) { //debugPlan<config().setDefaultValues(*p); if (!m_projectdialog) { m_projectdialog = new MainProjectDialog(*p, this, false /*edit*/); connect(m_projectdialog.data(), &MainProjectDialog::dialogFinished, this, &WelcomeView::slotProjectEditFinished); connect(m_projectdialog.data(), &MainProjectDialog::sigLoadSharedResources, this, &WelcomeView::slotLoadSharedResources); } m_projectdialog->open(); } } void WelcomeView::slotProjectEditFinished(int result) { - qDebug()<(sender()); if (dia == 0) { return; } if (result == QDialog::Accepted) { MacroCommand *cmd = dia->buildCommand(); if (cmd) { cmd->execute(); delete cmd; koDocument()->setModified(true); } emit projectCreated(); emit selectDefaultView(); emit finished(); } dia->deleteLater(); } void WelcomeView::slotCreateResourceFile() { QString file = QStandardPaths::locate(QStandardPaths::AppDataLocation, "templates/.source/SharedResources.plant"); emit openTemplate(QUrl::fromUserInput(file)); emit finished(); } void WelcomeView::slotOpenProject() { - if (m_projectdialog) { - qWarning()<mimeFilter(KoFilterManager::Import)); filedialog.setHideNameFilterDetailsOption(); KoPart *part = koDocument()->isEmpty() ? koDocument()->documentPart() : nullptr; QUrl url = QUrl::fromUserInput(filedialog.filename()); - if (!url.isEmpty() && mainWindow()->openDocument(part, url)) { - emit finished(); + if (!url.isEmpty()) { + mainWindow()->openDocument(part, url); + } + } +} + +void WelcomeView::slotOpenProjectTemplate(const QModelIndex &idx) +{ + if (idx.isValid()) { + QString file = idx.data(Qt::UserRole+1).toString(); + QUrl url = QUrl::fromUserInput(file); + debugWelcome<documentPart(); + bool ok = false; + if (part && url.isValid()) { + ok = part->openProjectTemplate(url); + } + if (ok) { + Project *p = part->document()->project(); + Q_ASSERT(p); + MainProjectDialog dlg(*p, nullptr, false /*edit*/); + if (dlg.exec()) { + MacroCommand *cmd = dlg.buildCommand(); + if (cmd) { + cmd->execute(); + delete cmd; + part->document()->setModified(true); + if (part->document()->url().isEmpty() && !p->name().isEmpty()) { + part->document()->setUrl(QUrl(p->name() + ".plan")); + } + emit finished(); + } + } else { + warnWelcome<<"cancelled dialog"; + koDocument()->initEmpty(); + } + } else { + warnWelcome<<"Failed to load template:"<(sender()); if (dia == 0) { return; } if (result == QDialog::Accepted) { QUrl url = QUrl::fromUserInput(dia->filename()); KoPart *part = koDocument()->isEmpty() ? nullptr : koDocument()->documentPart(); if (!url.isEmpty() && mainWindow()->openDocument(part, url)) { emit finished(); } } dia->deleteLater(); } void WelcomeView::slotLoadSharedResources(const QString &file, const QUrl &projects, bool loadProjectsAtStartup) { QUrl url(file); if (url.scheme().isEmpty()) { url.setScheme("file"); } if (url.isValid()) { emit loadSharedResources(url, loadProjectsAtStartup ? projects :QUrl()); } } -} // namespace KPlato +void WelcomeView::setProjectTemplatesModel() +{ + QStandardItemModel *m = new QStandardItemModel(ui.projectTemplates); + const ConfigBase &config = koDocument()->project()->config(); + const QStringList dirs = config.projectTemplatePaths(); + for (const QString &path : dirs) { + QDir dir(path, "*.plant"); + for (const QString &file : dir.entryList(QDir::Files)) { + QStandardItem *item = new QStandardItem(file.left(file.lastIndexOf(".plant"))); + item->setData(QString(path + '/' + file)); + item->setToolTip(item->data().toString()); + item->setIcon(koIcon("document-new-from-template")); + m->appendRow(item); + } + } + delete ui.projectTemplates->model(); + ui.projectTemplates->setModel(m); +} diff --git a/src/libs/ui/welcome/WelcomeView.h b/src/welcome/WelcomeView.h similarity index 91% rename from src/libs/ui/welcome/WelcomeView.h rename to src/welcome/WelcomeView.h index ec5fc0ff..d38e68d1 100644 --- a/src/libs/ui/welcome/WelcomeView.h +++ b/src/welcome/WelcomeView.h @@ -1,102 +1,107 @@ /* This file is part of the KDE project Copyright (C) 2017 Dag Andersen 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 WELCOMEVIEW_H #define WELCOMEVIEW_H -#include "planui_export.h" +#include "plan_export.h" #include "kptviewbase.h" #include "ui_WelcomeView.h" #include "kptmainprojectdialog.h" #include class KoDocument; class QUrl; class QItemSelecteion; namespace KPlato { class RecentFilesModel; -class PLANUI_EXPORT WelcomeView : public ViewBase +class PLAN_EXPORT WelcomeView : public ViewBase { Q_OBJECT public: WelcomeView(KoPart *part, KoDocument *doc, QWidget *parent); ~WelcomeView() override; void setRecentFiles(const QList &actions); void setupGui(); void updateReadWrite(bool readwrite) override; KoPrintJob *createPrintJob() override; + void setProjectTemplatesModel(); + + Project *project() const override; public Q_SLOTS: /// Activate/deactivate the gui void setGuiActive(bool activate) override; Q_SIGNALS: void newProject(); void openProject(); void recentProject(const QUrl &file, KoPart *part); void showIntroduction(); void selectDefaultView(); void loadSharedResources(const QUrl &url, const QUrl &projects); void openExistingFile(const QUrl &url); void projectCreated(); void finished(); void openTemplate(QUrl); protected: void updateActionsEnabled( bool on = true); private Q_SLOTS: void slotContextMenuRequested(const QModelIndex &index, const QPoint& pos); void slotRecentFileSelected(const QModelIndex &idx); void slotEnableActions(bool on); void slotNewProject(); void slotOpenProject(); void slotLoadSharedResources(const QString &file, const QUrl &projects, bool loadProjectsAtStartup); + void slotOpenProjectTemplate(const QModelIndex &idx); + void slotProjectEditFinished(int result); void slotOpenFileFinished(int result); void slotCreateResourceFile(); private: - Ui::WelcomeView widget; + Ui::WelcomeView ui; RecentFilesModel *m_model; QPointer m_projectdialog; QPointer m_filedialog; }; } //KPlato namespace #endif diff --git a/src/libs/ui/welcome/WelcomeView.ui b/src/welcome/WelcomeView.ui similarity index 90% rename from src/libs/ui/welcome/WelcomeView.ui rename to src/welcome/WelcomeView.ui index 50e19dae..86474a0b 100644 --- a/src/libs/ui/welcome/WelcomeView.ui +++ b/src/welcome/WelcomeView.ui @@ -1,343 +1,362 @@ KPlato::WelcomeView 0 0 688 364 Qt::Horizontal 13 14 New project .. Qt::Horizontal 13 17 - - - Qt::Vertical + + + <html><head/><body><p><span style=" font-size:8pt; font-style:italic;">Template projects:</span></p></body></html> - - - 20 - 40 - + + Qt::AlignCenter - + + + + + + QAbstractItemView::NoEditTriggers + + + true + + + Qt::ElideMiddle + + + false + + + false + + Qt::Horizontal 40 20 Create Shared Resources .. Qt::Horizontal 40 20 Qt::Horizontal 40 20 <html><head/><body><p><span style=" font-weight:600;">Open Project</span></p><p>Displays a file dialog enabling you to open an existing project.</p></body></html> Open Project... .. Qt::Horizontal 40 20 <html><head/><body><p><span style=" font-size:8pt; font-style:italic;">Recent projects:</span></p></body></html> Qt::AlignCenter true + + Qt::ElideMiddle + false false false Qt::Vertical Qt::Horizontal 40 20 Introduction .. Qt::Horizontal 40 20 Qt::Vertical 20 40 <html><head/><body><p><span style=" font-weight:600;">Context Help</span></p><p>Many functions has help and hints that can be displayed with <span style=" font-style:italic;">What's this</span>.</p><p>Activate it by selecting <span style=" font-style:italic;">Help-&gt;What's this</span> or <span style=" font-style:italic;">Shift+F1</span>.</p><p>Try it on this text!</p></body></html> true Qt::AlignHCenter|Qt::AlignTop true Qt::Vertical 20 40 Qt::Horizontal https://userbase.kde.org/Plan <a href="https://userbase.kde.org/Plan">Plan user documentation</a> Qt::AlignCenter true https://forum.kde.org/viewforum.php?f=203 <a href="https://forum.kde.org/viewforum.php?f=203">Community forum</a> Qt::AlignCenter true https://calligra.org <a href="https://calligra.org">Calligra</a> Qt::AlignCenter true