michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- michael@0: * michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "mozilla/mozalloc.h" // for operator new michael@0: #include "nsAString.h" michael@0: #include "nsComponentManagerUtils.h" // for do_CreateInstance michael@0: #include "nsComposerCommandsUpdater.h" michael@0: #include "nsDebug.h" // for NS_ENSURE_TRUE, etc michael@0: #include "nsError.h" // for NS_OK, NS_ERROR_FAILURE, etc michael@0: #include "nsICommandManager.h" // for nsICommandManager michael@0: #include "nsID.h" // for NS_GET_IID, etc michael@0: #include "nsIDOMWindow.h" // for nsIDOMWindow michael@0: #include "nsIDocShell.h" // for nsIDocShell michael@0: #include "nsIInterfaceRequestorUtils.h" // for do_GetInterface michael@0: #include "nsISelection.h" // for nsISelection michael@0: #include "nsITransactionManager.h" // for nsITransactionManager michael@0: #include "nsLiteralString.h" // for NS_LITERAL_STRING michael@0: #include "nsPICommandUpdater.h" // for nsPICommandUpdater michael@0: #include "nsPIDOMWindow.h" // for nsPIDOMWindow michael@0: michael@0: class nsIDOMDocument; michael@0: class nsITransaction; michael@0: michael@0: nsComposerCommandsUpdater::nsComposerCommandsUpdater() michael@0: : mDirtyState(eStateUninitialized) michael@0: , mSelectionCollapsed(eStateUninitialized) michael@0: , mFirstDoOfFirstUndo(true) michael@0: { michael@0: } michael@0: michael@0: nsComposerCommandsUpdater::~nsComposerCommandsUpdater() michael@0: { michael@0: // cancel any outstanding update timer michael@0: if (mUpdateTimer) michael@0: { michael@0: mUpdateTimer->Cancel(); michael@0: } michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(nsComposerCommandsUpdater, nsISelectionListener, michael@0: nsIDocumentStateListener, nsITransactionListener, nsITimerCallback) michael@0: michael@0: #if 0 michael@0: #pragma mark - michael@0: #endif michael@0: michael@0: NS_IMETHODIMP michael@0: nsComposerCommandsUpdater::NotifyDocumentCreated() michael@0: { michael@0: // Trigger an nsIObserve notification that the document has been created michael@0: UpdateOneCommand("obs_documentCreated"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsComposerCommandsUpdater::NotifyDocumentWillBeDestroyed() michael@0: { michael@0: // cancel any outstanding update timer michael@0: if (mUpdateTimer) michael@0: { michael@0: mUpdateTimer->Cancel(); michael@0: mUpdateTimer = nullptr; michael@0: } michael@0: michael@0: // We can't call this right now; it is too late in some cases and the window michael@0: // is already partially destructed (e.g. JS objects may be gone). michael@0: #if 0 michael@0: // Trigger an nsIObserve notification that the document will be destroyed michael@0: UpdateOneCommand("obs_documentWillBeDestroyed"); michael@0: #endif michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsComposerCommandsUpdater::NotifyDocumentStateChanged(bool aNowDirty) michael@0: { michael@0: // update document modified. We should have some other notifications for this too. michael@0: return UpdateDirtyState(aNowDirty); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsComposerCommandsUpdater::NotifySelectionChanged(nsIDOMDocument *, michael@0: nsISelection *, int16_t) michael@0: { michael@0: return PrimeUpdateTimer(); michael@0: } michael@0: michael@0: #if 0 michael@0: #pragma mark - michael@0: #endif michael@0: michael@0: NS_IMETHODIMP michael@0: nsComposerCommandsUpdater::WillDo(nsITransactionManager *aManager, michael@0: nsITransaction *aTransaction, bool *aInterrupt) michael@0: { michael@0: *aInterrupt = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsComposerCommandsUpdater::DidDo(nsITransactionManager *aManager, michael@0: nsITransaction *aTransaction, nsresult aDoResult) michael@0: { michael@0: // only need to update if the status of the Undo menu item changes. michael@0: int32_t undoCount; michael@0: aManager->GetNumberOfUndoItems(&undoCount); michael@0: if (undoCount == 1) michael@0: { michael@0: if (mFirstDoOfFirstUndo) michael@0: UpdateCommandGroup(NS_LITERAL_STRING("undo")); michael@0: mFirstDoOfFirstUndo = false; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsComposerCommandsUpdater::WillUndo(nsITransactionManager *aManager, michael@0: nsITransaction *aTransaction, michael@0: bool *aInterrupt) michael@0: { michael@0: *aInterrupt = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsComposerCommandsUpdater::DidUndo(nsITransactionManager *aManager, michael@0: nsITransaction *aTransaction, michael@0: nsresult aUndoResult) michael@0: { michael@0: int32_t undoCount; michael@0: aManager->GetNumberOfUndoItems(&undoCount); michael@0: if (undoCount == 0) michael@0: mFirstDoOfFirstUndo = true; // reset the state for the next do michael@0: michael@0: UpdateCommandGroup(NS_LITERAL_STRING("undo")); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsComposerCommandsUpdater::WillRedo(nsITransactionManager *aManager, michael@0: nsITransaction *aTransaction, michael@0: bool *aInterrupt) michael@0: { michael@0: *aInterrupt = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsComposerCommandsUpdater::DidRedo(nsITransactionManager *aManager, michael@0: nsITransaction *aTransaction, michael@0: nsresult aRedoResult) michael@0: { michael@0: UpdateCommandGroup(NS_LITERAL_STRING("undo")); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsComposerCommandsUpdater::WillBeginBatch(nsITransactionManager *aManager, michael@0: bool *aInterrupt) michael@0: { michael@0: *aInterrupt = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsComposerCommandsUpdater::DidBeginBatch(nsITransactionManager *aManager, michael@0: nsresult aResult) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsComposerCommandsUpdater::WillEndBatch(nsITransactionManager *aManager, michael@0: bool *aInterrupt) michael@0: { michael@0: *aInterrupt = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsComposerCommandsUpdater::DidEndBatch(nsITransactionManager *aManager, michael@0: nsresult aResult) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsComposerCommandsUpdater::WillMerge(nsITransactionManager *aManager, michael@0: nsITransaction *aTopTransaction, michael@0: nsITransaction *aTransactionToMerge, michael@0: bool *aInterrupt) michael@0: { michael@0: *aInterrupt = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsComposerCommandsUpdater::DidMerge(nsITransactionManager *aManager, michael@0: nsITransaction *aTopTransaction, michael@0: nsITransaction *aTransactionToMerge, michael@0: bool aDidMerge, nsresult aMergeResult) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: #if 0 michael@0: #pragma mark - michael@0: #endif michael@0: michael@0: nsresult michael@0: nsComposerCommandsUpdater::Init(nsIDOMWindow* aDOMWindow) michael@0: { michael@0: NS_ENSURE_ARG(aDOMWindow); michael@0: mDOMWindow = do_GetWeakReference(aDOMWindow); michael@0: michael@0: nsCOMPtr window(do_QueryInterface(aDOMWindow)); michael@0: if (window) michael@0: { michael@0: mDocShell = do_GetWeakReference(window->GetDocShell()); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsComposerCommandsUpdater::PrimeUpdateTimer() michael@0: { michael@0: if (!mUpdateTimer) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: mUpdateTimer = do_CreateInstance("@mozilla.org/timer;1", &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: const uint32_t kUpdateTimerDelay = 150; michael@0: return mUpdateTimer->InitWithCallback(static_cast(this), michael@0: kUpdateTimerDelay, michael@0: nsITimer::TYPE_ONE_SHOT); michael@0: } michael@0: michael@0: michael@0: void nsComposerCommandsUpdater::TimerCallback() michael@0: { michael@0: // if the selection state has changed, update stuff michael@0: bool isCollapsed = SelectionIsCollapsed(); michael@0: if (static_cast(isCollapsed) != mSelectionCollapsed) michael@0: { michael@0: UpdateCommandGroup(NS_LITERAL_STRING("select")); michael@0: mSelectionCollapsed = isCollapsed; michael@0: } michael@0: michael@0: // isn't this redundant with the UpdateCommandGroup above? michael@0: // can we just nuke the above call? or create a meta command group? michael@0: UpdateCommandGroup(NS_LITERAL_STRING("style")); michael@0: } michael@0: michael@0: nsresult michael@0: nsComposerCommandsUpdater::UpdateDirtyState(bool aNowDirty) michael@0: { michael@0: if (mDirtyState != static_cast(aNowDirty)) michael@0: { michael@0: UpdateCommandGroup(NS_LITERAL_STRING("save")); michael@0: UpdateCommandGroup(NS_LITERAL_STRING("undo")); michael@0: mDirtyState = aNowDirty; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsComposerCommandsUpdater::UpdateCommandGroup(const nsAString& aCommandGroup) michael@0: { michael@0: nsCOMPtr commandUpdater = GetCommandUpdater(); michael@0: NS_ENSURE_TRUE(commandUpdater, NS_ERROR_FAILURE); michael@0: michael@0: michael@0: // This hardcoded list of commands is temporary. michael@0: // This code should use nsIControllerCommandGroup. michael@0: if (aCommandGroup.EqualsLiteral("undo")) michael@0: { michael@0: commandUpdater->CommandStatusChanged("cmd_undo"); michael@0: commandUpdater->CommandStatusChanged("cmd_redo"); michael@0: } michael@0: else if (aCommandGroup.EqualsLiteral("select") || michael@0: aCommandGroup.EqualsLiteral("style")) michael@0: { michael@0: commandUpdater->CommandStatusChanged("cmd_bold"); michael@0: commandUpdater->CommandStatusChanged("cmd_italic"); michael@0: commandUpdater->CommandStatusChanged("cmd_underline"); michael@0: commandUpdater->CommandStatusChanged("cmd_tt"); michael@0: michael@0: commandUpdater->CommandStatusChanged("cmd_strikethrough"); michael@0: commandUpdater->CommandStatusChanged("cmd_superscript"); michael@0: commandUpdater->CommandStatusChanged("cmd_subscript"); michael@0: commandUpdater->CommandStatusChanged("cmd_nobreak"); michael@0: michael@0: commandUpdater->CommandStatusChanged("cmd_em"); michael@0: commandUpdater->CommandStatusChanged("cmd_strong"); michael@0: commandUpdater->CommandStatusChanged("cmd_cite"); michael@0: commandUpdater->CommandStatusChanged("cmd_abbr"); michael@0: commandUpdater->CommandStatusChanged("cmd_acronym"); michael@0: commandUpdater->CommandStatusChanged("cmd_code"); michael@0: commandUpdater->CommandStatusChanged("cmd_samp"); michael@0: commandUpdater->CommandStatusChanged("cmd_var"); michael@0: michael@0: commandUpdater->CommandStatusChanged("cmd_increaseFont"); michael@0: commandUpdater->CommandStatusChanged("cmd_decreaseFont"); michael@0: michael@0: commandUpdater->CommandStatusChanged("cmd_paragraphState"); michael@0: commandUpdater->CommandStatusChanged("cmd_fontFace"); michael@0: commandUpdater->CommandStatusChanged("cmd_fontColor"); michael@0: commandUpdater->CommandStatusChanged("cmd_backgroundColor"); michael@0: commandUpdater->CommandStatusChanged("cmd_highlight"); michael@0: } michael@0: else if (aCommandGroup.EqualsLiteral("save")) michael@0: { michael@0: // save commands (most are not in C++) michael@0: commandUpdater->CommandStatusChanged("cmd_setDocumentModified"); michael@0: commandUpdater->CommandStatusChanged("cmd_save"); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsComposerCommandsUpdater::UpdateOneCommand(const char *aCommand) michael@0: { michael@0: nsCOMPtr commandUpdater = GetCommandUpdater(); michael@0: NS_ENSURE_TRUE(commandUpdater, NS_ERROR_FAILURE); michael@0: michael@0: commandUpdater->CommandStatusChanged(aCommand); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: nsComposerCommandsUpdater::SelectionIsCollapsed() michael@0: { michael@0: nsCOMPtr domWindow = do_QueryReferent(mDOMWindow); michael@0: NS_ENSURE_TRUE(domWindow, true); michael@0: michael@0: nsCOMPtr domSelection; michael@0: if (NS_SUCCEEDED(domWindow->GetSelection(getter_AddRefs(domSelection))) && domSelection) michael@0: { michael@0: bool selectionCollapsed = false; michael@0: domSelection->GetIsCollapsed(&selectionCollapsed); michael@0: return selectionCollapsed; michael@0: } michael@0: michael@0: NS_WARNING("nsComposerCommandsUpdater::SelectionIsCollapsed - no domSelection"); michael@0: michael@0: return false; michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsComposerCommandsUpdater::GetCommandUpdater() michael@0: { michael@0: nsCOMPtr docShell = do_QueryReferent(mDocShell); michael@0: NS_ENSURE_TRUE(docShell, nullptr); michael@0: nsCOMPtr manager = do_GetInterface(docShell); michael@0: nsCOMPtr updater = do_QueryInterface(manager); michael@0: return updater.forget(); michael@0: } michael@0: michael@0: #if 0 michael@0: #pragma mark - michael@0: #endif michael@0: michael@0: nsresult michael@0: nsComposerCommandsUpdater::Notify(nsITimer *timer) michael@0: { michael@0: NS_ASSERTION(timer == mUpdateTimer.get(), "Hey, this ain't my timer!"); michael@0: TimerCallback(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: #if 0 michael@0: #pragma mark - michael@0: #endif michael@0: michael@0: michael@0: nsresult michael@0: NS_NewComposerCommandsUpdater(nsISelectionListener** aInstancePtrResult) michael@0: { michael@0: nsComposerCommandsUpdater* newThang = new nsComposerCommandsUpdater; michael@0: NS_ENSURE_TRUE(newThang, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: return newThang->QueryInterface(NS_GET_IID(nsISelectionListener), michael@0: (void **)aInstancePtrResult); michael@0: }