diff --git a/src/libs/kundo2/kundo2stack.cpp b/src/libs/kundo2/kundo2stack.cpp index f2ce1e7c..79af9b23 100644 --- a/src/libs/kundo2/kundo2stack.cpp +++ b/src/libs/kundo2/kundo2stack.cpp @@ -1,1441 +1,1446 @@ /* * Copyright (c) 2014 Dmitry Kazakov * Copyright (c) 2014 Mohit Goyal * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /**************************************************************************** ** ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of the QtGui module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** No Commercial Usage ** This file contains pre-release code and may not be distributed. ** You may use this file in accordance with the terms and conditions ** contained in the Technology Preview License Agreement accompanying ** this package. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** If you have questions regarding the use of this file, please contact ** Nokia at qt-info@nokia.com. ** ** ** ** ** ** ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #include #include #include #include "kundo2stack.h" #include "kundo2stack_p.h" #include "kundo2group.h" #include #include #ifndef QT_NO_UNDOCOMMAND /*! \class KUndo2Command \brief The KUndo2Command class is the base class of all commands stored on a KUndo2QStack. \since 4.2 For an overview of Qt's Undo Framework, see the \l{Overview of Qt's Undo Framework}{overview document}. A KUndo2Command represents a single editing action on a document; for example, inserting or deleting a block of text in a text editor. KUndo2Command can apply a change to the document with redo() and undo the change with undo(). The implementations for these functions must be provided in a derived class. \snippet doc/src/snippets/code/src_gui_util_qundostack.cpp 0 A KUndo2Command has an associated text(). This is a short string describing what the command does. It is used to update the text properties of the stack's undo and redo actions; see KUndo2QStack::createUndoAction() and KUndo2QStack::createRedoAction(). KUndo2Command objects are owned by the stack they were pushed on. KUndo2QStack deletes a command if it has been undone and a new command is pushed. For example: \snippet doc/src/snippets/code/src_gui_util_qundostack.cpp 1 In effect, when a command is pushed, it becomes the top-most command on the stack. To support command compression, KUndo2Command has an id() and the virtual function mergeWith(). These functions are used by KUndo2QStack::push(). To support command macros, a KUndo2Command object can have any number of child commands. Undoing or redoing the parent command will cause the child commands to be undone or redone. A command can be assigned to a parent explicitly in the constructor. In this case, the command will be owned by the parent. The parent in this case is usually an empty command, in that it doesn't provide its own implementation of undo() and redo(). Instead, it uses the base implementations of these functions, which simply call undo() or redo() on all its children. The parent should, however, have a meaningful text(). \snippet doc/src/snippets/code/src_gui_util_qundostack.cpp 2 Another way to create macros is to use the convenience functions KUndo2QStack::beginMacro() and KUndo2QStack::endMacro(). \sa KUndo2QStack */ /*! Constructs a KUndo2Command object with the given \a parent and \a text. If \a parent is not 0, this command is appended to parent's child list. The parent command then owns this command and will delete it in its destructor. \sa ~KUndo2Command() */ KUndo2Command::KUndo2Command(const KUndo2MagicString &text, KUndo2Command *parent): m_hasParent(parent != 0), m_timedID(0), m_endOfCommand(QTime::currentTime()) { d = new KUndo2CommandPrivate; if (parent != 0) { parent->d->child_list.append(this); } setText(text); setTime(); } /*! Constructs a KUndo2Command object with parent \a parent. If \a parent is not 0, this command is appended to parent's child list. The parent command then owns this command and will delete it in its destructor. \sa ~KUndo2Command() */ KUndo2Command::KUndo2Command(KUndo2Command *parent): m_hasParent(parent != 0),m_timedID(0) { d = new KUndo2CommandPrivate; if (parent != 0) parent->d->child_list.append(this); setTime(); } /*! Destroys the KUndo2Command object and all child commands. \sa KUndo2Command() */ KUndo2Command::~KUndo2Command() { qDeleteAll(d->child_list); delete d; } /*! Returns the ID of this command. A command ID is used in command compression. It must be an integer unique to this command's class, or -1 if the command doesn't support compression. If the command supports compression this function must be overridden in the derived class to return the correct ID. The base implementation returns -1. KUndo2QStack::push() will only try to merge two commands if they have the same ID, and the ID is not -1. \sa mergeWith(), KUndo2QStack::push() */ int KUndo2Command::id() const { return -1; } /*! Attempts to merge this command with \a command. Returns true on success; otherwise returns false. If this function returns true, calling this command's redo() must have the same effect as redoing both this command and \a command. Similarly, calling this command's undo() must have the same effect as undoing \a command and this command. KUndo2QStack will only try to merge two commands if they have the same id, and the id is not -1. The default implementation returns false. \snippet doc/src/snippets/code/src_gui_util_qundostack.cpp 3 \sa id() KUndo2QStack::push() */ bool KUndo2Command::mergeWith(const KUndo2Command *command) { Q_UNUSED(command); return false; } /*! Applies a change to the document. This function must be implemented in the derived class. Calling KUndo2QStack::push(), KUndo2QStack::undo() or KUndo2QStack::redo() from this function leads to undefined behavior. The default implementation calls redo() on all child commands. \sa undo() */ void KUndo2Command::redo() { for (int i = 0; i < d->child_list.size(); ++i) d->child_list.at(i)->redo(); } /*! Reverts a change to the document. After undo() is called, the state of the document should be the same as before redo() was called. This function must be implemented in the derived class. Calling KUndo2QStack::push(), KUndo2QStack::undo() or KUndo2QStack::redo() from this function leads to undefined behavior. The default implementation calls undo() on all child commands in reverse order. \sa redo() */ void KUndo2Command::undo() { for (int i = d->child_list.size() - 1; i >= 0; --i) d->child_list.at(i)->undo(); } /*! Returns a short text string describing what this command does; for example, "insert text". The text is used when the text properties of the stack's undo and redo actions are updated. \sa setText(), KUndo2QStack::createUndoAction(), KUndo2QStack::createRedoAction() */ QString KUndo2Command::actionText() const { if(d->actionText!=0) return d->actionText; else return QString(); } /*! Returns a short text string describing what this command does; for example, "insert text". The text is used when the text properties of the stack's undo and redo actions are updated. \sa setText(), KUndo2QStack::createUndoAction(), KUndo2QStack::createRedoAction() */ KUndo2MagicString KUndo2Command::text() const { return d->text; } /*! Sets the command's text to be the \a text specified. The specified text should be a short user-readable string describing what this command does. \sa text() KUndo2QStack::createUndoAction() KUndo2QStack::createRedoAction() */ void KUndo2Command::setText(const KUndo2MagicString &undoText) { d->text = undoText; d->actionText = undoText.toSecondaryString(); } /*! \since 4.4 Returns the number of child commands in this command. \sa child() */ int KUndo2Command::childCount() const { return d->child_list.count(); } /*! \since 4.4 Returns the child command at \a index. \sa childCount(), KUndo2QStack::command() */ const KUndo2Command *KUndo2Command::child(int index) const { if (index < 0 || index >= d->child_list.count()) return 0; return d->child_list.at(index); } bool KUndo2Command::hasParent() { return m_hasParent; } int KUndo2Command::timedId() { return m_timedID; } void KUndo2Command::setTimedID(int value) { m_timedID = value; } bool KUndo2Command::timedMergeWith(KUndo2Command *other) { if(other->timedId() == this->timedId() && other->timedId()!=-1 ) m_mergeCommandsVector.append(other); else return false; return true; } void KUndo2Command::setTime() { m_timeOfCreation = QTime::currentTime(); } QTime KUndo2Command::time() { return m_timeOfCreation; } void KUndo2Command::setEndTime() { m_endOfCommand = QTime::currentTime(); } QTime KUndo2Command::endTime() { return m_endOfCommand; } void KUndo2Command::undoMergedCommands() { undo(); if (!mergeCommandsVector().isEmpty()) { QVectorIterator it(mergeCommandsVector()); it.toFront(); while (it.hasNext()) { KUndo2Command* cmd = it.next(); cmd->undoMergedCommands(); } } } void KUndo2Command::redoMergedCommands() { if (!mergeCommandsVector().isEmpty()) { QVectorIterator it(mergeCommandsVector()); it.toBack(); while (it.hasPrevious()) { KUndo2Command* cmd = it.previous(); cmd->redoMergedCommands(); } } redo(); } QVector KUndo2Command::mergeCommandsVector() { return m_mergeCommandsVector; } bool KUndo2Command::isMerged() { return !m_mergeCommandsVector.isEmpty(); } KUndo2CommandExtraData* KUndo2Command::extraData() const { return d->extraData.data(); } void KUndo2Command::setExtraData(KUndo2CommandExtraData *data) { d->extraData.reset(data); } #endif // QT_NO_UNDOCOMMAND #ifndef QT_NO_UNDOSTACK /*! \class KUndo2QStack \brief The KUndo2QStack class is a stack of KUndo2Command objects. \since 4.2 For an overview of Qt's Undo Framework, see the \l{Overview of Qt's Undo Framework}{overview document}. An undo stack maintains a stack of commands that have been applied to a document. New commands are pushed on the stack using push(). Commands can be undone and redone using undo() and redo(), or by triggering the actions returned by createUndoAction() and createRedoAction(). KUndo2QStack keeps track of the \a current command. This is the command which will be executed by the next call to redo(). The index of this command is returned by index(). The state of the edited object can be rolled forward or back using setIndex(). If the top-most command on the stack has already been redone, index() is equal to count(). KUndo2QStack provides support for undo and redo actions, command compression, command macros, and supports the concept of a \e{clean state}. \section1 Undo and Redo Actions KUndo2QStack provides convenient undo and redo QAction objects, which can be inserted into a menu or a toolbar. When commands are undone or redone, KUndo2QStack updates the text properties of these actions to reflect what change they will trigger. The actions are also disabled when no command is available for undo or redo. These actions are returned by KUndo2QStack::createUndoAction() and KUndo2QStack::createRedoAction(). \section1 Command Compression and Macros Command compression is useful when several commands can be compressed into a single command that can be undone and redone in a single operation. For example, when a user types a character in a text editor, a new command is created. This command inserts the character into the document at the cursor position. However, it is more convenient for the user to be able to undo or redo typing of whole words, sentences, or paragraphs. Command compression allows these single-character commands to be merged into a single command which inserts or deletes sections of text. For more information, see KUndo2Command::mergeWith() and push(). A command macro is a sequence of commands, all of which are undone and redone in one go. Command macros are created by giving a command a list of child commands. Undoing or redoing the parent command will cause the child commands to be undone or redone. Command macros may be created explicitly by specifying a parent in the KUndo2Command constructor, or by using the convenience functions beginMacro() and endMacro(). Although command compression and macros appear to have the same effect to the user, they often have different uses in an application. Commands that perform small changes to a document may be usefully compressed if there is no need to individually record them, and if only larger changes are relevant to the user. However, for commands that need to be recorded individually, or those that cannot be compressed, it is useful to use macros to provide a more convenient user experience while maintaining a record of each command. \section1 Clean State KUndo2QStack supports the concept of a clean state. When the document is saved to disk, the stack can be marked as clean using setClean(). Whenever the stack returns to this state through undoing and redoing commands, it emits the signal cleanChanged(). This signal is also emitted when the stack leaves the clean state. This signal is usually used to enable and disable the save actions in the application, and to update the document's title to reflect that it contains unsaved changes. \sa KUndo2Command, KUndo2View */ #ifndef QT_NO_ACTION KUndo2Action::KUndo2Action(const QString &textTemplate, const QString &defaultText, QObject *parent) : QAction(parent) { m_textTemplate = textTemplate; m_defaultText = defaultText; } void KUndo2Action::setPrefixedText(const QString &text) { if (text.isEmpty()) setText(m_defaultText); else setText(m_textTemplate.arg(text)); } #endif // QT_NO_ACTION /*! \internal Sets the current index to \a idx, emitting appropriate signals. If \a clean is true, makes \a idx the clean index as well. */ void KUndo2QStack::setIndex(int idx, bool clean) { bool was_clean = m_index == m_clean_index; if (m_lastMergedIndex <= idx) { m_lastMergedSetCount = idx - m_lastMergedIndex; } else { m_lastMergedSetCount = 1; m_lastMergedIndex = idx-1; } if(idx == 0){ m_lastMergedSetCount = 0; m_lastMergedIndex = 0; } if (idx != m_index) { m_index = idx; emit indexChanged(m_index); emit canUndoChanged(canUndo()); emit undoTextChanged(undoText()); emit canRedoChanged(canRedo()); emit redoTextChanged(redoText()); } if (clean) m_clean_index = m_index; bool is_clean = m_index == m_clean_index; if (is_clean != was_clean) emit cleanChanged(is_clean); } void KUndo2QStack::purgeRedoState() { bool macro = !m_macro_stack.isEmpty(); if (macro) return; bool redoStateChanged = false; bool cleanStateChanged = false; while (m_index < m_command_list.size()) { delete m_command_list.takeLast(); redoStateChanged = true; } if (m_clean_index > m_index) { m_clean_index = -1; // we've deleted the clean state cleanStateChanged = true; } if (redoStateChanged) { emit canRedoChanged(canRedo()); emit redoTextChanged(redoText()); } if (cleanStateChanged) { emit cleanChanged(isClean()); } } /*! \internal If the number of commands on the stack exceedes the undo limit, deletes commands from the bottom of the stack. Returns true if commands were deleted. */ bool KUndo2QStack::checkUndoLimit() { if (m_undo_limit <= 0 || !m_macro_stack.isEmpty() || m_undo_limit >= m_command_list.count()) return false; int del_count = m_command_list.count() - m_undo_limit; for (int i = 0; i < del_count; ++i) delete m_command_list.takeFirst(); m_index -= del_count; if (m_clean_index != -1) { if (m_clean_index < del_count) m_clean_index = -1; // we've deleted the clean command else m_clean_index -= del_count; } return true; } /*! Constructs an empty undo stack with the parent \a parent. The stack will initially be in the clean state. If \a parent is a KUndo2Group object, the stack is automatically added to the group. \sa push() */ KUndo2QStack::KUndo2QStack(QObject *parent) : QObject(parent), m_index(0), m_clean_index(0), m_group(0), m_undo_limit(0), m_useCumulativeUndoRedo(false), m_lastMergedSetCount(0), m_lastMergedIndex(0) { setTimeT1(5); setTimeT2(1); setStrokesN(2); #ifndef QT_NO_UNDOGROUP if (KUndo2Group *group = qobject_cast(parent)) group->addStack(this); #endif } /*! Destroys the undo stack, deleting any commands that are on it. If the stack is in a KUndo2Group, the stack is automatically removed from the group. \sa KUndo2QStack() */ KUndo2QStack::~KUndo2QStack() { #ifndef QT_NO_UNDOGROUP if (m_group != 0) m_group->removeStack(this); #endif clear(); } /*! Clears the command stack by deleting all commands on it, and returns the stack to the clean state.{ } Commands are not undone or redone; the state of the edited object remains unchanged. This function is usually used when the contents of the document are abandoned. \sa KUndo2QStack() */ void KUndo2QStack::clear() { if (m_command_list.isEmpty()) return; bool was_clean = isClean(); m_macro_stack.clear(); qDeleteAll(m_command_list); m_command_list.clear(); m_index = 0; m_clean_index = 0; emit indexChanged(0); emit canUndoChanged(false); emit undoTextChanged(QString()); emit canRedoChanged(false); emit redoTextChanged(QString()); if (!was_clean) emit cleanChanged(true); } /*! Pushes \a cmd on the stack or merges it with the most recently executed command. In either case, executes \a cmd by calling its redo() function. If \a cmd's id is not -1, and if the id is the same as that of the most recently executed command, KUndo2QStack will attempt to merge the two commands by calling KUndo2Command::mergeWith() on the most recently executed command. If KUndo2Command::mergeWith() returns true, \a cmd is deleted and false is returned. In all other cases \a cmd is simply pushed on the stack and true is returned. If commands were undone before \a cmd was pushed, the current command and all commands above it are deleted. Hence \a cmd always ends up being the top-most on the stack. Once a command is pushed, the stack takes ownership of it. There are no getters to return the command, since modifying it after it has been executed will almost always lead to corruption of the document's state. \sa KUndo2Command::id() KUndo2Command::mergeWith() */ bool KUndo2QStack::push(KUndo2Command *cmd) { cmd->redoMergedCommands(); cmd->setEndTime(); bool macro = !m_macro_stack.isEmpty(); KUndo2Command *cur = 0; if (macro) { KUndo2Command *macro_cmd = m_macro_stack.last(); if (!macro_cmd->d->child_list.isEmpty()) cur = macro_cmd->d->child_list.last(); } else { if (m_index > 0) cur = m_command_list.at(m_index - 1); while (m_index < m_command_list.size()) delete m_command_list.takeLast(); if (m_clean_index > m_index) m_clean_index = -1; // we've deleted the clean state } bool try_merge = cur != 0 && cur->id() != -1 && cur->id() == cmd->id() && (macro || m_index != m_clean_index); /*! *Here we are going to try to merge several commands together using the QVector field in the commands using *3 parameters. N : Number of commands that should remain individual at the top of the stack. T1 : Time lapsed between current command and previously merged command -- signal to *merge throughout the stack. T2 : Time lapsed between two commands signalling both commands belong to the same set *Whenever a KUndo2Command is initialized -- it consists of a start-time and when it is pushed --an end time. *Every time a command is pushed -- it checks whether the command pushed was pushed after T1 seconds of the last merged command *Then the merging begins with each group depending on the time in between each command (T2). * *@TODO : Currently it is not able to merge two merged commands together. */ if (!macro && m_command_list.size() > 1 && cmd->timedId() != -1 && m_useCumulativeUndoRedo) { KUndo2Command* lastcmd = m_command_list.last(); if (qAbs(cmd->time().msecsTo(lastcmd->endTime())) < m_timeT2 * 1000) { m_lastMergedSetCount++; } else { m_lastMergedSetCount = 0; m_lastMergedIndex = m_index-1; } if (lastcmd->timedId() == -1){ m_lastMergedSetCount = 0; m_lastMergedIndex = m_index; } if (m_lastMergedSetCount > m_strokesN) { KUndo2Command* toMerge = m_command_list.at(m_lastMergedIndex); if (toMerge && m_command_list.size() >= m_lastMergedIndex + 1 && m_command_list.at(m_lastMergedIndex + 1)) { if(toMerge->timedMergeWith(m_command_list.at(m_lastMergedIndex + 1))){ m_command_list.removeAt(m_lastMergedIndex + 1); } m_lastMergedSetCount--; m_lastMergedIndex = m_command_list.indexOf(toMerge); } } m_index = m_command_list.size(); if(m_lastMergedIndextime().msecsTo(m_command_list.at(m_lastMergedIndex)->endTime()) < -m_timeT1 * 1000) { //T1 time elapsed QListIterator it(m_command_list); it.toBack(); m_lastMergedSetCount = 1; while (it.hasPrevious()) { KUndo2Command* curr = it.previous(); KUndo2Command* lastCmdInCurrent = curr; if (!lastcmd->mergeCommandsVector().isEmpty()) { if (qAbs(lastcmd->mergeCommandsVector().constLast()->time().msecsTo(lastCmdInCurrent->endTime())) < int(m_timeT2 * 1000) && lastcmd != lastCmdInCurrent && lastcmd != curr) { if(lastcmd->timedMergeWith(curr)){ if (m_command_list.contains(curr)) { m_command_list.removeOne(curr); } } } else { lastcmd = curr; //end of a merge set } } else { if (qAbs(lastcmd->time().msecsTo(lastCmdInCurrent->endTime())) < int(m_timeT2 * 1000) && lastcmd != lastCmdInCurrent &&lastcmd!=curr) { if(lastcmd->timedMergeWith(curr)){ if (m_command_list.contains(curr)){ m_command_list.removeOne(curr); } } } else { lastcmd = curr; //end of a merge set } } } m_lastMergedIndex = m_command_list.size()-1; } } m_index = m_command_list.size(); } if (try_merge && cur->mergeWith(cmd)) { delete cmd; cmd = 0; if (!macro) { emit indexChanged(m_index); emit canUndoChanged(canUndo()); emit undoTextChanged(undoText()); emit canRedoChanged(canRedo()); emit redoTextChanged(redoText()); } } else { if (macro) { m_macro_stack.last()->d->child_list.append(cmd); } else { m_command_list.append(cmd); if(checkUndoLimit()) { m_lastMergedIndex = m_index - m_strokesN; } setIndex(m_index + 1, false); } } return cmd; } /*! Marks the stack as clean and emits cleanChanged() if the stack was not already clean. Whenever the stack returns to this state through the use of undo/redo commands, it emits the signal cleanChanged(). This signal is also emitted when the stack leaves the clean state. \sa isClean(), cleanIndex() */ void KUndo2QStack::setClean() { if (!m_macro_stack.isEmpty()) { qWarning("KUndo2QStack::setClean(): cannot set clean in the middle of a macro"); return; } setIndex(m_index, true); } /*! If the stack is in the clean state, returns true; otherwise returns false. \sa setClean() cleanIndex() */ bool KUndo2QStack::isClean() const { if (!m_macro_stack.isEmpty()) return false; return m_clean_index == m_index; } /*! Returns the clean index. This is the index at which setClean() was called. A stack may not have a clean index. This happens if a document is saved, some commands are undone, then a new command is pushed. Since push() deletes all the undone commands before pushing the new command, the stack can't return to the clean state again. In this case, this function returns -1. \sa isClean() setClean() */ int KUndo2QStack::cleanIndex() const { return m_clean_index; } /*! Undoes the command below the current command by calling KUndo2Command::undo(). Decrements the current command index. If the stack is empty, or if the bottom command on the stack has already been undone, this function does nothing. \sa redo() index() */ void KUndo2QStack::undo() { if (m_index == 0) return; if (!m_macro_stack.isEmpty()) { qWarning("KUndo2QStack::undo(): cannot undo in the middle of a macro"); return; } int idx = m_index - 1; m_command_list.at(idx)->undoMergedCommands(); setIndex(idx, false); } /*! Redoes the current command by calling KUndo2Command::redo(). Increments the current command index. If the stack is empty, or if the top command on the stack has already been redone, this function does nothing. \sa undo() index() */ void KUndo2QStack::redo() { if (m_index == m_command_list.size()) return; if (!m_macro_stack.isEmpty()) { qWarning("KUndo2QStack::redo(): cannot redo in the middle of a macro"); return; } m_command_list.at(m_index)->redoMergedCommands(); setIndex(m_index + 1, false); } /*! Returns the number of commands on the stack. Macro commands are counted as one command. \sa index() setIndex() command() */ int KUndo2QStack::count() const { return m_command_list.size(); } /*! Returns the index of the current command. This is the command that will be executed on the next call to redo(). It is not always the top-most command on the stack, since a number of commands may have been undone. \sa undo() redo() count() */ int KUndo2QStack::index() const { return m_index; } /*! Repeatedly calls undo() or redo() until the current command index reaches \a idx. This function can be used to roll the state of the document forwards of backwards. indexChanged() is emitted only once. \sa index() count() undo() redo() */ void KUndo2QStack::setIndex(int idx) { if (!m_macro_stack.isEmpty()) { qWarning("KUndo2QStack::setIndex(): cannot set index in the middle of a macro"); return; } if (idx < 0) idx = 0; else if (idx > m_command_list.size()) idx = m_command_list.size(); int i = m_index; while (i < idx) { m_command_list.at(i++)->redoMergedCommands(); notifySetIndexChangedOneCommand(); } while (i > idx) { m_command_list.at(--i)->undoMergedCommands(); notifySetIndexChangedOneCommand(); } setIndex(idx, false); } /** * Called by setIndex after every command execution. It is needed by * Krita to insert barriers between different kind of commands */ void KUndo2QStack::notifySetIndexChangedOneCommand() { } /*! Returns true if there is a command available for undo; otherwise returns false. This function returns false if the stack is empty, or if the bottom command on the stack has already been undone. Synonymous with index() == 0. \sa index() canRedo() */ bool KUndo2QStack::canUndo() const { if (!m_macro_stack.isEmpty()) return false; return m_index > 0; } /*! Returns true if there is a command available for redo; otherwise returns false. This function returns false if the stack is empty or if the top command on the stack has already been redone. Synonymous with index() == count(). \sa index() canUndo() */ bool KUndo2QStack::canRedo() const { if (!m_macro_stack.isEmpty()) return false; return m_index < m_command_list.size(); } /*! Returns the text of the command which will be undone in the next call to undo(). \sa KUndo2Command::text() redoActionText() undoItemText() */ QString KUndo2QStack::undoText() const { if (!m_macro_stack.isEmpty()) return QString(); if (m_index > 0 && m_command_list.at(m_index-1)!=0) return m_command_list.at(m_index - 1)->actionText(); return QString(); } /*! Returns the text of the command which will be redone in the next call to redo(). \sa KUndo2Command::text() undoActionText() redoItemText() */ QString KUndo2QStack::redoText() const { if (!m_macro_stack.isEmpty()) return QString(); if (m_index < m_command_list.size()) return m_command_list.at(m_index)->actionText(); return QString(); } #ifndef QT_NO_ACTION /*! Creates an undo QAction object with the given \a parent. Triggering this action will cause a call to undo(). The text of this action is the text of the command which will be undone in the next call to undo(), prefixed by the specified \a prefix. If there is no command available for undo, this action will be disabled. If \a prefix is empty, the default prefix "Undo" is used. \sa createRedoAction(), canUndo(), KUndo2Command::text() */ QAction *KUndo2QStack::createUndoAction(QObject *parent) const { KUndo2Action *result = new KUndo2Action(i18n("Undo %1"), i18nc("Default text for undo action", "Undo"), parent); result->setEnabled(canUndo()); result->setPrefixedText(undoText()); connect(this, SIGNAL(canUndoChanged(bool)), result, SLOT(setEnabled(bool))); connect(this, SIGNAL(undoTextChanged(QString)), result, SLOT(setPrefixedText(QString))); connect(result, SIGNAL(triggered()), this, SLOT(undo())); return result; } /*! Creates an redo QAction object with the given \a parent. Triggering this action will cause a call to redo(). The text of this action is the text of the command which will be redone in the next call to redo(), prefixed by the specified \a prefix. If there is no command available for redo, this action will be disabled. If \a prefix is empty, the default prefix "Redo" is used. \sa createUndoAction(), canRedo(), KUndo2Command::text() */ QAction *KUndo2QStack::createRedoAction(QObject *parent) const { KUndo2Action *result = new KUndo2Action(i18n("Redo %1"), i18nc("Default text for redo action", "Redo"), parent); result->setEnabled(canRedo()); result->setPrefixedText(redoText()); connect(this, SIGNAL(canRedoChanged(bool)), result, SLOT(setEnabled(bool))); connect(this, SIGNAL(redoTextChanged(QString)), result, SLOT(setPrefixedText(QString))); connect(result, SIGNAL(triggered()), this, SLOT(redo())); return result; } #endif // QT_NO_ACTION /*! Begins composition of a macro command with the given \a text description. An empty command described by the specified \a text is pushed on the stack. Any subsequent commands pushed on the stack will be appended to the empty command's children until endMacro() is called. Calls to beginMacro() and endMacro() may be nested, but every call to beginMacro() must have a matching call to endMacro(). While a macro is composed, the stack is disabled. This means that: \list \i indexChanged() and cleanChanged() are not emitted, \i canUndo() and canRedo() return false, \i calling undo() or redo() has no effect, \i the undo/redo actions are disabled. \endlist The stack becomes enabled and appropriate signals are emitted when endMacro() is called for the outermost macro. \snippet doc/src/snippets/code/src_gui_util_qundostack.cpp 4 This code is equivalent to: \snippet doc/src/snippets/code/src_gui_util_qundostack.cpp 5 \sa endMacro() */ void KUndo2QStack::beginMacro(const KUndo2MagicString &text) { KUndo2Command *cmd = new KUndo2Command(); cmd->setText(text); if (m_macro_stack.isEmpty()) { while (m_index < m_command_list.size()) delete m_command_list.takeLast(); if (m_clean_index > m_index) m_clean_index = -1; // we've deleted the clean state m_command_list.append(cmd); } else { m_macro_stack.last()->d->child_list.append(cmd); } m_macro_stack.append(cmd); if (m_macro_stack.count() == 1) { emit canUndoChanged(false); emit undoTextChanged(QString()); emit canRedoChanged(false); emit redoTextChanged(QString()); } } /*! Ends composition of a macro command. If this is the outermost macro in a set nested macros, this function emits indexChanged() once for the entire macro command. \sa beginMacro() */ void KUndo2QStack::endMacro() { if (m_macro_stack.isEmpty()) { qWarning("KUndo2QStack::endMacro(): no matching beginMacro()"); return; } m_macro_stack.removeLast(); if (m_macro_stack.isEmpty()) { checkUndoLimit(); setIndex(m_index + 1, false); } } /*! \since 4.4 Returns a const pointer to the command at \a index. This function returns a const pointer, because modifying a command, once it has been pushed onto the stack and executed, almost always causes corruption of the state of the document, if the command is later undone or redone. \sa KUndo2Command::child() */ const KUndo2Command *KUndo2QStack::command(int index) const { if (index < 0 || index >= m_command_list.count()) return 0; return m_command_list.at(index); } /*! Returns the text of the command at index \a idx. \sa beginMacro() */ QString KUndo2QStack::text(int idx) const { if (idx < 0 || idx >= m_command_list.size()) return QString(); return m_command_list.at(idx)->text().toString(); } /*! \property KUndo2QStack::undoLimit \brief the maximum number of commands on this stack. \since 4.3 When the number of commands on a stack exceedes the stack's undoLimit, commands are deleted from the bottom of the stack. Macro commands (commands with child commands) are treated as one command. The default value is 0, which means that there is no limit. This property may only be set when the undo stack is empty, since setting it on a non-empty stack might delete the command at the current index. Calling setUndoLimit() on a non-empty stack prints a warning and does nothing. */ void KUndo2QStack::setUndoLimit(int limit) { if (!m_command_list.isEmpty()) { qWarning("KUndo2QStack::setUndoLimit(): an undo limit can only be set when the stack is empty"); return; } if (limit == m_undo_limit) return; m_undo_limit = limit; checkUndoLimit(); + emit undoLimitChanged(m_undo_limit); } int KUndo2QStack::undoLimit() const { return m_undo_limit; } /*! \property KUndo2QStack::active \brief the active status of this stack. An application often has multiple undo stacks, one for each opened document. The active stack is the one associated with the currently active document. If the stack belongs to a KUndo2Group, calls to KUndo2Group::undo() or KUndo2Group::redo() will be forwarded to this stack when it is active. If the KUndo2Group is watched by a KUndo2View, the view will display the contents of this stack when it is active. If the stack does not belong to a KUndo2Group, making it active has no effect. It is the programmer's responsibility to specify which stack is active by calling setActive(), usually when the associated document window receives focus. \sa KUndo2Group */ void KUndo2QStack::setActive(bool active) { #ifdef QT_NO_UNDOGROUP Q_UNUSED(active); #else + bool prev = isActive(); if (m_group != 0) { if (active) m_group->setActiveStack(this); else if (m_group->activeStack() == this) m_group->setActiveStack(0); } + if (isActive() != prev) { + emit activeChanged(!prev); + } #endif } bool KUndo2QStack::isActive() const { #ifdef QT_NO_UNDOGROUP return true; #else return m_group == 0 || m_group->activeStack() == this; #endif } void KUndo2QStack::setUseCumulativeUndoRedo(bool value) { m_useCumulativeUndoRedo = value; } bool KUndo2QStack::useCumulativeUndoRedo() { return m_useCumulativeUndoRedo; } void KUndo2QStack::setTimeT1(double value) { m_timeT1 = value; } double KUndo2QStack::timeT1() { return m_timeT1; } void KUndo2QStack::setTimeT2(double value) { m_timeT2 = value; } double KUndo2QStack::timeT2() { return m_timeT2; } int KUndo2QStack::strokesN() { return m_strokesN; } void KUndo2QStack::setStrokesN(int value) { m_strokesN = value; } QAction* KUndo2Stack::createRedoAction(KActionCollection* actionCollection, const QString& actionName) { QAction* action = KUndo2QStack::createRedoAction(actionCollection); if (actionName.isEmpty()) { action->setObjectName(KStandardAction::name(KStandardAction::Redo)); } else { action->setObjectName(actionName); } action->setIcon(koIcon("edit-redo")); action->setIconText(i18n("Redo")); action->setShortcuts(KStandardShortcut::redo()); actionCollection->addAction(action->objectName(), action); return action; } QAction* KUndo2Stack::createUndoAction(KActionCollection* actionCollection, const QString& actionName) { QAction* action = KUndo2QStack::createUndoAction(actionCollection); if (actionName.isEmpty()) { action->setObjectName(KStandardAction::name(KStandardAction::Undo)); } else { action->setObjectName(actionName); } action->setIcon(koIcon("edit-undo")); action->setIconText(i18n("Undo")); action->setShortcuts(KStandardShortcut::undo()); actionCollection->addAction(action->objectName(), action); return action; } /*! \fn void KUndo2QStack::indexChanged(int idx) This signal is emitted whenever a command modifies the state of the document. This happens when a command is undone or redone. When a macro command is undone or redone, or setIndex() is called, this signal is emitted only once. \a idx specifies the index of the current command, ie. the command which will be executed on the next call to redo(). \sa index() setIndex() */ /*! \fn void KUndo2QStack::cleanChanged(bool clean) This signal is emitted whenever the stack enters or leaves the clean state. If \a clean is true, the stack is in a clean state; otherwise this signal indicates that it has left the clean state. \sa isClean() setClean() */ /*! \fn void KUndo2QStack::undoTextChanged(const QString &undoText) This signal is emitted whenever the value of undoText() changes. It is used to update the text property of the undo action returned by createUndoAction(). \a undoText specifies the new text. */ /*! \fn void KUndo2QStack::canUndoChanged(bool canUndo) This signal is emitted whenever the value of canUndo() changes. It is used to enable or disable the undo action returned by createUndoAction(). \a canUndo specifies the new value. */ /*! \fn void KUndo2QStack::redoTextChanged(const QString &redoText) This signal is emitted whenever the value of redoText() changes. It is used to update the text property of the redo action returned by createRedoAction(). \a redoText specifies the new text. */ /*! \fn void KUndo2QStack::canRedoChanged(bool canRedo) This signal is emitted whenever the value of canRedo() changes. It is used to enable or disable the redo action returned by createRedoAction(). \a canRedo specifies the new value. */ KUndo2Stack::KUndo2Stack(QObject *parent): KUndo2QStack(parent) { } #endif // QT_NO_UNDOSTACK diff --git a/src/libs/kundo2/kundo2stack.h b/src/libs/kundo2/kundo2stack.h index c35c8f14..103f6a48 100644 --- a/src/libs/kundo2/kundo2stack.h +++ b/src/libs/kundo2/kundo2stack.h @@ -1,259 +1,261 @@ /* * Copyright (c) 2014 Dmitry Kazakov * Copyright (c) 2014 Mohit Goyal * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /**************************************************************************** ** ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of the QtGui module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** No Commercial Usage ** This file contains pre-release code and may not be distributed. ** You may use this file in accordance with the terms and conditions ** contained in the Technology Preview License Agreement accompanying ** this package. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** If you have questions regarding the use of this file, please contact ** Nokia at qt-info@nokia.com. ** ** ** ** ** ** ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #ifndef KUNDO2STACK_H #define KUNDO2STACK_H #include #include #include #include #include #include #include "kundo2_export.h" class QAction; class KUndo2CommandPrivate; class KUndo2Group; class KActionCollection; #ifndef QT_NO_UNDOCOMMAND #include "kundo2magicstring.h" #include "kundo2commandextradata.h" class KUNDO2_EXPORT KUndo2Command { KUndo2CommandPrivate *d; int timedID; public: explicit KUndo2Command(KUndo2Command *parent = 0); explicit KUndo2Command(const KUndo2MagicString &text, KUndo2Command *parent = 0); virtual ~KUndo2Command(); virtual void undo(); virtual void redo(); QString actionText() const; KUndo2MagicString text() const; void setText(const KUndo2MagicString &text); virtual int id() const; virtual int timedId(); virtual void setTimedID(int timedID); virtual bool mergeWith(const KUndo2Command *other); virtual bool timedMergeWith(KUndo2Command *other); int childCount() const; const KUndo2Command *child(int index) const; bool hasParent(); virtual void setTime(); virtual QTime time(); virtual void setEndTime(); virtual QTime endTime(); virtual QVector mergeCommandsVector(); virtual bool isMerged(); virtual void undoMergedCommands(); virtual void redoMergedCommands(); /** * \return user-defined object associated with the command * * \see setExtraData() */ KUndo2CommandExtraData* extraData() const; /** * The user can assign an arbitrary object associated with the * command. The \p data object is owned by the command. If you assign * the object twice, the first one will be destroyed. */ void setExtraData(KUndo2CommandExtraData *data); private: Q_DISABLE_COPY(KUndo2Command) friend class KUndo2QStack; bool m_hasParent; int m_timedID; QTime m_timeOfCreation; QTime m_endOfCommand; QVector m_mergeCommandsVector; }; #endif // QT_NO_UNDOCOMMAND #ifndef QT_NO_UNDOSTACK class KUNDO2_EXPORT KUndo2QStack : public QObject { Q_OBJECT // Q_DECLARE_PRIVATE(KUndo2QStack) - Q_PROPERTY(bool active READ isActive WRITE setActive) - Q_PROPERTY(int undoLimit READ undoLimit WRITE setUndoLimit) + Q_PROPERTY(bool active READ isActive WRITE setActive NOTIFY activeChanged) + Q_PROPERTY(int undoLimit READ undoLimit WRITE setUndoLimit NOTIFY undoLimitChanged) public: explicit KUndo2QStack(QObject *parent = 0); virtual ~KUndo2QStack(); void clear(); bool push(KUndo2Command *cmd); bool canUndo() const; bool canRedo() const; QString undoText() const; QString redoText() const; int count() const; int index() const; QString actionText(int idx) const; QString text(int idx) const; #ifndef QT_NO_ACTION QAction *createUndoAction(QObject *parent) const; QAction *createRedoAction(QObject *parent) const; #endif // QT_NO_ACTION bool isActive() const; bool isClean() const; int cleanIndex() const; void beginMacro(const KUndo2MagicString &text); void endMacro(); void setUndoLimit(int limit); int undoLimit() const; const KUndo2Command *command(int index) const; void setUseCumulativeUndoRedo(bool value); bool useCumulativeUndoRedo(); void setTimeT1(double value); double timeT1(); void setTimeT2(double value); double timeT2(); int strokesN(); void setStrokesN(int value); public Q_SLOTS: void setClean(); virtual void setIndex(int idx); virtual void undo(); virtual void redo(); void setActive(bool active = true); void purgeRedoState(); Q_SIGNALS: void indexChanged(int idx); void cleanChanged(bool clean); void canUndoChanged(bool canUndo); void canRedoChanged(bool canRedo); void undoTextChanged(const QString &undoActionText); void redoTextChanged(const QString &redoActionText); + void activeChanged(bool); + void undoLimitChanged(int); protected: virtual void notifySetIndexChangedOneCommand(); private: // from QUndoStackPrivate QList m_command_list; QList m_macro_stack; int m_index; int m_clean_index; KUndo2Group *m_group; int m_undo_limit; bool m_useCumulativeUndoRedo; double m_timeT1; double m_timeT2; int m_strokesN; int m_lastMergedSetCount; int m_lastMergedIndex; // also from QUndoStackPrivate void setIndex(int idx, bool clean); bool checkUndoLimit(); Q_DISABLE_COPY(KUndo2QStack) friend class KUndo2Group; }; class KUNDO2_EXPORT KUndo2Stack : public KUndo2QStack { Q_OBJECT public: explicit KUndo2Stack(QObject *parent = 0); // functions from KUndoStack QAction* createRedoAction(KActionCollection* actionCollection, const QString& actionName = QString()); QAction* createUndoAction(KActionCollection* actionCollection, const QString& actionName = QString()); }; #endif // QT_NO_UNDOSTACK #endif // KUNDO2STACK_H diff --git a/src/libs/kundo2/kundo2view.cpp b/src/libs/kundo2/kundo2view.cpp index 8c83ab55..1b615d66 100644 --- a/src/libs/kundo2/kundo2view.cpp +++ b/src/libs/kundo2/kundo2view.cpp @@ -1,285 +1,287 @@ /* This file is part of the KDE project * Copyright (C) 2010 Matus Talcik * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /**************************************************************************** ** ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of the QtGui module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** No Commercial Usage ** This file contains pre-release code and may not be distributed. ** You may use this file in accordance with the terms and conditions ** contained in the Technology Preview License Agreement accompanying ** this package. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** If you have questions regarding the use of this file, please contact ** Nokia at qt-info@nokia.com. ** ** ** ** ** ** ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "kundo2stack.h" #include "kundo2view.h" #include "kundo2model.h" #include "kundo2group.h" #ifndef QT_NO_UNDOVIEW #include #include #include /*! \class KUndo2View \brief The KUndo2View class displays the contents of a KUndo2QStack. \since 4.2 \ingroup advanced KUndo2View is a QListView which displays the list of commands pushed on an undo stack. The most recently executed command is always selected. Selecting a different command results in a call to KUndo2QStack::setIndex(), rolling the state of the document backwards or forward to the new command. The stack can be set explicitly with setStack(). Alternatively, a QUndoGroup object can be set with setGroup(). The view will then update itself automatically whenever the active stack of the group changes. \image KUndo2View.png */ class KUndo2ViewPrivate { public: KUndo2ViewPrivate() : #ifndef QT_NO_UNDOGROUP group(0), #endif model(0) {} #ifndef QT_NO_UNDOGROUP QPointer group; #endif KUndo2Model *model; KUndo2View* q; void init(KUndo2View* view); }; void KUndo2ViewPrivate::init(KUndo2View* view) { q = view; model = new KUndo2Model(q); q->setModel(model); q->setSelectionModel(model->selectionModel()); } /*! Constructs a new view with parent \a parent. */ KUndo2View::KUndo2View(QWidget *parent) : QListView(parent), d(new KUndo2ViewPrivate) { d->init(this); } /*! Constructs a new view with parent \a parent and sets the observed stack to \a stack. */ KUndo2View::KUndo2View(KUndo2QStack *stack, QWidget *parent) : QListView(parent), d(new KUndo2ViewPrivate) { d->init(this); setStack(stack); } #ifndef QT_NO_UNDOGROUP /*! Constructs a new view with parent \a parent and sets the observed group to \a group. The view will update itself autmiatically whenever the active stack of the group changes. */ KUndo2View::KUndo2View(KUndo2Group *group, QWidget *parent) : QListView(parent), d(new KUndo2ViewPrivate) { d->init(this); setGroup(group); } #endif // QT_NO_UNDOGROUP /*! Destroys this view. */ KUndo2View::~KUndo2View() { delete d; } /*! Returns the stack currently displayed by this view. If the view is looking at a QUndoGroup, this the group's active stack. \sa setStack() setGroup() */ KUndo2QStack *KUndo2View::stack() const { return d->model->stack(); } /*! Sets the stack displayed by this view to \a stack. If \a stack is 0, the view will be empty. If the view was previously looking at a QUndoGroup, the group is set to 0. \sa stack() setGroup() */ void KUndo2View::setStack(KUndo2QStack *stack) { #ifndef QT_NO_UNDOGROUP setGroup(0); #endif d->model->setStack(stack); } #ifndef QT_NO_UNDOGROUP /*! Sets the group displayed by this view to \a group. If \a group is 0, the view will be empty. The view will update itself autmiatically whenever the active stack of the group changes. \sa group() setStack() */ void KUndo2View::setGroup(KUndo2Group *group) { if (d->group == group) return; if (d->group != 0) { disconnect(d->group, SIGNAL(activeStackChanged(KUndo2QStack*)), d->model, SLOT(setStack(KUndo2QStack*))); } d->group = group; if (d->group != 0) { connect(d->group, SIGNAL(activeStackChanged(KUndo2QStack*)), d->model, SLOT(setStack(KUndo2QStack*))); d->model->setStack((KUndo2QStack *)d->group->activeStack()); } else { d->model->setStack(0); } } /*! Returns the group displayed by this view. If the view is not looking at group, this function returns 0. \sa setGroup() setStack() */ KUndo2Group *KUndo2View::group() const { return d->group; } #endif // QT_NO_UNDOGROUP /*! \property KUndo2View::emptyLabel \brief the label used for the empty state. The empty label is the topmost element in the list of commands, which represents the state of the document before any commands were pushed on the stack. The default is the string "". */ void KUndo2View::setEmptyLabel(const QString &label) { - + bool changed = d->model->emptyLabel() != label; d->model->setEmptyLabel(label); + if (changed) { + emit emptyLabelChanged(); + } } QString KUndo2View::emptyLabel() const { return d->model->emptyLabel(); } /*! \property KUndo2View::cleanIcon \brief the icon used to represent the clean state. A stack may have a clean state set with KUndo2QStack::setClean(). This is usually the state of the document at the point it was saved. KUndo2View can display an icon in the list of commands to show the clean state. If this property is a null icon, no icon is shown. The default value is the null icon. */ void KUndo2View::setCleanIcon(const QIcon &icon) { - d->model->setCleanIcon(icon); - + emit cleanIconChanged(); } QIcon KUndo2View::cleanIcon() const { return d->model->cleanIcon(); } #endif // QT_NO_UNDOVIEW diff --git a/src/libs/kundo2/kundo2view.h b/src/libs/kundo2/kundo2view.h index 672cd634..d2b6dcb0 100644 --- a/src/libs/kundo2/kundo2view.h +++ b/src/libs/kundo2/kundo2view.h @@ -1,111 +1,115 @@ /* This file is part of the KDE project * Copyright (C) 2010 Matus Talcik * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /**************************************************************************** ** ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of the QtGui module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** No Commercial Usage ** This file contains pre-release code and may not be distributed. ** You may use this file in accordance with the terms and conditions ** contained in the Technology Preview License Agreement accompanying ** this package. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** If you have questions regarding the use of this file, please contact ** Nokia at qt-info@nokia.com. ** ** ** ** ** ** ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #ifndef KUNDO2VIEW_H #define KUNDO2VIEW_H #include #include #include "kundo2_export.h" #ifndef QT_NO_UNDOVIEW class KUndo2ViewPrivate; class KUndo2QStack; class KUndo2Group; class QIcon; class KUNDO2_EXPORT KUndo2View : public QListView { Q_OBJECT - Q_PROPERTY(QString emptyLabel READ emptyLabel WRITE setEmptyLabel) - Q_PROPERTY(QIcon cleanIcon READ cleanIcon WRITE setCleanIcon) + Q_PROPERTY(QString emptyLabel READ emptyLabel WRITE setEmptyLabel NOTIFY emptyLabelChanged) + Q_PROPERTY(QIcon cleanIcon READ cleanIcon WRITE setCleanIcon NOTIFY cleanIconChanged) public: explicit KUndo2View(QWidget *parent = 0); explicit KUndo2View(KUndo2QStack *stack, QWidget *parent = 0); #ifndef QT_NO_UNDOGROUP explicit KUndo2View(KUndo2Group *group, QWidget *parent = 0); #endif ~KUndo2View(); KUndo2QStack *stack() const; #ifndef QT_NO_UNDOGROUP KUndo2Group *group() const; #endif void setEmptyLabel(const QString &label); QString emptyLabel() const; void setCleanIcon(const QIcon &icon); QIcon cleanIcon() const; public Q_SLOTS: void setStack(KUndo2QStack *stack); #ifndef QT_NO_UNDOGROUP void setGroup(KUndo2Group *group); #endif +Q_SIGNALS: + void emptyLabelChanged(); + void cleanIconChanged(); + private: KUndo2ViewPrivate* const d; Q_DISABLE_COPY(KUndo2View) }; #endif // QT_NO_UNDOVIEW #endif // KUNDO2VIEW_H diff --git a/src/libs/main/KoDocument.cpp b/src/libs/main/KoDocument.cpp index 03ad374b..a81d75df 100644 --- a/src/libs/main/KoDocument.cpp +++ b/src/libs/main/KoDocument.cpp @@ -1,2686 +1,2689 @@ /* 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. */ #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() { // signal that the job is done setValue(-1); } int maximum() const { return 100; } void setValue(int value) { if (m_mainWindow) { m_mainWindow->slotProgress(value); } } void setRange(int /*minimum*/, int /*maximum*/) { } void setFormat(const QString &/*format*/) { } }; } //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*))); QObject::connect(m_job, SIGNAL(mimetype(KIO::Job*,QString)), document, SLOT(_k_slotGotMimeType(KIO::Job*,QString))); } // 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, SIGNAL(timeout()), this, SLOT(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, SIGNAL(indexChanged(int)), this, SLOT(slotUndoStackIndexChanged(int))); } 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, SLOT(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, SIGNAL(sigProgress(int)), d->parentPart->currentMainwindow(), SLOT(slotProgress(int))); 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, SIGNAL(sigProgress(int)), d->parentPart->currentMainwindow(), SLOT(slotProgress(int))); 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 { d->parentPart->addRecentURLToAllMainWindows(_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()); } } 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, SLOT(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, SLOT(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) { - d->backupFile = _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, SIGNAL(saveRequested()), mainwin, SLOT(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*)) ); 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/KoDocument.h b/src/libs/main/KoDocument.h index b8320681..082f0d8c 100644 --- a/src/libs/main/KoDocument.h +++ b/src/libs/main/KoDocument.h @@ -1,812 +1,814 @@ /* 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 KODOCUMENT_H #define KODOCUMENT_H #include #include #include "komain_export.h" #include #include #include class KUndo2Command; class KoPart; class KoStore; class KoDocumentInfo; //class KoDocumentRdf; //class KoDocumentRdfBase; class KoProgressUpdater; class KoProgressProxy; class KoDocumentInfoDlg; class KoUnit; //class KoGridData; //class KoGuidesData; class KoXmlWriter; class QDomDocument; // MSVC seems to need to know the declaration of the classes // we pass references of in, when used by external modules // e.g. // when building chartshapecore.lib, the forward-declaration // approach lead to unresolved externals warnings when it used // the pagelayout functions. // Also when building calligra_shape_formular.dll - FormulaDocument // referenced the same two pagelayout functions incorrectly. #if defined(_WIN32) || defined(_WIN64) #include #else struct KoPageLayout; #endif class KoVersionInfo { public: QDateTime date; QString saved_by; QString comment; QString title; QByteArray data; //the content of the compressed version }; /** * The %Calligra document class * * This class provides some functionality each %Calligra document should have. * * @short The %Calligra document class */ class KOMAIN_EXPORT KoDocument : public QObject, public KoDocumentBase { Q_OBJECT - Q_PROPERTY(bool backupFile READ backupFile WRITE setBackupFile) - Q_PROPERTY(int pageCount READ pageCount) + Q_PROPERTY(bool backupFile READ backupFile WRITE setBackupFile NOTIFY backupFileChanged) + Q_PROPERTY(int pageCount READ pageCount) // clazy:exclude=qproperty-without-notify public: /** * Constructor. * * @param parent The KoPart that owns the document. XXX: should be removed! * @param undoStack accepts the stack for the document. You can create any type of stack if you need. * The stack objects will become owned by the document. This is used by Krita's KisDoc2. The default value for this * parameter is a usual Qt's stack. */ explicit KoDocument(KoPart *parent, KUndo2Stack *undoStack = new KUndo2Stack()); /** * Destructor. * * The destructor does not delete any attached KoView objects and it does not * delete the attached widget as returned by widget(). */ virtual ~KoDocument(); /// XXX: Temporary! KoPart *documentPart() const; /** * Reimplemented from KoParts::ReadWritePart for internal reasons * (for the autosave functionality) */ virtual bool openUrl(const QUrl &url); /** * Opens the document given by @p url, without storing the URL * in the KoDocument. * Call this instead of openUrl() to implement KoMainWindow's * File --> Import feature. * * @note This will call openUrl(). To differentiate this from an ordinary * Open operation (in any reimplementation of openUrl() or openFile()) * call isImporting(). */ bool importDocument(const QUrl &url); /** * Saves the document as @p url without changing the state of the * KoDocument (URL, modified flag etc.). Call this instead of * KoParts::ReadWritePart::saveAs() to implement KoMainWindow's * File --> Export feature. * * @note This will call KoDocument::saveAs(). To differentiate this * from an ordinary Save operation (in any reimplementation of * saveFile()) call isExporting(). */ bool exportDocument(const QUrl &url); /** * @brief Sets whether the document can be edited or is read only. * * This recursively applied to all child documents and * KoView::updateReadWrite is called for every attached * view. */ virtual void setReadWrite(bool readwrite = true); /** * To be preferred when a document exists. It is fast when calling * it multiple times since it caches the result that readNativeFormatMimeType() * delivers. * This comes from the X-KDE-NativeMimeType key in the .desktop file. */ virtual QByteArray nativeFormatMimeType() const = 0; /** * Returns the OASIS OpenDocument mimetype of the document, if supported * This comes from the X-KDE-NativeOasisMimeType key in the * desktop file * * @return the oasis mimetype or, if it hasn't one, the nativeformatmimetype. */ virtual QByteArray nativeOasisMimeType() const = 0; /// Checks whether a given mimetype can be handled natively. bool isNativeFormat(const QByteArray& mimetype) const; /// Returns a list of the mimetypes considered "native", i.e. which can /// be saved by KoDocument without a filter, in *addition* to the main one virtual QStringList extraNativeMimeTypes() const = 0; /** * Return the set of SupportedSpecialFormats that the application wants to * offer in the "Save" file dialog. */ virtual int supportedSpecialFormats() const; /** * Returns the actual mimetype of the document */ QByteArray mimeType() const; /** * @brief Sets the mime type for the document. * * When choosing "save as" this is also the mime type * selected by default. */ void setMimeType(const QByteArray & mimeType); /** * @brief Set the format in which the document should be saved. * * This is called on loading, and in "save as", so you shouldn't * have to call it. * * @param mimeType the mime type (format) to use. * @param specialOutputFlag is for "save as older version" etc. */ void setOutputMimeType(const QByteArray & mimeType, int specialOutputFlag = 0); QByteArray outputMimeType() const; int specialOutputFlag() const; /** * Returns true if this document was the result of opening a foreign * file format and if the user hasn't yet saved the document (in any * format). * * Used by KoMainWindow to warn the user when s/he lazily presses * CTRL+S to save in the same foreign format, putting all his/her * formatting at risk (normally an export confirmation only comes up * with Save As). * * @param exporting specifies whether this is the setting for a * File --> Export or File --> Save/Save As operation. */ bool confirmNonNativeSave(const bool exporting) const; void setConfirmNonNativeSave(const bool exporting, const bool on); /** * @return true if saving/exporting should inhibit the option dialog */ bool saveInBatchMode() const; /** * @param batchMode if true, do not show the option dialog when saving or exporting. */ void setSaveInBatchMode(const bool batchMode); /** * Sets the error message to be shown to the user (use i18n()!) * when loading or saving fails. * If you asked the user about something and they chose "Cancel", * set the message to the magic string "USER_CANCELED", to skip the error dialog. */ void setErrorMessage(const QString& errMsg); /** * Return the last error message. Usually KoDocument takes care of * showing it; this method is mostly provided for non-interactive use. */ QString errorMessage() const; /** * Show the last error message in a message box. * The dialog box will mention a loading problem. * openUrl/openFile takes care of doing it, but not loadNativeFormat itself, * so this is often called after loadNativeFormat returned false. */ void showLoadingErrorDialog(); /** * @brief Generates a preview picture of the document * @note The preview is used in the File Dialog and also to create the Thumbnail */ virtual QPixmap generatePreview(const QSize& size); /** * Paints the data itself. * It's this method that %Calligra Parts have to implement. * * @param painter The painter object onto which will be drawn. * @param rect The rect that should be used in the painter object. */ virtual void paintContent(QPainter &painter, const QRect &rect) = 0; /** * Tells the document that its title has been modified, either because * the modified status changes (this is done by setModified() ) or * because the URL or the document-info's title changed. */ void setTitleModified(); /** * @return true if the document is empty. */ virtual bool isEmpty() const; /** * @brief Sets the document to empty. * * Used after loading a template * (which is not empty, but not the user's input). * * @see isEmpty() */ virtual void setEmpty(); /** * @brief Loads a document from a store. * * You should never have to reimplement. * * @param store The store to load from * @param url An internal url, like tar:/1/2 */ virtual bool loadFromStore(KoStore *store, const QString& url); /** * @brief Loads an OASIS document from a store. * This is used for both the main document and embedded objects. */ virtual bool loadOasisFromStore(KoStore *store); /** * @brief Saves a sub-document to a store. * * You should not have to reimplement this. */ virtual bool saveToStore(KoStore *store, const QString& path); /** * Reimplement this method to load the contents of your Calligra document, * from the XML document. This is for the pre-Oasis file format (maindoc.xml). */ virtual bool loadXML(const KoXmlDocument & doc, KoStore *store) = 0; /** * Reimplement this to save the contents of the %Calligra document into * a QDomDocument. The framework takes care of saving it to the store. */ virtual QDomDocument saveXML(); /** * Return a correctly created QDomDocument for this KoDocument, * including processing instruction, complete DOCTYPE tag (with systemId and publicId), and root element. * @param tagName the name of the tag for the root element * @param version the DTD version (usually the application's version). */ QDomDocument createDomDocument(const QString& tagName, const QString& version) const; /** * Return a correctly created QDomDocument for an old (1.3-style) %Calligra document, * including processing instruction, complete DOCTYPE tag (with systemId and publicId), and root element. * This static method can be used e.g. by filters. * @param appName the app's instance name, e.g. words, kspread, kpresenter etc. * @param tagName the name of the tag for the root element, e.g. DOC for words/kpresenter. * @param version the DTD version (usually the application's version). */ static QDomDocument createDomDocument(const QString& appName, const QString& tagName, const QString& version); /** * The first thing to do in loadOasis is get hold of the office:body tag, then its child. * If the child isn't the expected one, the error message can indicate what it is instead. * This method returns a translated name for the type of document, * e.g. i18n("Word Processing") for office:text. */ static QString tagNameToDocumentType(const QString& localName); /** * Loads a document in the native format from a given URL. * Reimplement if your native format isn't XML. * * @param file the file to load - usually KReadOnlyPart::m_file or the result of a filter */ virtual bool loadNativeFormat(const QString & file); /** * Saves the document in native format, to a given file * You should never have to reimplement. * Made public for writing templates. */ virtual bool saveNativeFormat(const QString & file); /** * Saves the document in native ODF format to the given store. */ bool saveNativeFormatODF(KoStore *store, const QByteArray &mimeType); /** * Saves the document in the native format to the given store. */ bool saveNativeFormatCalligra(KoStore *store); /** * Activate/deactivate/configure the autosave feature. * @param delay in seconds, 0 to disable */ void setAutoSave(int delay); /** * Checks whether the document is currently in the process of autosaving */ bool isAutosaving() const; /** * Set whether the next openUrl call should check for an auto-saved file * and offer to open it. This is usually true, but can be turned off * (e.g. for the preview module). This only checks for names auto-saved * files, unnamed auto-saved files are only checked on KoApplication startup. */ void setCheckAutoSaveFile(bool b); /** * Set whether the next openUrl call should show error message boxes in case * of errors. This is usually the case, but e.g. not when generating thumbnail * previews. */ void setAutoErrorHandlingEnabled(bool b); /** * Checks whether error message boxes should be shown. */ bool isAutoErrorHandlingEnabled() const; /** * Retrieve the default value for autosave in seconds. * Called by the applications to use the correct default in their config */ static int defaultAutoSave(); /** * @return the information concerning this document. * @see KoDocumentInfo */ KoDocumentInfo *documentInfo() const; /** * @return the Rdf metadata for this document. * This method should only be used by code that links to * the RDF system and needs full access to the KoDocumentRdf object. * @see KoDocumentRdf */ // KoDocumentRdfBase *documentRdf() const; /** * Replace the current rdf document with the given rdf document. The existing RDF document * will be deleted, and if RDF support is compiled out, KoDocument does not take ownership. * Otherwise, KoDocument will own the rdf document. */ //void setDocumentRdf(KoDocumentRdfBase *rdfDocument); /** * @return the object to report progress to. * One can add more KoUpdaters to it to make the progress reporting more * accurate. If no active progress reporter is present, 0 is returned. **/ KoProgressUpdater *progressUpdater() const; /** * Set a custom progress proxy to use to report loading * progress to. */ void setProgressProxy(KoProgressProxy *progressProxy); KoProgressProxy* progressProxy() const; /** * Return true if url() is a real filename, false if url() is * an internal url in the store, like "tar:/..." */ virtual bool isStoredExtern() const; /** * @return the page layout associated with this document (margins, pageSize, etc). * Override this if you want to provide different sized pages. * * @see KoPageLayout */ virtual KoPageLayout pageLayout(int pageNumber = 0) const; virtual void setPageLayout(const KoPageLayout &pageLayout); /** * Performs a cleanup of unneeded backup files */ void removeAutoSaveFiles(); void setBackupFile(bool _b); bool backupFile()const; /** * Returns true if this document or any of its internal child documents are modified. */ Q_INVOKABLE bool isModified() const; /** * Returns true during loading (openUrl can be asynchronous) */ bool isLoading() const; int queryCloseDia(); /** * Sets the backup path of the document */ void setBackupPath(const QString & _path); /** * @return path to the backup document */ QString backupPath()const; /** * @return caption of the document * * Caption is of the form "[title] - [url]", * built out of the document info (title) and pretty-printed * document URL. * If the title is not present, only the URL it returned. */ QString caption() const; /** * Sets the document URL to empty URL * KParts doesn't allow this, but %Calligra apps have e.g. templates * After using loadNativeFormat on a template, one wants * to set the url to QUrl() */ void resetURL(); /** * Set when you want an external embedded document to be stored internally */ void setStoreInternal(bool i); /** * @return true when external embedded documents are stored internally */ bool storeInternal() const; bool hasExternURL() const; /** * @internal (public for KoMainWindow) */ void setMimeTypeAfterLoading(const QString& mimeType); /** * @return returns the number of pages in the document. */ virtual int pageCount() const; /** * Returns the unit used to display all measures/distances. */ KoUnit unit() const; /** * Sets the unit used to display all measures/distances. */ void setUnit(const KoUnit &unit); /** * Save the unit to the settings writer * * @param settingsWriter */ void saveUnitOdf(KoXmlWriter *settingsWriter) const; QList &versionList(); bool loadNativeFormatFromStore(QByteArray &data); /** * Adds a new version and then saves the whole document. * @param comment the comment for the version * @return true on success, otherwise false */ bool addVersion(const QString& comment); /// return the grid data for this document. // KoGridData &gridData(); /// returns the guides data for this document. // KoGuidesData &guidesData(); void clearUndoHistory(); /** * Sets the modified flag on the document. This means that it has * to be saved or not before deleting it. */ Q_INVOKABLE virtual void setModified(bool _mod); /** * Initialize an empty document using default values */ virtual void initEmpty(); /** * Returns the global undo stack */ KUndo2Stack *undoStack(); /** * Set the output stream to report profile information to. */ void setProfileStream(QTextStream *profilestream); /** * Set the output stream to report profile information to. */ void setProfileReferenceTime(const QTime& referenceTime); /// If set, the document shall be saved even if it is not marked as modified. /// @see setAlwaysAllowSaving() bool alwaysAllowSaving() const; /// Set alwaysAllowSaving to @p allow. /// Enables applications to always allow saving even when document is not modified. /// This makes it possible to save settings/context info without marking /// the document as modified. /// @see alwaysAllowSaving() void setAlwaysAllowSaving(bool allow); public Q_SLOTS: /** * Adds a command to the undo stack and executes it by calling the redo() function. * @param command command to add to the undo stack */ virtual void addCommand(KUndo2Command *command); /** * Begins recording of a macro command. At the end endMacro needs to be called. * @param text command description */ virtual void beginMacro(const KUndo2MagicString &text); /** * Ends the recording of a macro command. */ virtual void endMacro(); Q_SIGNALS: /** * This signal is emitted when the unit is changed by setUnit(). * It is common to connect views to it, in order to change the displayed units * (e.g. in the rulers) */ void unitChanged(const KoUnit &unit); /** * Progress info while loading or saving. The value is in percents (i.e. a number between 0 and 100) * Your KoDocument-derived class should emit the signal now and then during load/save. * KoMainWindow will take care of displaying a progress bar automatically. */ void sigProgress(int value); /** * Emitted e.g. at the beginning of a save operation * This is emitted by KoDocument and used by KoView to display a statusbar message */ void statusBarMessage(const QString& text); /** * Emitted e.g. at the end of a save operation * This is emitted by KoDocument and used by KoView to clear the statusbar message */ void clearStatusBarMessage(); /** * Emitted when the document is modified */ void modified(bool); void titleModified(const QString &caption, bool isModified); + void backupFileChanged(bool); + protected: friend class KoPart; /** * Generate a name for the document. */ QString newObjectName(); QString autoSaveFile(const QString & path) const; void setDisregardAutosaveFailure(bool disregardFailure); /** * Loads a document from KReadOnlyPart::m_file (KParts takes care of downloading * remote documents). * Applies a filter if necessary, and calls loadNativeFormat in any case * You should not have to reimplement, except for very special cases. * * NOTE: this method also creates a new KoView instance! * * This method is called from the KReadOnlyPart::openUrl method. */ virtual bool openFile(); /** * This method is called by @a openFile() to allow applications to setup there * own KoProgressUpdater-subTasks which are then taken into account for the * displayed progressbar during loading. */ virtual void setupOpenFileSubProgress(); /** * Saves a document to KReadOnlyPart::m_file (KParts takes care of uploading * remote documents) * Applies a filter if necessary, and calls saveNativeFormat in any case * You should not have to reimplement, except for very special cases. */ virtual bool saveFile(); /** * Overload this function if you have to load additional files * from a store. This function is called after loadXML() * and after loadChildren() have been called. */ virtual bool completeLoading(KoStore *store); /** * If you want to write additional files to a store, * then you must do it here. * In the implementation, you should prepend the document * url (using url().url()) before the filename, so that everything is kept relative * to this document. For instance it will produce urls such as * tar:/1/pictures/picture0.png, if the doc url is tar:/1 * But do this ONLY if the document is not stored extern (see isStoredExtern() ). * If it is, then the pictures should be saved to tar:/pictures. */ virtual bool completeSaving(KoStore *store); /** @internal */ virtual void setModified(); /** * Returns whether or not the current openUrl() or openFile() call is * actually an import operation (like File --> Import). * This is for informational purposes only. */ bool isImporting() const; /** * Returns whether or not the current saveFile() call is actually an export * operation (like File --> Export). * If this function returns true during saveFile() and you are changing * some sort of state, you _must_ restore it before the end of saveFile(); * otherwise, File --> Export will not work properly. */ bool isExporting() const; public: QString localFilePath() const; void setLocalFilePath( const QString &localFilePath ); virtual KoDocumentInfoDlg* createDocumentInfoDialog(QWidget *parent, KoDocumentInfo *docInfo) const; bool isReadWrite() const; QUrl url() const; void setUrl(const QUrl &url); virtual bool closeUrl(bool promptToSave = true); virtual bool saveAs( const QUrl &url ); public Q_SLOTS: virtual bool save(); bool waitSaveComplete(); Q_SIGNALS: void completed(); void canceled(const QString &); private Q_SLOTS: void slotAutoSave(); /// Called by the undo stack when undo or redo is called void slotUndoStackIndexChanged(int idx); protected: bool oldLoadAndParse(KoStore *store, const QString& filename, KoXmlDocument& doc); private: bool saveToStream(QIODevice *dev); QString checkImageMimeTypes(const QString &mimeType, const QUrl &url) const; bool loadNativeFormatFromStore(const QString& file); bool loadNativeFormatFromStoreInternal(KoStore *store); bool savePreview(KoStore *store); bool saveOasisPreview(KoStore *store, KoXmlWriter *manifestWriter); QString prettyPathOrUrl() const; bool queryClose(); bool saveToUrl(); bool openUrlInternal(const QUrl &url); void abortLoad(); class Private; Private *const d; Q_PRIVATE_SLOT(d, void _k_slotJobFinished( KJob * job )) Q_PRIVATE_SLOT(d, void _k_slotStatJobFinished(KJob*)) Q_PRIVATE_SLOT(d, void _k_slotGotMimeType(KIO::Job *job, const QString &mime)) Q_PRIVATE_SLOT(d, void _k_slotUploadFinished( KJob * job )) }; Q_DECLARE_METATYPE(KoDocument*) #endif diff --git a/src/libs/models/kcalendar/kdatepicker.h b/src/libs/models/kcalendar/kdatepicker.h index 124a68ab..a94bda2b 100644 --- a/src/libs/models/kcalendar/kdatepicker.h +++ b/src/libs/models/kcalendar/kdatepicker.h @@ -1,187 +1,187 @@ /* -*- C++ -*- This file is part of the KDE libraries Copyright (C) 1997 Tim D. Gilman (tdgilman@best.org) (C) 1998-2001 Mirko Boehm (mirko@kde.org) (C) 2007 John Layt 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 KP_KDATEPICKER_H #define KP_KDATEPICKER_H #include "planmodels_export.h" #include #include namespace KPlato { class KDateTable; /** * @short A date selection widget. * * Provides a widget for calendar date input. * * Different from the * previous versions, it now emits two types of signals, either * dateSelected() or dateEntered() (see documentation for both * signals). * * A line edit has been added in the newer versions to allow the user * to select a date directly by entering numbers like 19990101 * or 990101. * * \image html kdatepicker.png "KDE Date Widget" * * @author Tim Gilman, Mirko Boehm * **/ class PLANMODELS_EXPORT KDatePicker: public QFrame { Q_OBJECT Q_PROPERTY(QDate date READ date WRITE setDate NOTIFY dateChanged USER true) - Q_PROPERTY(bool closeButton READ hasCloseButton WRITE setCloseButton) - Q_PROPERTY(int fontSize READ fontSize WRITE setFontSize) + Q_PROPERTY(bool closeButton READ hasCloseButton WRITE setCloseButton) // clazy:exclude=qproperty-without-notify + Q_PROPERTY(int fontSize READ fontSize WRITE setFontSize) // clazy:exclude=qproperty-without-notify public: /** * The constructor. The current date will be displayed initially. **/ explicit KDatePicker(QWidget *parent = 0); /** * The constructor. The given date will be displayed initially. **/ explicit KDatePicker(const QDate &dt, QWidget *parent = 0); /** * The destructor. **/ virtual ~KDatePicker(); /** The size hint for date pickers. The size hint recommends the * minimum size of the widget so that all elements may be placed * without clipping. This sometimes looks ugly, so when using the * size hint, try adding 28 to each of the reported numbers of * pixels. **/ QSize sizeHint() const Q_DECL_OVERRIDE; /** * Sets the date. * * @returns @p false and does not change anything if the date given is invalid. **/ bool setDate(const QDate &date); /** * @returns the selected date. */ const QDate &date() const; /** * @returns the KDateTable widget child of this KDatePicker * widget. */ KDateTable *dateTable() const; /** * Sets the font size of the widgets elements. **/ void setFontSize(int); /** * Returns the font size of the widget elements. */ int fontSize() const; /** * By calling this method with @p enable = true, KDatePicker will show * a little close-button in the upper button-row. Clicking the * close-button will cause the KDatePicker's topLevelWidget()'s close() * method being called. This is mostly useful for toplevel datepickers * without a window manager decoration. * @see hasCloseButton */ void setCloseButton(bool enable); /** * @returns true if a KDatePicker shows a close-button. * @see setCloseButton */ bool hasCloseButton() const; protected: /// to catch move keyEvents when QLineEdit has keyFocus bool eventFilter(QObject *o, QEvent *e) Q_DECL_OVERRIDE; /// the resize event void resizeEvent(QResizeEvent *) Q_DECL_OVERRIDE; void changeEvent(QEvent *event) Q_DECL_OVERRIDE; protected Q_SLOTS: void dateChangedSlot(const QDate &date); void tableClickedSlot(); void monthForwardClicked(); void monthBackwardClicked(); void yearForwardClicked(); void yearBackwardClicked(); void selectMonthClicked(); void selectYearClicked(); void uncheckYearSelector(); void lineEnterPressed(); void todayButtonClicked(); void weekSelected(int); Q_SIGNALS: /** This signal is emitted each time the selected date is changed. * Usually, this does not mean that the date has been entered, * since the date also changes, for example, when another month is * selected. * @see dateSelected */ void dateChanged(const QDate &date); /** This signal is emitted each time a day has been selected by * clicking on the table (hitting a day in the current month). It * has the same meaning as dateSelected() in older versions of * KDatePicker. */ void dateSelected(const QDate &date); /** This signal is emitted when enter is pressed and a VALID date * has been entered before into the line edit. Connect to both * dateEntered() and dateSelected() to receive all events where the * user really enters a date. */ void dateEntered(const QDate &date); /** This signal is emitted when the day has been selected by * clicking on it in the table. */ void tableClicked(); private: void initWidget(const QDate &date); class KDatePickerPrivate; friend class KDatePickerPrivate; KDatePickerPrivate *const d; }; } //namespace KPlato #endif // KP_KDATEPICKER_H diff --git a/src/libs/models/kcalendar/kdatetable.cpp b/src/libs/models/kcalendar/kdatetable.cpp index 33463a84..dbfb4d75 100644 --- a/src/libs/models/kcalendar/kdatetable.cpp +++ b/src/libs/models/kcalendar/kdatetable.cpp @@ -1,1242 +1,1242 @@ /* -*- C++ -*- This file is part of the KDE libraries Copyright (C) 1997 Tim D. Gilman (tdgilman@best.org) (C) 1998-2001 Mirko Boehm (mirko@kde.org) (C) 2007 John Layt 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 "kdatetable.h" #include "kdatepicker.h" #include "kptdebug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace KPlato { class KDateTable::KDateTablePrivate { public: KDateTablePrivate(KDateTable *q): q(q) { m_popupMenuEnabled = false; m_selectionmode = KDateTable::SingleSelection; m_paintweeknumbers = false; m_hoveredPos = -1; m_model = 0; m_grid = false; } ~KDateTablePrivate() { qDeleteAll(customPaintingModes); delete m_dateDelegate; delete m_weekDayDelegate; delete m_weekNumberDelegate; } void nextMonth(); void previousMonth(); void beginningOfMonth(); void endOfMonth(); void beginningOfWeek(); void endOfWeek(); KDateTable *q; /** * The currently selected date. */ QDate m_date; /** * The weekday number of the first day in the month [1..7]. */ int m_weekDayFirstOfMonth; /** * Save the size of the largest used cell content. */ QRectF m_maxCell; /** * The font size of the displayed text. */ int fontsize; bool m_popupMenuEnabled; //-----> QHash customPaintingModes; int m_hoveredPos; KDateTableDataModel *m_model; KDateTableDateDelegate *m_dateDelegate; KDateTableWeekDayDelegate *m_weekDayDelegate; KDateTableWeekNumberDelegate *m_weekNumberDelegate; StyleOptionViewItem m_styleOptionDate; StyleOptionHeader m_styleOptionWeekDay; StyleOptionHeader m_styleOptionWeekNumber; QList m_selectedDates; SelectionMode m_selectionmode; bool m_paintweeknumbers; bool m_grid; }; KDateTable::KDateTable(const QDate& date, QWidget* parent) : QWidget(parent), d(new KDateTablePrivate(this)) { if (!date.isValid()) { debugPlan << "KDateTable ctor: WARNING: Given date is invalid, using current date."; initWidget(QDate::currentDate()); // this initializes m_weekDayFirstOfMonth, m_numDaysThisMonth, numDaysPrevMonth } else { initWidget(date); // this initializes m_weekDayFirstOfMonth, m_numDaysThisMonth, numDaysPrevMonth } } KDateTable::KDateTable(QWidget *parent) : QWidget(parent), d(new KDateTablePrivate(this)) { initWidget(QDate::currentDate()); } KDateTable::~KDateTable() { delete d; } void KDateTable::initWidget(const QDate &date) { setFontSize(10); setFocusPolicy(Qt::StrongFocus); setBackgroundRole(QPalette::Base); setAutoFillBackground(true); initAccels(); setAttribute(Qt::WA_Hover, true); d->m_dateDelegate = new KDateTableDateDelegate( this ); d->m_weekDayDelegate = new KDateTableWeekDayDelegate( this ); d->m_weekNumberDelegate = new KDateTableWeekNumberDelegate( this ); d->m_styleOptionDate.initFrom( this ); d->m_styleOptionDate.displayAlignment = Qt::AlignCenter; d->m_styleOptionWeekDay.initFrom( this ); d->m_styleOptionWeekDay.textAlignment = Qt::AlignCenter; d->m_styleOptionWeekNumber.initFrom( this ); d->m_styleOptionWeekNumber.textAlignment = Qt::AlignCenter; //setModel( new KDateTableDataModel( this ) ); setDate(date); } void KDateTable::setStyleOptionDate( const StyleOptionViewItem &so ) { d->m_styleOptionDate = so; } void KDateTable::setStyleOptionWeekDay( const StyleOptionHeader &so ) { d->m_styleOptionWeekDay = so; } void KDateTable::setStyleOptionWeekNumber( const StyleOptionHeader &so ) { d->m_styleOptionWeekNumber = so; } void KDateTable::slotReset() { update(); } void KDateTable::slotDataChanged( const QDate &start, const QDate &end ) { Q_UNUSED(start); Q_UNUSED(end); update(); } void KDateTable::setModel( KDateTableDataModel *model ) { if ( d->m_model ) { disconnect( d->m_model, SIGNAL( reset() ), this, SLOT( slotReset() ) ); } d->m_model = model; if ( d->m_model ) { connect( d->m_model, SIGNAL( reset() ), this, SLOT( slotReset() ) ); } update(); } KDateTableDataModel *KDateTable::model() const { return d->m_model; } void KDateTable::setDateDelegate( KDateTableDateDelegate *delegate ) { delete d->m_dateDelegate; d->m_dateDelegate = delegate; } void KDateTable::setDateDelegate( const QDate &date, KDateTableDateDelegate *delegate ) { delete d->customPaintingModes.take(date.toJulianDay()); d->customPaintingModes.insert(date.toJulianDay(), delegate); } void KDateTable::setWeekDayDelegate( KDateTableWeekDayDelegate *delegate ) { delete d->m_weekDayDelegate; d->m_weekDayDelegate = delegate; } void KDateTable::setWeekNumberDelegate( KDateTableWeekNumberDelegate *delegate ) { delete d->m_weekNumberDelegate; d->m_weekNumberDelegate = delegate; } void KDateTable::setWeekNumbersEnabled( bool enable ) { d->m_paintweeknumbers = enable; } void KDateTable::setGridEnabled( bool enable ) { d->m_grid = enable; } void KDateTable::initAccels() { KActionCollection* localCollection = new KActionCollection(this); localCollection->addAssociatedWidget(this); QAction* next = localCollection->addAction(QLatin1String("next")); next->setShortcuts(KStandardShortcut::next()); connect(next, SIGNAL(triggered(bool)), SLOT(nextMonth())); QAction* prior = localCollection->addAction(QLatin1String("prior")); prior->setShortcuts(KStandardShortcut::prior()); connect(prior, SIGNAL(triggered(bool)), SLOT(previousMonth())); QAction* beginMonth = localCollection->addAction(QLatin1String("beginMonth")); beginMonth->setShortcuts(KStandardShortcut::home()); connect(beginMonth, SIGNAL(triggered(bool)), SLOT(beginningOfMonth())); QAction* endMonth = localCollection->addAction(QLatin1String("endMonth")); endMonth->setShortcuts(KStandardShortcut::end()); connect(endMonth, SIGNAL(triggered(bool)), SLOT(endOfMonth())); QAction* beginWeek = localCollection->addAction(QLatin1String("beginWeek")); beginWeek->setShortcuts(KStandardShortcut::beginningOfLine()); connect(beginWeek, SIGNAL(triggered(bool)), SLOT(beginningOfWeek())); QAction* endWeek = localCollection->addAction("endWeek"); endWeek->setShortcuts(KStandardShortcut::endOfLine()); connect(endWeek, SIGNAL(triggered(bool)), SLOT(endOfWeek())); localCollection->readSettings(); } int KDateTable::posFromDate( const QDate &date ) { int initialPosition = date.day(); int offset = (d->m_weekDayFirstOfMonth - QLocale().firstDayOfWeek() + 7) % 7; // make sure at least one day of the previous month is visible. // adjust this <1 if more days should be forced visible: if ( offset < 1 ) { offset += 7; } return initialPosition + offset; } QDate KDateTable::dateFromPos( int position ) { int offset = (d->m_weekDayFirstOfMonth - QLocale().firstDayOfWeek() + 7) % 7; // make sure at least one day of the previous month is visible. // adjust this <1 if more days should be forced visible: if ( offset < 1 ) { offset += 7; } return QDate(d->m_date.year(), d->m_date.month(), 1).addDays(position - offset); } void KDateTable::paintEvent(QPaintEvent *e) { QPainter p(this); const QRect &rectToUpdate = e->rect(); double cellWidth = width() / ( d->m_paintweeknumbers ? 8.0 : 7.0 ); double cellHeight = height() / 7.0; int leftCol = (int)std::floor(rectToUpdate.left() / cellWidth); int topRow = (int)std::floor(rectToUpdate.top() / cellHeight); int rightCol = (int)std::ceil(rectToUpdate.right() / cellWidth); int bottomRow = (int)std::ceil(rectToUpdate.bottom() / cellHeight); bottomRow = qMin(bottomRow, 7 - 1); rightCol = qMin(rightCol, ( d->m_paintweeknumbers ? 8 : 7 ) - 1); if (layoutDirection() == Qt::RightToLeft) { p.translate((( d->m_paintweeknumbers ? 8 : 7 ) - leftCol - 1) * cellWidth, topRow * cellHeight); } else { p.translate(leftCol * cellWidth, topRow * cellHeight); } for (int i = leftCol; i <= rightCol; ++i) { for (int j = topRow; j <= bottomRow; ++j) { paintCell(&p, j, i); p.translate(0, cellHeight); } if (layoutDirection() == Qt::RightToLeft) { p.translate(-cellWidth, 0); } else { p.translate(cellWidth, 0); } p.translate(0, -cellHeight * (bottomRow - topRow + 1)); } } void KDateTable::paintCell(QPainter *painter, int row, int column) { //debugPlan; double w = (width() / ( d->m_paintweeknumbers ? 8.0 : 7.0 )) - 1; double h = (height() / 7.0) - 1; QRectF rect( 0, 0, w, h ); QSizeF cell; if ( row == 0 ) { if (column == 0 && d->m_paintweeknumbers ) { // paint something in the corner?? /* painter->setPen(palette().color(QPalette::Text)); painter->drawRect( rect );*/ return; } // we are drawing the headline (weekdays) d->m_styleOptionWeekDay.rectF = rect; d->m_styleOptionWeekDay.state = QStyle::State_None; int col = d->m_paintweeknumbers ? column - 1 : column; int day = col + QLocale().firstDayOfWeek(); if (day >= 8 ) { day -= 7; } if ( d->m_weekDayDelegate ) { cell = d->m_weekDayDelegate->paint( painter, d->m_styleOptionWeekDay, day, d->m_model ).size(); } } else { if ( d->m_paintweeknumbers && column == 0 ) { d->m_styleOptionWeekNumber.rectF = rect; d->m_styleOptionWeekNumber.state = QStyle::State_None; int pos = 7 * (row-1); QDate pCellDate = dateFromPos( pos ); if ( d->m_weekNumberDelegate ) { cell = d->m_weekNumberDelegate->paint( painter, d->m_styleOptionWeekNumber, pCellDate.weekNumber(), d->m_model ).size(); } } else { // draw the dates int col = d->m_paintweeknumbers ? column - 1 : column; int pos = 7 * (row-1) + col; if ( d->m_grid ) { painter->save(); // TODO: do not hardcode color! QPen pen( "lightgrey" ); pen.setWidthF( 0.5 ); painter->setPen( pen ); double pw = painter->pen().width(); if ( col > 0 ) { painter->drawLine( rect.topLeft(), rect.bottomLeft() ); } if ( row > 1 ) { painter->drawLine( rect.topLeft(), rect.topRight() ); } rect = rect.adjusted(pw, pw, 0, 0 ); painter->restore(); //debugPlan<m_grid<<" "<m_styleOptionDate.rectF = rect; d->m_styleOptionDate.state = QStyle::State_None; QDate pCellDate = dateFromPos( pos ); if( pCellDate.month() == d->m_date.month() ) { d->m_styleOptionDate.state |= QStyle::State_Active; } if ( d->m_selectedDates.contains( pCellDate ) ) { d->m_styleOptionDate.state |= QStyle::State_Selected; } if ( isEnabled() ) { d->m_styleOptionDate.state |= QStyle::State_Enabled; if (pos == d->m_hoveredPos) { d->m_styleOptionDate.state |= QStyle::State_MouseOver; } } if ( pCellDate == d->m_date ) { d->m_styleOptionDate.state |= QStyle::State_Active; if ( d->m_selectionmode != SingleSelection && hasFocus() ) { d->m_styleOptionDate.state |= QStyle::State_HasFocus; } } KDateTableDateDelegate *del = d->customPaintingModes.value( pCellDate.toJulianDay() ); if ( del == 0 ) { del = d->m_dateDelegate; } if ( del ) { //debugPlan<paint( painter, d->m_styleOptionDate, pCellDate, d->m_model ).size(); } else { warnPlan<<"No delegate!"; } } } // If the day cell we just drew is bigger than the current max cell sizes, // then adjust the max to the current cell if (cell.width() > d->m_maxCell.width()) { d->m_maxCell.setWidth(cell.width()); } if (cell.height() > d->m_maxCell.height()) { d->m_maxCell.setHeight(cell.height()); } } void KDateTable::KDateTablePrivate::nextMonth() { // setDate does validity checking for us q->setDate(m_date.addMonths(1)); } void KDateTable::KDateTablePrivate::previousMonth() { // setDate does validity checking for us q->setDate(m_date.addMonths(-1)); } void KDateTable::KDateTablePrivate::beginningOfMonth() { // setDate does validity checking for us q->setDate(QDate(m_date.year(), m_date.month(), 1)); } void KDateTable::KDateTablePrivate::endOfMonth() { // setDate does validity checking for us q->setDate(QDate(m_date.year(), m_date.month() + 1, 0)); } void KDateTable::KDateTablePrivate::beginningOfWeek() { // setDate does validity checking for us q->setDate(m_date.addDays(1 - m_date.dayOfWeek())); } void KDateTable::KDateTablePrivate::endOfWeek() { // setDate does validity checking for us q->setDate(m_date.addDays(7 - m_date.dayOfWeek())); } void KDateTable::keyPressEvent( QKeyEvent *e ) { QDate cd = d->m_date; switch( e->key() ) { case Qt::Key_Up: // setDate does validity checking for us setDate(d->m_date.addDays(-7)); break; case Qt::Key_Down: // setDate does validity checking for us setDate(d->m_date.addDays(7)); break; case Qt::Key_Left: // setDate does validity checking for us setDate(d->m_date.addDays(-1)); break; case Qt::Key_Right: // setDate does validity checking for us setDate(d->m_date.addDays(1)); break; case Qt::Key_Minus: // setDate does validity checking for us setDate(d->m_date.addDays(-1)); break; case Qt::Key_Plus: // setDate does validity checking for us setDate(d->m_date.addDays(1)); break; case Qt::Key_N: // setDate does validity checking for us setDate(QDate::currentDate()); break; case Qt::Key_Return: case Qt::Key_Enter: emit tableClicked(); break; case Qt::Key_Control: case Qt::Key_Alt: case Qt::Key_Meta: case Qt::Key_Shift: // Don't beep for modifiers break; default: if (!e->modifiers()) { // hm KNotification::beep(); } } switch( e->key() ) { case Qt::Key_Down: case Qt::Key_Up: case Qt::Key_Left: case Qt::Key_Right: case Qt::Key_Minus: case Qt::Key_Plus: { if ( d->m_selectionmode == ExtendedSelection ) { if ( e->modifiers() & Qt::ShiftModifier ) { int inc = cd > d->m_date ? 1 : -1; for ( QDate dd = d->m_date; dd != cd; dd = dd.addDays( inc ) ) { if ( ! d->m_selectedDates.contains( dd ) ) { d->m_selectedDates << dd; } } } else if ( e->modifiers() & Qt::ControlModifier ) { // keep selection, just move on } else { d->m_selectedDates.clear(); } } break;} case Qt::Key_Space: case Qt::Key_Select: if ( d->m_selectionmode == ExtendedSelection ) { if ( e->modifiers() & Qt::ControlModifier ) { if ( d->m_selectedDates.contains( d->m_date ) ) { d->m_selectedDates.removeAt( d->m_selectedDates.indexOf( d->m_date ) ); } else { d->m_selectedDates << d->m_date; } } else if ( ! d->m_selectedDates.contains( d->m_date ) ) { d->m_selectedDates << d->m_date; } update(); } break; case Qt::Key_Menu: if ( d->m_popupMenuEnabled ) { QMenu *menu = new QMenu(); if ( d->m_selectionmode == ExtendedSelection ) { emit aboutToShowContextMenu( menu, d->m_selectedDates ); } else { menu->setTitle( QLocale().toString(d->m_date, QLocale::ShortFormat) ); emit aboutToShowContextMenu( menu, d->m_date ); } if ( menu->isEmpty() ) { delete menu; } else { int p = posFromDate( d->m_date ) - 1; int col = p % 7; int row = p / 7; QPoint pos = geometry().topLeft(); QSize size = geometry().size(); int sx = size.width() / 8; int sy = size.height() / 7; pos = QPoint( pos.x() + sx + sx / 2 + sx * col, pos.y() + sy + sy * row ); debugPlan<popup(mapToGlobal(pos)); } } break; } } void KDateTable::setFontSize(int size) { QFontMetricsF metrics(fontMetrics()); QRectF rect; // ----- store rectangles: d->fontsize = size; // ----- find largest day name: d->m_maxCell.setWidth(0); d->m_maxCell.setHeight(0); QLocale locale; for (int weekday = 1; weekday <= 7; ++weekday) { rect = metrics.boundingRect(locale.dayName(weekday, QLocale::ShortFormat)); d->m_maxCell.setWidth(qMax(d->m_maxCell.width(), rect.width())); d->m_maxCell.setHeight(qMax(d->m_maxCell.height(), rect.height())); } // ----- compare with a real wide number and add some space: rect = metrics.boundingRect(QStringLiteral("88")); d->m_maxCell.setWidth(qMax(d->m_maxCell.width() + 2, rect.width())); d->m_maxCell.setHeight(qMax(d->m_maxCell.height() + 4, rect.height())); } void KDateTable::wheelEvent ( QWheelEvent * e ) { setDate(d->m_date.addMonths( -(int)(e->delta()/120)) ); e->accept(); } bool KDateTable::event( QEvent *ev ) { switch (ev->type()) { case QEvent::HoverMove: { QHoverEvent *e = static_cast(ev); const int row = e->pos().y() * 7 / height(); int col; if (layoutDirection() == Qt::RightToLeft) { col = (d->m_paintweeknumbers ? 8 : 7) - (e->pos().x() * (d->m_paintweeknumbers ? 8 : 7) / width()) - 1; } else { col = e->pos().x() * (d->m_paintweeknumbers ? 8 : 7) / width(); } const int pos = row < 1 ? -1 : ((d->m_paintweeknumbers ? 8 : 7) * (row - 1)) + col; if (pos != d->m_hoveredPos) { d->m_hoveredPos = pos; update(); } break; } case QEvent::HoverLeave: if (d->m_hoveredPos != -1) { d->m_hoveredPos = -1; update(); } break; case QEvent::ToolTip: { //debugPlan<<"Tooltip"; QHelpEvent *e = static_cast( ev ); double cellWidth = width() / ( d->m_paintweeknumbers ? 8.0 : 7.0 ); double cellHeight = height() / 7.0; int column = (int)std::floor(e->pos().x() / cellWidth); int row = (int)std::floor(e->pos().y() / cellHeight); QString text; if ( row == 0 ) { if (column == 0 && d->m_paintweeknumbers ) { // corner } else { // we are drawing the headline (weekdays) int col = d->m_paintweeknumbers ? column - 1 : column; int day = col + QLocale().firstDayOfWeek(); if (day >= 8 ) { day -= 7; } if ( d->m_weekDayDelegate ) { text = d->m_weekDayDelegate->data( day, Qt::ToolTipRole, d->m_model ).toString(); } } } else { if ( d->m_paintweeknumbers && column == 0 ) { int pos = 7 * (row-1); QDate pCellDate = dateFromPos( pos ); if ( d->m_weekNumberDelegate ) { text = d->m_weekNumberDelegate->data( pCellDate.weekNumber(), Qt::ToolTipRole, d->m_model ).toString(); } } else { // draw the dates int col = d->m_paintweeknumbers ? column - 1 : column; int pos=7*(row-1)+col; QDate pCellDate = dateFromPos( pos ); if ( d->m_dateDelegate ) { text = d->m_dateDelegate->data( pCellDate, Qt::ToolTipRole, d->m_model ).toString(); } } } //debugPlan<globalPos(), text ); } e->accept(); return true; break; } default: break; } return QWidget::event(ev); } void KDateTable::mousePressEvent(QMouseEvent *e) { if(e->type()!=QEvent::MouseButtonPress) { // the KDatePicker only reacts on mouse press events: return; } if(!isEnabled()) { KNotification::beep(); return; } int row, col, pos; QPoint mouseCoord = e->pos(); row = mouseCoord.y() * 7 / height(); if (layoutDirection() == Qt::RightToLeft) { col = ( d->m_paintweeknumbers ? 8 : 7 ) - (mouseCoord.x() * ( d->m_paintweeknumbers ? 8 : 7 ) / width()) - 1; } else { col = mouseCoord.x() * ( d->m_paintweeknumbers ? 8 : 7 ) / width(); } //debugPlan<m_maxCell<<", "<m_paintweeknumbers ) { --col; } if (row < 1 || col < 0) { // the user clicked on the frame of the table return; } // Rows and columns are zero indexed. The (row - 1) below is to avoid counting // the row with the days of the week in the calculation. // new position and date pos = (7 * (row - 1)) + col; QDate clickedDate = dateFromPos( pos ); if ( d->m_selectionmode != ExtendedSelection || e->button() != Qt::RightButton || ! d->m_selectedDates.contains( clickedDate ) ) { switch ( d->m_selectionmode ) { case SingleSelection: break; case ExtendedSelection: //debugPlan<<"extended "<modifiers()<<", "<modifiers() & Qt::ShiftModifier ) { if ( d->m_selectedDates.isEmpty() ) { d->m_selectedDates << clickedDate; } else if ( d->m_date != clickedDate ) { QDate dt = d->m_date; int nxt = dt < clickedDate ? 1 : -1; if ( d->m_selectedDates.contains( clickedDate ) ) { d->m_selectedDates.removeAt( d->m_selectedDates.indexOf( clickedDate ) ); } while ( dt != clickedDate ) { if ( ! d->m_selectedDates.contains( dt ) ) { d->m_selectedDates << dt; } dt = dt.addDays( nxt ); } d->m_selectedDates << clickedDate; } else { break; // selection not changed } } else if ( e->modifiers() & Qt::ControlModifier ) { if ( d->m_selectedDates.contains( clickedDate ) ) { d->m_selectedDates.removeAt( d->m_selectedDates.indexOf( clickedDate ) ); } else { d->m_selectedDates << clickedDate; } } else { d->m_selectedDates.clear(); d->m_selectedDates << clickedDate; } emit selectionChanged( d->m_selectedDates ); break; default: break; } // set the new date. If it is in the previous or next month, the month will // automatically be changed, no need to do that manually... // validity checking done inside setDate setDate( clickedDate ); // This could be optimized to only call update over the regions // of old and new cell, but 99% of times there is also a call to // setDate that already calls update() so no need to optimize that // much here update(); } emit tableClicked(); if (e->button() == Qt::RightButton && d->m_popupMenuEnabled ) { QMenu *menu = new QMenu(); if ( d->m_selectionmode == ExtendedSelection ) { emit aboutToShowContextMenu( menu, d->m_selectedDates ); } else { menu->setTitle( QLocale().toString(clickedDate, QLocale::ShortFormat) ); emit aboutToShowContextMenu( menu, clickedDate ); } menu->popup(e->globalPos()); } } bool KDateTable::setDate(const QDate& date_) { if (!date_.isValid()) { debugPlan << "KDateTable::setDate: refusing to set invalid date."; return false; } if (d->m_date != date_) { const QDate oldDate = d->m_date; d->m_date = date_; if (oldDate.year() != date_.year() || oldDate.month() != date_.month()) { QDate dt(date_.year(), date_.month(), 1); d->m_weekDayFirstOfMonth = dt.dayOfWeek(); } - emit(dateChanged(oldDate, date_)); - emit(dateChanged(date_)); + emit dateChanged(oldDate, date_); + emit dateChanged(date_); } if ( d->m_selectionmode == KDateTable::SingleSelection ) { d->m_selectedDates.clear(); d->m_selectedDates << date_; emit selectionChanged( d->m_selectedDates ); } update(); return true; } const QDate &KDateTable::date() const { return d->m_date; } void KDateTable::focusInEvent( QFocusEvent *e ) { QWidget::focusInEvent( e ); } void KDateTable::focusOutEvent( QFocusEvent *e ) { QWidget::focusOutEvent( e ); } QSize KDateTable::sizeHint() const { if(d->m_maxCell.height() > 0 && d->m_maxCell.width() > 0) { int s = d->m_paintweeknumbers ? 8 : 7; return QSize(qRound(d->m_maxCell.width() * s), (qRound(d->m_maxCell.height() + 2) * s)); } else { debugPlan << "KDateTable::sizeHint: obscure failure - "; return QSize(-1, -1); } } void KDateTable::setPopupMenuEnabled( bool enable ) { d->m_popupMenuEnabled=enable; } bool KDateTable::popupMenuEnabled() const { return d->m_popupMenuEnabled; } void KDateTable::setCustomDatePainting(const QDate &date, const QColor &fgColor, BackgroundMode bgMode, const QColor &bgColor) { KDateTableCustomDateDelegate *del = new KDateTableCustomDateDelegate(); del->fgColor = fgColor; del->bgMode = bgMode; del->bgColor = bgColor; setDateDelegate( date, del ); update(); } void KDateTable::unsetCustomDatePainting(const QDate &date) { d->customPaintingModes.remove(date.toJulianDay()); } void KDateTable::setSelectionMode( SelectionMode mode ) { d->m_selectionmode = mode; } //----------------------- KDateTableDataModel::KDateTableDataModel( QObject *parent ) : QObject( parent ) { } KDateTableDataModel::~KDateTableDataModel() { } QVariant KDateTableDataModel::data( const QDate &date, int role, int dataType ) const { Q_UNUSED(date); Q_UNUSED(role); Q_UNUSED(dataType); return QVariant(); } QVariant KDateTableDataModel::weekDayData( int day, int role ) const { Q_UNUSED(day); Q_UNUSED(role); return QVariant(); } QVariant KDateTableDataModel::weekNumberData( int week, int role ) const { Q_UNUSED(week); Q_UNUSED(role); return QVariant(); } //------------- KDateTableDateDelegate::KDateTableDateDelegate( QObject *parent ) : QObject( parent ) { } QVariant KDateTableDateDelegate::data( const QDate &date, int role, KDateTableDataModel *model ) { //debugPlan<data( date, role ); } QRectF KDateTableDateDelegate::paint( QPainter *painter, const StyleOptionViewItem &option, const QDate &date, KDateTableDataModel *model ) { //debugPlan<save(); QRectF r; QPalette palette = option.palette; if ( option.state & QStyle::State_Enabled && option.state & QStyle::State_Active ) { palette.setCurrentColorGroup( QPalette::Active ); } else { palette.setCurrentColorGroup( QPalette::Inactive ); } // TODO: honor QStyle::State_MouseOver, and perhaps switch to style()->drawPrimitive(QStyle::PE_PanelItemViewItem, ... QFont font = option.font; QColor textColor = palette.text().color(); QBrush bg( palette.base() ); Qt::Alignment align = option.displayAlignment; QString text = QLocale().toString(date.day()); if ( model ) { QVariant v = model->data( date, Qt::ForegroundRole ); if ( v.isValid() ) { textColor = v.value(); } v = model->data( date, Qt::BackgroundRole ); if ( v.isValid() ) { bg.setColor( v.value() ); } v = model->data( date ); if ( v.isValid() ) { text = v.toString(); } v = model->data( date, Qt::TextAlignmentRole ); if ( v.isValid() ) { align = (Qt::Alignment)v.toInt(); } v = model->data( date, Qt::FontRole ); if ( v.isValid() ) { font = v.value(); } } QPen pen = painter->pen(); pen.setColor( textColor ); if ( option.state & QStyle::State_Selected ) { bg = palette.highlight(); } painter->fillRect( option.rectF, bg ); painter->setBrush( bg ); if ( option.state & QStyle::State_HasFocus ) { painter->setPen( palette.text().color() ); painter->setPen( Qt::DotLine ); painter->drawRect( option.rectF ); } else if ( date == QDate::currentDate() ) { painter->setPen( palette.text().color() ); painter->drawRect( option.rectF ); } if ( option.state & QStyle::State_Selected ) { pen.setColor( palette.highlightedText().color() ); } painter->setFont( font ); painter->setPen( pen ); painter->drawText( option.rectF, align, text, &r ); painter->restore(); return r; } //--------- KDateTableCustomDateDelegate::KDateTableCustomDateDelegate( QObject *parent ) : KDateTableDateDelegate( parent ) { } QRectF KDateTableCustomDateDelegate::paint( QPainter *painter, const StyleOptionViewItem &option, const QDate &date, KDateTableDataModel *model ) { //debugPlan<save(); QRectF r; bool paintRect=true; QBrush bg(option.palette.base()); if( (option.state & QStyle::State_Active) == 0 ) { // we are either // ° painting a day of the previous month or // ° painting a day of the following month // TODO: don't hardcode gray here! Use a color with less contrast to the background than normal text. painter->setPen( option.palette.color(QPalette::Mid) ); // painter->setPen(gray); } else { // paint a day of the current month if (bgMode != KDateTable::NoBgMode) { QBrush oldbrush=painter->brush(); painter->setBrush( bgColor ); switch(bgMode) { case(KDateTable::CircleMode) : painter->drawEllipse(option.rectF);break; case(KDateTable::RectangleMode) : painter->drawRect(option.rectF);break; case(KDateTable::NoBgMode) : // Should never be here, but just to get one // less warning when compiling default: break; } painter->setBrush( oldbrush ); paintRect=false; } painter->setPen( fgColor ); QPen pen=painter->pen(); if ( option.state & QStyle::State_Selected ) { // draw the currently selected date //debugPlan<<"selected: "<setPen(option.palette.color(QPalette::Highlight)); painter->setBrush(option.palette.color(QPalette::Highlight)); } else { //debugPlan<<"disabled & selected: "<setPen(option.palette.color(QPalette::Text)); painter->setBrush(option.palette.color(QPalette::Text)); } pen=option.palette.color(QPalette::HighlightedText); } else { painter->setBrush(option.palette.color(QPalette::Background)); painter->setPen(option.palette.color(QPalette::Background)); } if ( date == QDate::currentDate() ) { painter->setPen(option.palette.color(QPalette::Text)); } if ( paintRect ) { painter->drawRect(option.rectF); } painter->setPen(pen); QString text = QLocale().toString(date.day()); if ( model ) { QVariant v = model->data( date ); if ( v.isValid() ) { text = v.toString(); } } painter->drawText(option.rectF, Qt::AlignCenter, text, &r); } painter->restore(); return r; } //--------- KDateTableWeekDayDelegate::KDateTableWeekDayDelegate( QObject *parent ) : QObject( parent ) { } QVariant KDateTableWeekDayDelegate::data( int day, int role, KDateTableDataModel *model ) { //debugPlan<weekDayData( day, role ); } QRectF KDateTableWeekDayDelegate::paint( QPainter *painter, const StyleOptionHeader &option, int daynum, KDateTableDataModel *model ) { //debugPlan<save(); QPalette palette = option.palette; if ( option.state & QStyle::State_Active ) { palette.setCurrentColorGroup( QPalette::Active ); } else { palette.setCurrentColorGroup( QPalette::Inactive ); } QRectF rect; QFont font = QFontDatabase::systemFont(QFontDatabase::GeneralFont); // font.setBold(true); painter->setFont(font); QColor titleColor( palette.button().color() ); QColor textColor( palette.buttonText().color() ); painter->setPen(titleColor); painter->setBrush(titleColor); painter->drawRect(option.rectF); QString value; if ( model ) { QVariant v = model->weekDayData( daynum, Qt::DisplayRole ); if ( v.isValid() ) { value = v.toString(); } } if (value.isEmpty()) { value = QLocale().dayName(daynum, QLocale::ShortFormat); } //debugPlan<setPen( textColor ); painter->drawText(option.rectF, option.textAlignment, value, &rect); // painter->setPen( palette.color(QPalette::Text) ); // painter->drawLine(QPointF(0, option.rectF.height()), QPointF(option.rectF.width(), option.rectF.height())); painter->restore(); return rect; } //--------- KDateTableWeekNumberDelegate::KDateTableWeekNumberDelegate( QObject *parent ) : QObject( parent ) { } QVariant KDateTableWeekNumberDelegate::data( int week, int role, KDateTableDataModel *model ) { //debugPlan<weekNumberData( week, role ); } QRectF KDateTableWeekNumberDelegate::paint( QPainter *painter, const StyleOptionHeader &option, int week, KDateTableDataModel *model ) { //debugPlan; painter->save(); QRectF result; QFont font = QFontDatabase::systemFont(QFontDatabase::GeneralFont); painter->setFont(font); QColor titleColor( option.palette.button().color() ); QColor textColor( option.palette.buttonText().color() ); painter->setPen(titleColor); painter->setBrush(titleColor); painter->drawRect(option.rectF); painter->setPen(textColor); QString value = QString("%1").arg( week ); if ( model ) { QVariant v = model->weekNumberData( week, Qt::DisplayRole ); if ( v.isValid() ) { value = v.toString(); } } painter->drawText(option.rectF, option.textAlignment, value, &result); // painter->setPen(option.palette.color(QPalette::Text)); // painter->drawLine(QPointF(option.rectF.width(), 0), QPointF(option.rectF.width(), option.rectF.height())); painter->restore(); return result; } } //namespace KPlato #include "moc_kdatetable.cpp" diff --git a/src/libs/models/kcalendar/kdatetable.h b/src/libs/models/kcalendar/kdatetable.h index aa1bd1a1..f07091ca 100644 --- a/src/libs/models/kcalendar/kdatetable.h +++ b/src/libs/models/kcalendar/kdatetable.h @@ -1,339 +1,339 @@ /* -*- C++ -*- This file is part of the KDE libraries Copyright (C) 1997 Tim D. Gilman (tdgilman@best.org) (C) 1998-2001 Mirko Boehm (mirko@kde.org) (C) 2007 John Layt 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 KP_KDATETABLE_H #define KP_KDATETABLE_H #include "planmodels_export.h" #include #include #include #include #include class QMenu; namespace KPlato { class KDateTableDataModel; class KDateTableDateDelegate; class KDateTableWeekDayDelegate; class KDateTableWeekNumberDelegate; class StyleOptionHeader; class StyleOptionViewItem; /** * Date selection table. * This is a support class for the KDatePicker class. It just * draws the calendar table without titles, but could theoretically * be used as a standalone. * * When a date is selected by the user, it emits a signal: * dateSelected(QDate) * * @internal * @author Tim Gilman, Mirko Boehm */ class PLANMODELS_EXPORT KDateTable : public QWidget { Q_OBJECT - Q_PROPERTY( QDate date READ date WRITE setDate ) - Q_PROPERTY( bool popupMenu READ popupMenuEnabled WRITE setPopupMenuEnabled ) + Q_PROPERTY( QDate date READ date WRITE setDate ) // clazy:exclude=qproperty-without-notify + Q_PROPERTY( bool popupMenu READ popupMenuEnabled WRITE setPopupMenuEnabled ) // clazy:exclude=qproperty-without-notify public: /** * The constructor. */ explicit KDateTable(QWidget* parent = 0); /** * The constructor. */ explicit KDateTable(const QDate&, QWidget* parent = 0); /** * The destructor. */ ~KDateTable(); /** * Returns a recommended size for the widget. * To save some time, the size of the largest used cell content is * calculated in each paintCell() call, since all calculations have * to be done there anyway. The size is stored in maxCell. The * sizeHint() simply returns a multiple of maxCell. */ QSize sizeHint() const Q_DECL_OVERRIDE; /** * Set the font size of the date table. */ void setFontSize(int size); /** * Select and display this date. */ bool setDate(const QDate &date); // KDE5 remove the const & from the returned QDate /** * @returns the selected date. */ const QDate& date() const; /** * Enables a popup menu when right clicking on a date. * * When it's enabled, this object emits a aboutToShowContextMenu signal * where you can fill in the menu items. */ void setPopupMenuEnabled( bool enable ); /** * Returns if the popup menu is enabled or not */ bool popupMenuEnabled() const; enum BackgroundMode { NoBgMode=0, RectangleMode, CircleMode }; /** * Makes a given date be painted with a given foregroundColor, and background * (a rectangle, or a circle/ellipse) in a given color. */ void setCustomDatePainting( const QDate &date, const QColor &fgColor, BackgroundMode bgMode=NoBgMode, const QColor &bgColor = QColor()); /** * Unsets the custom painting of a date so that the date is painted as usual. */ void unsetCustomDatePainting( const QDate &date ); //-----> enum ItemDataRole { DisplayRole_1 = Qt::UserRole + 1 }; enum SelectionMode { SingleSelection, ExtendedSelection }; void setSelectionMode( SelectionMode mode ); void setModel( KDateTableDataModel *model ); KDateTableDataModel *model() const; // datetable takes ownership of delegate void setDateDelegate( KDateTableDateDelegate *delegate ); // datetable takes ownership of delegate void setDateDelegate( const QDate &date, KDateTableDateDelegate *delegate ); // datetable takes ownership of delegate void setWeekDayDelegate( KDateTableWeekDayDelegate *delegate ); // datetable takes ownership of delegate void setWeekNumberDelegate( KDateTableWeekNumberDelegate *delegate ); void setWeekNumbersEnabled( bool enable ); void setStyleOptionDate( const StyleOptionViewItem &so ); void setStyleOptionWeekDay( const StyleOptionHeader &so ); void setStyleOptionWeekNumber( const StyleOptionHeader &so ); void setGridEnabled( bool enable ); //<----- protected: /** * calculate the position of the cell in the matrix for the given date. * The result is the 0-based index. */ virtual int posFromDate( const QDate &date ); /** * calculate the date that is displayed at a given cell in the matrix. pos is the * 0-based index in the matrix. Inverse function to posForDate(). */ virtual QDate dateFromPos( int pos ); void paintEvent(QPaintEvent *e) Q_DECL_OVERRIDE; /** * React on mouse clicks that select a date. */ void mousePressEvent(QMouseEvent *e ) Q_DECL_OVERRIDE; void wheelEvent(QWheelEvent *e) Q_DECL_OVERRIDE; void keyPressEvent(QKeyEvent *e) Q_DECL_OVERRIDE; void focusInEvent(QFocusEvent *e) Q_DECL_OVERRIDE; void focusOutEvent(QFocusEvent *e) Q_DECL_OVERRIDE; /** * Cell highlight on mouse hovering */ bool event(QEvent *e) Q_DECL_OVERRIDE; Q_SIGNALS: /** * The selected date changed. */ void dateChanged(const QDate &date); /** * This function behaves essentially like the one above. * The selected date changed. * @param cur The current date * @param old The date before the date was changed */ void dateChanged(const QDate& cur, const QDate& old); /** * A date has been selected by clicking on the table. */ void tableClicked(); /** * A popup menu for a given date is about to be shown (as when the user * right clicks on that date and the popup menu is enabled). Connect * the slot where you fill the menu to this signal. */ void aboutToShowContextMenu( QMenu * menu, const QDate &date ); /** * A popup menu for selected dates is about to be shown. * Connect the slot where you fill the menu to this signal. */ void aboutToShowContextMenu( QMenu * menu, const QList& ); //-----> void selectionChanged( const QList& ); protected Q_SLOTS: void slotReset(); void slotDataChanged( const QDate &start, const QDate &end ); //<------ private: Q_PRIVATE_SLOT(d, void nextMonth()) Q_PRIVATE_SLOT(d, void previousMonth()) Q_PRIVATE_SLOT(d, void beginningOfMonth()) Q_PRIVATE_SLOT(d, void endOfMonth()) Q_PRIVATE_SLOT(d, void beginningOfWeek()) Q_PRIVATE_SLOT(d, void endOfWeek()) private: class KDateTablePrivate; friend class KDateTablePrivate; KDateTablePrivate * const d; void initWidget(const QDate &date); void initAccels(); void paintCell(QPainter *painter, int row, int col); Q_DISABLE_COPY(KDateTable) }; //-----> class PLANMODELS_EXPORT KDateTableDataModel : public QObject { Q_OBJECT public: KDateTableDataModel( QObject *parent ); ~KDateTableDataModel(); /// Fetch data for @p date, @p dataType specifies the type of data virtual QVariant data( const QDate &date, int role = Qt::DisplayRole, int dataType = -1 ) const; virtual QVariant weekDayData( int day, int role = Qt::DisplayRole ) const; virtual QVariant weekNumberData( int week, int role = Qt::DisplayRole ) const; Q_SIGNALS: void reset(); void dataChanged( const QDate &start, const QDate &end ); }; //------- class PLANMODELS_EXPORT KDateTableDateDelegate : public QObject { Q_OBJECT public: KDateTableDateDelegate( QObject *parent = 0 ); ~KDateTableDateDelegate() {} virtual QRectF paint( QPainter *painter, const StyleOptionViewItem &option, const QDate &date, KDateTableDataModel *model ); virtual QVariant data( const QDate &date, int role, KDateTableDataModel *model ); }; class PLANMODELS_EXPORT KDateTableCustomDateDelegate : public KDateTableDateDelegate { Q_OBJECT public: KDateTableCustomDateDelegate( QObject *parent = 0 ); ~KDateTableCustomDateDelegate() {} virtual QRectF paint( QPainter *painter, const StyleOptionViewItem &option, const QDate &date, KDateTableDataModel *model ); private: friend class KDateTable; QColor fgColor; QColor bgColor; KDateTable::BackgroundMode bgMode; }; class PLANMODELS_EXPORT KDateTableWeekDayDelegate : public QObject { Q_OBJECT public: KDateTableWeekDayDelegate( QObject *parent = 0 ); ~KDateTableWeekDayDelegate() {} virtual QRectF paint( QPainter *painter, const StyleOptionHeader &option, int weekday, KDateTableDataModel *model ); virtual QVariant data( int day, int role, KDateTableDataModel *model ); }; class PLANMODELS_EXPORT KDateTableWeekNumberDelegate : public QObject { Q_OBJECT public: KDateTableWeekNumberDelegate( QObject *parent = 0 ); ~KDateTableWeekNumberDelegate() {} virtual QRectF paint( QPainter *painter, const StyleOptionHeader &option, int week, KDateTableDataModel *model ); virtual QVariant data( int week, int role, KDateTableDataModel *model ); }; class StyleOptionHeader : public QStyleOptionHeader { public: StyleOptionHeader() : QStyleOptionHeader() {} QRectF rectF; }; class StyleOptionViewItem : public QStyleOptionViewItem { public: StyleOptionViewItem() : QStyleOptionViewItem() {} StyleOptionViewItem( const StyleOptionViewItem &style ) : QStyleOptionViewItem( style ) { rectF = style.rectF; } QRectF rectF; }; } //namespace KPlato #endif // KP_KDATETABLE_H