/// ------------------------------------------------------------------------------------ /// <summary> /// If a data update is actually in progress, we want to only complete edits and not /// notify the world of the prop changes yet (we'll store the info in a queue for /// broadcast later, in Dispose()). /// </summary> /// <param name="vwsel"></param> /// <param name="sda">Data access object (corresponds to a DB connection)</param> /// <returns>Return value from IVwSelection.Commit() or IVwSelection.CompleteEdits() /// </returns> /// ------------------------------------------------------------------------------------ public static bool Commit(IVwSelection vwsel, ISilDataAccess sda) { if (vwsel == null) { return(false); } UpdateSemaphore semaphore = null; if (s_UpdateSemaphores.ContainsKey(sda)) { semaphore = s_UpdateSemaphores[sda]; } if (semaphore == null || !semaphore.fDataUpdateInProgress) { return(vwsel.Commit()); } VwChangeInfo changeInfo; bool fRet = vwsel.CompleteEdits(out changeInfo); if (changeInfo.hvo != 0) { semaphore.changeInfoQueue.Enqueue(changeInfo); } return(fRet); }
/// <summary> /// Create one. /// </summary> /// <param name="owner"></param> /// <param name="sda"></param> /// <param name="site"></param> /// <param name="updateDescription"></param> /// <param name="fTurnOnMonitor">if true, start monitoring. if false, suspend/disable monitoring.</param> /// <param name="fSuppressRecordingPriorSelection">True for operations (currently only ReplaceAll) /// where we do not want to record the prior selection. This prevents calling editinghelper.OnAboutToEdit, /// which therefore doesn't save info about the current selection. The current actual effect /// is to suppress the work of the AnnotationAdjuster for the selected paragraphs.</param> public DataUpdateMonitor(Control owner, ISilDataAccess sda, IVwRootSite site, string updateDescription, bool fTurnOnMonitor, bool fSuppressRecordingPriorSelection) { // DataUpdateMonitor may be nested, so make sure we're not already monitoring the site. m_fTurnOnMonitor = fTurnOnMonitor && (site == null || !s_sitesMonitoring.Contains(site)); if (!m_fTurnOnMonitor) { return; } // register this site as being monitored. if (site != null) { s_sitesMonitoring.Add(site); } Debug.Assert(sda != null); if (s_UpdateSemaphores.ContainsKey(sda)) { UpdateSemaphore semaphore = s_UpdateSemaphores[sda]; if (semaphore.fDataUpdateInProgress) { throw new Exception("Concurrent access on Database detected"); } // Set ((static semaphore) members) for this data update semaphore.fDataUpdateInProgress = true; semaphore.sDescr = updateDescription; } else { s_UpdateSemaphores[sda] = new UpdateSemaphore(true, updateDescription); } m_Owner = owner; m_sda = sda; m_rootSite = site; if (m_rootSite != null) { m_editingHelper = ((IRootSite)m_rootSite).EditingHelper; } // store original selection info. // Note, some of its internals are computed from the actual selection // which can get changed during the life of DataUpdateMonitor. // But its properties and Hvo(fEndPoint) should remain the same. if (m_editingHelper != null && !fSuppressRecordingPriorSelection) { m_tsi = new TextSelInfo(m_editingHelper.EditedRootBox); m_editingHelper.OnAboutToEdit(); } // Set wait cursor if (owner != null) { m_oldCursor = owner.Cursor; owner.Cursor = Cursors.WaitCursor; } }
/// ------------------------------------------------------------------------------------ /// <summary> /// Initializes a new instance of the <see cref="DataUpdateMonitor"/> class. /// </summary> /// <param name="owner">An optional owner (A wait cursor will be put on it if not null). /// </param> /// <param name="updateDescription">A simple description of what is being done.</param> /// ------------------------------------------------------------------------------------ public DataUpdateMonitor(Control owner, string updateDescription) { if (s_updateSemaphore == null) s_updateSemaphore = new UpdateSemaphore(true, updateDescription); else { if (s_updateSemaphore.fDataUpdateInProgress) throw new Exception("Concurrent access on Database detected. Previous access: " + s_updateSemaphore.sDescr); // Set ((static semaphore) members) for this data update s_updateSemaphore.fDataUpdateInProgress = true; s_updateSemaphore.sDescr = updateDescription; } // Set wait cursor if (owner != null) { m_wait = new WaitCursor(owner); } }
/// ------------------------------------------------------------------------------------ /// <summary> /// Initializes a new instance of the <see cref="DataUpdateMonitor"/> class. /// </summary> /// <param name="owner">An optional owner (A wait cursor will be put on it if not null). /// </param> /// <param name="updateDescription">A simple description of what is being done.</param> /// ------------------------------------------------------------------------------------ public DataUpdateMonitor(Control owner, string updateDescription) { if (s_updateSemaphore == null) { s_updateSemaphore = new UpdateSemaphore(true, updateDescription); } else { if (s_updateSemaphore.fDataUpdateInProgress) { throw new Exception("Concurrent access on Database detected. Previous access: " + s_updateSemaphore.sDescr); } // Set ((static semaphore) members) for this data update s_updateSemaphore.fDataUpdateInProgress = true; s_updateSemaphore.sDescr = updateDescription; } // Set wait cursor if (owner != null) { m_wait = new WaitCursor(owner); } }
/// ------------------------------------------------------------------------------------ /// <summary> /// Executes in two distinct scenarios. /// 1. If disposing is true, the method has been called directly /// or indirectly by a user's code via the Dispose method. /// Both managed and unmanaged resources can be disposed. /// 2. If disposing is false, the method has been called by the /// runtime from inside the finalizer and you should not reference (access) /// other managed objects, as they already have been garbage collected. /// Only unmanaged resources can be disposed. /// </summary> /// <param name="disposing">if set to <c>true</c> [disposing].</param> /// <remarks> /// If any exceptions are thrown, that is fine. /// If the method is being done in a finalizer, it will be ignored. /// If it is thrown by client code calling Dispose, /// it needs to be handled by fixing the bug. /// If subclasses override this method, they should call the base implementation. /// </remarks> /// ------------------------------------------------------------------------------------ protected virtual void Dispose(bool disposing) { Debug.WriteLineIf(!disposing, "****************** Missing Dispose() call for " + GetType().Name + "******************"); // Must not be run more than once. if (m_isDisposed) { return; } if (disposing) { try { // Dispose managed resources here. UpdateSemaphore semaphore = s_updateSemaphore; Debug.Assert(semaphore.fDataUpdateInProgress); semaphore.fDataUpdateInProgress = false; semaphore.sDescr = string.Empty; } finally { // end Wait Cursor // Since it needs m_wait, this must be done when 'disposing' is true, // as that is a disposable object, which may be gone in // Finalizer mode. if (m_wait != null) { m_wait.Dispose(); } } } // Dispose unmanaged resources here, whether disposing is true or false. m_wait = null; m_isDisposed = true; }
/// ------------------------------------------------------------------------------------ /// <summary> /// Remove knowledge of a particular SilDataAccess (DB connection). This is used both /// for testing and also whenever a database connection is being closed but the app is /// not being destroyed (e.g., during backup, or when the last window connected to a /// particular DB is closed). /// </summary> /// ------------------------------------------------------------------------------------ public static void ClearSemaphore() { Debug.Assert(!IsUpdateInProgress()); s_updateSemaphore = null; }
/// ------------------------------------------------------------------------------------ /// <summary> /// Executes in two distinct scenarios. /// 1. If disposing is true, the method has been called directly /// or indirectly by a user's code via the Dispose method. /// Both managed and unmanaged resources can be disposed. /// 2. If disposing is false, the method has been called by the /// runtime from inside the finalizer and you should not reference (access) /// other managed objects, as they already have been garbage collected. /// Only unmanaged resources can be disposed. /// </summary> /// <param name="disposing">if set to <c>true</c> [disposing].</param> /// <remarks> /// If any exceptions are thrown, that is fine. /// If the method is being done in a finalizer, it will be ignored. /// If it is thrown by client code calling Dispose, /// it needs to be handled by fixing the bug. /// If subclasses override this method, they should call the base implementation. /// </remarks> /// ------------------------------------------------------------------------------------ protected virtual void Dispose(bool disposing) { //Debug.WriteLineIf(!disposing, "****************** " + GetType().Name + " 'disposing' is false. ******************"); // Must not be run more than once. if (m_isDisposed) { return; } if (disposing && m_fTurnOnMonitor) { try { // Dispose managed resources here. if (m_sda != null) { UpdateSemaphore semaphore = s_UpdateSemaphores[m_sda]; Debug.Assert(semaphore.fDataUpdateInProgress); // Remember selection so that we can try to reconstruct it after the PropChangeds SelectionHelper selection = SelectionHelper.Create(m_rootSite); TextSelInfo tsiAfterEdit = null; if (selection != null) { tsiAfterEdit = new TextSelInfo(selection.Selection); } bool fAdjustedChangeInfo = false; foreach (VwChangeInfo changeInfo in semaphore.changeInfoQueue) { int ivIns = changeInfo.ivIns; int cvDel = changeInfo.cvDel; int cvIns = changeInfo.cvIns; // Note: m_sda.MetaDataCache increments an internal com object // ref count that may not get cleared until you do // Marshal.FinalReleaseComObject on it. if it doesn't get cleared // it may hang tests. IFwMetaDataCache mdc = m_sda.MetaDataCache; if (!fAdjustedChangeInfo && mdc != null && tsiAfterEdit != null && m_editingHelper != null && m_editingHelper.MonitorTextEdits) { // if the selection-edit resulted in keeping the cursor in the same paragraph // we may need to do some more adjustments because views code // calculates VwChangeInfo based upon a string comparison, which does not // accurately tell us where the string was actually changed if the inserted // characters match those character positions in the original string. // For example changing "this is the old text" by inserting "this is the new text, but " // at the beginning results in the string "this is the old text, but this is the new text" // In that example the views code StringCompare would say ivIns started at "old text" // rather than the beginning of the string, since "this is the " matches the old string. // The first condition prevents our trying to make these adjustments when we have a multi-para // (end-before-anchor) selection. if (m_tsi.Hvo(true) == m_tsi.Hvo(false) && m_tsi.HvoAnchor == tsiAfterEdit.HvoAnchor && m_tsi.HvoAnchor == changeInfo.hvo && m_tsi.TagAnchor == changeInfo.tag) { // Our insertion point can be at the beginning or end of the range. int ichInsertionPointOrig = Math.Min(m_tsi.IchAnchor, m_tsi.IchEnd); // we may need to adjust ivIns, but not for MultiStrings, since // ivIns in that case is a ws, not an offset. int flidtype = mdc.GetFieldType((uint)changeInfo.tag); if (flidtype == (int)CellarModuleDefns.kcptBigString || flidtype == (int)CellarModuleDefns.kcptString) { // if the anchor has moved back after a delete, use it as a starting point if (!m_tsi.IsRange && cvDel > 0 && ivIns < m_tsi.IchAnchor) { if (ivIns + cvDel == m_tsi.IchAnchor) { // user did backspace from insertion point, so effectively // move the IP back the number of characters deleted. ivIns = Math.Max(m_tsi.IchAnchor - cvDel, 0); } // ctrl-del can also cause this, but in that case, characters // after the IP may have been deleted, too. Seems best not to try to adjust. } else { // use the original IP, since changeInfo uses CompareStrings // to calculate it, and that can be wrong when pasted string has // characters that coincidentally match the original string. ivIns = ichInsertionPointOrig; } } // if the initial selection is a range selection in the same paragraph // set the number of deleted characters to be the difference between // the begin and end offsets. if (m_tsi.HvoAnchor == m_tsi.Hvo(true) && m_tsi.IsRange) { cvDel = Math.Abs(m_tsi.IchEnd - m_tsi.IchAnchor); } // Review: do we expect this string to be Normalized already? // Review: should we do nothing if the pasted string contains newline, or set cvIns to the // length of the text after the last newline, or what?? if (InsertedTss != null && InsertedTss.Text != null && InsertedTss.Text.IndexOf(Environment.NewLine) == -1) { cvIns = InsertedTss.Length; } // indicate we've adjusted the changeInfo for the next PropChange. // this should be done only once per edit action. fAdjustedChangeInfo = true; } } m_sda.PropChanged(null, (int)PropChangeType.kpctNotifyAll, changeInfo.hvo, changeInfo.tag, ivIns, cvIns, cvDel); } semaphore.fDataUpdateInProgress = false; semaphore.sDescr = string.Empty; semaphore.changeInfoQueue.Clear(); // It is possible that the PropChanged caused a regenerate of the view. This // turned our selection invalid. Try to recover it. if (selection != null && !selection.IsValid) { selection.SetSelection(false); } // This needs to be called after setting the selection. It can cause // AnnotationAdjuster.EndKeyPressed() to be called which expects a // selection to be set. if (m_editingHelper != null) { m_editingHelper.OnFinishedEdit(); } // It is possible that OnFinishedEdit() caused a regenerate of the view. This // turned our selection invalid. Try to recover it. if (selection != null && !selection.IsValid) { selection.SetSelection(false); } } } finally { // In case anything goes wrong, if we possibly can, do this anyway, other wise the pane // may be more-or-less permanently locked. if (m_rootSite != null) { s_sitesMonitoring.Remove(m_rootSite); } // end Wait Cursor // Since it needs m_Owner, this must be done when 'disposing' is true, // as that is a disposable object, which may be gone in // Finalizer mode. if (m_Owner != null) { m_Owner.Cursor = m_oldCursor; } } } // Dispose unmanaged resources here, whether disposing is true or false. m_sda = null; m_Owner = null; m_rootSite = null; m_oldCursor = null; m_tsi = null; m_tssIns = null; m_isDisposed = true; }
/// <summary> /// Create one. /// </summary> /// <param name="owner"></param> /// <param name="sda"></param> /// <param name="site"></param> /// <param name="updateDescription"></param> /// <param name="fTurnOnMonitor">if true, start monitoring. if false, suspend/disable monitoring.</param> /// <param name="fSuppressRecordingPriorSelection">True for operations (currently only ReplaceAll) /// where we do not want to record the prior selection. This prevents calling editinghelper.OnAboutToEdit, /// which therefore doesn't save info about the current selection. The current actual effect /// is to suppress the work of the AnnotationAdjuster for the selected paragraphs.</param> public DataUpdateMonitor(Control owner, ISilDataAccess sda, IVwRootSite site, string updateDescription, bool fTurnOnMonitor, bool fSuppressRecordingPriorSelection) { // DataUpdateMonitor may be nested, so make sure we're not already monitoring the site. m_fTurnOnMonitor = fTurnOnMonitor && (site == null || !s_sitesMonitoring.Contains(site)); if (!m_fTurnOnMonitor) return; // register this site as being monitored. if (site != null) s_sitesMonitoring.Add(site); Debug.Assert(sda != null); if (s_UpdateSemaphores.ContainsKey(sda)) { UpdateSemaphore semaphore = s_UpdateSemaphores[sda]; if (semaphore.fDataUpdateInProgress) throw new Exception("Concurrent access on Database detected"); // Set ((static semaphore) members) for this data update semaphore.fDataUpdateInProgress = true; semaphore.sDescr = updateDescription; } else { s_UpdateSemaphores[sda] = new UpdateSemaphore(true, updateDescription); } m_Owner = owner; m_sda = sda; m_rootSite = site; if (m_rootSite != null) m_editingHelper = ((IRootSite)m_rootSite).EditingHelper; // store original selection info. // Note, some of its internals are computed from the actual selection // which can get changed during the life of DataUpdateMonitor. // But its properties and Hvo(fEndPoint) should remain the same. if (m_editingHelper != null && !fSuppressRecordingPriorSelection) { m_tsi = new TextSelInfo(m_editingHelper.EditedRootBox); m_editingHelper.OnAboutToEdit(); } // Set wait cursor if (owner != null) { m_oldCursor = owner.Cursor; owner.Cursor = Cursors.WaitCursor; } }