FIX issue #19 where multiple widget values led to bad replacements in the command
[qgit4/redivivus.git] / src / domain.cpp
blob74cba4da26f7cac662204c4c940c59dae63681b8
1 /*
2 Author: Marco Costalba (C) 2005-2007
4 Copyright: See COPYING file that comes with this distribution
6 */
7 #include <QApplication>
8 #include <QStatusBar>
9 #include <QTimer>
10 #include "FileHistory.h"
11 #include "exceptionmanager.h"
12 #include "mainimpl.h"
13 #include "git.h"
14 #include "domain.h"
16 using namespace QGit;
18 void StateInfo::S::clear() {
20 sha = fn = dtSha = "";
21 isM = allM = false;
22 sel = true;
25 bool StateInfo::S::operator==(const StateInfo::S& st) const {
27 if (&st == this)
28 return true;
30 return ( sha == st.sha
31 && fn == st.fn
32 && dtSha == st.dtSha
33 && sel == st.sel
34 && isM == st.isM
35 && allM == st.allM);
38 bool StateInfo::S::operator!=(const StateInfo::S& st) const {
40 return !(StateInfo::S::operator==(st));
43 void StateInfo::clear() {
45 nextS.clear();
46 curS.clear();
47 prevS.clear();
48 isLocked = false;
51 StateInfo& StateInfo::operator=(const StateInfo& newState) {
53 if (&newState != this) {
54 if (isLocked)
55 nextS = newState.curS;
56 else
57 curS = newState.curS; // prevS is mot modified to allow a rollback
59 return *this;
62 bool StateInfo::operator==(const StateInfo& newState) const {
64 if (&newState == this)
65 return true;
67 return (curS == newState.curS); // compare is made on curS only
70 bool StateInfo::operator!=(const StateInfo& newState) const {
72 return !(StateInfo::operator==(newState));
75 bool StateInfo::isChanged(uint what) const {
77 bool ret = false;
78 if (what & SHA)
79 ret = (sha(true) != sha(false));
81 if (!ret && (what & FILE_NAME))
82 ret = (fileName(true) != fileName(false));
84 if (!ret && (what & DIFF_TO_SHA))
85 ret = (diffToSha(true) != diffToSha(false));
87 if (!ret && (what & ALL_MERGE_FILES))
88 ret = (allMergeFiles(true) != allMergeFiles(false));
90 return ret;
93 // ************************* Domain ****************************
95 Domain::Domain(MainImpl* m, Git* g, bool isMain) : QObject(m), git(g) {
97 EM_INIT(exDeleteRequest, "Deleting domain");
98 EM_INIT(exCancelRequest, "Canceling update");
100 fileHistory = new FileHistory(this, git);
101 if (isMain)
102 git->setDefaultModel(fileHistory);
104 st.clear();
105 busy = linked = false;
106 popupType = 0;
107 container = new QWidget(NULL); // will be reparented to m()->tabWdg
110 Domain::~Domain() {
112 if (!parent())
113 return;
115 // remove before to delete, avoids a Qt warning in QInputContext()
116 m()->tabWdg->removeTab(m()->tabWdg->indexOf(container));
117 container->deleteLater();
120 void Domain::clear(bool complete) {
122 if (complete)
123 st.clear();
125 fileHistory->clear();
128 void Domain::on_closeAllTabs() {
130 delete this; // must be sync, deleteLater() does not work
133 void Domain::deleteWhenDone() {
135 if (!EM_IS_PENDING(exDeleteRequest))
136 EM_RAISE(exDeleteRequest);
138 emit cancelDomainProcesses();
140 on_deleteWhenDone();
143 void Domain::on_deleteWhenDone() {
145 if (!EM_IS_PENDING(exDeleteRequest))
146 deleteLater();
147 else
148 QTimer::singleShot(20, this, SLOT(on_deleteWhenDone()));
151 void Domain::setThrowOnDelete(bool b) {
153 if (b)
154 EM_REGISTER(exDeleteRequest);
155 else
156 EM_REMOVE(exDeleteRequest);
159 bool Domain::isThrowOnDeleteRaised(int excpId, SCRef curContext) {
161 return EM_MATCH(excpId, exDeleteRequest, curContext);
164 MainImpl* Domain::m() const {
166 return static_cast<MainImpl*>(parent());
169 void Domain::showStatusBarMessage(const QString& msg, int timeout) {
171 m()->statusBar()->showMessage(msg, timeout);
174 void Domain::setTabCaption(const QString& caption) {
176 int idx = m()->tabWdg->indexOf(container);
177 m()->tabWdg->setTabText(idx, caption);
180 void Domain::unlinkDomain(Domain* d) {
182 d->linked = false;
183 while (d->disconnect(SIGNAL(updateRequested(StateInfo)), this))
184 ;// a signal is emitted for every connection you make,
185 // so if you duplicate a connection, two signals will be emitted.
188 void Domain::linkDomain(Domain* d) {
190 unlinkDomain(d); // be sure only one connection is active
191 connect(d, SIGNAL(updateRequested(StateInfo)), this, SLOT(on_updateRequested(StateInfo)));
192 d->linked = true;
195 void Domain::on_updateRequested(StateInfo newSt) {
197 st = newSt;
198 UPDATE();
201 bool Domain::flushQueue() {
202 // during dragging any state update is queued, so try to flush pending now
204 if (!busy && st.flushQueue()) {
205 UPDATE();
206 return true;
208 return false;
211 bool Domain::event(QEvent* e) {
213 bool fromMaster = false;
215 switch (int(e->type())) {
216 case UPD_DM_MST_EV:
217 fromMaster = true;
218 // fall through
219 case UPD_DM_EV:
220 update(fromMaster, ((UpdateDomainEvent*)e)->isForced());
221 break;
222 case MSG_EV:
223 if (!busy && !st.requestPending())
224 QApplication::postEvent(m(), new MessageEvent(((MessageEvent*)e)->myData()));
225 else // waiting for the end of updating
226 statusBarRequest = ((MessageEvent*)e)->myData();
227 break;
228 default:
229 break;
231 return QObject::event(e);
234 void Domain::populateState() {
236 const Rev* r = git->revLookup(st.sha());
237 if (r)
238 st.setIsMerge(r->parentsCount() > 1);
241 void Domain::update(bool fromMaster, bool force) {
243 if (busy && st.requestPending()) {
244 // quick exit current (obsoleted) update but only if state
245 // is going to change. Without this check calling update()
246 // many times with the same data nullify the update
247 EM_RAISE(exCancelRequest);
248 emit cancelDomainProcesses();
250 if (busy)
251 return;
253 if (linked && !fromMaster) {
254 // in this case let the update to fall down from master domain
255 StateInfo tmp(st);
256 st.rollBack(); // we don't want to filter out next update sent from master
257 emit updateRequested(tmp);
258 return;
260 try {
261 EM_REGISTER_Q(exCancelRequest); // quiet, no messages when thrown
262 setThrowOnDelete(true);
263 git->setThrowOnStop(true);
264 git->setCurContext(this);
265 busy = true;
266 populateState(); // complete any missing state information
267 st.setLock(true); // any state change will be queued now
269 if (doUpdate(force))
270 st.commit();
271 else
272 st.rollBack();
274 st.setLock(false);
275 busy = false;
276 if (git->curContext() != this)
277 qDebug("ASSERT in Domain::update, context is %p "
278 "instead of %p", (void*)git->curContext(), (void*)this);
280 git->setCurContext(NULL);
281 git->setThrowOnStop(false);
282 setThrowOnDelete(false);
283 EM_REMOVE(exCancelRequest);
285 } catch (int i) {
288 If we have a cancel request because of a new update is in queue we
289 cannot roolback current state to avoid new update is filtered out
290 in case rolled back sha and new sha are the same.
291 This could happen with arrow navigation:
293 sha -> go UP (new sha) -> go DOWN (cancel) -> rollback to sha
295 And pending request 'sha' is filtered out leaving us in an
296 inconsistent state.
298 st.commit();
299 st.setLock(false);
300 busy = false;
301 git->setCurContext(NULL);
302 git->setThrowOnStop(false);
303 setThrowOnDelete(false);
304 EM_REMOVE(exCancelRequest);
306 if (QApplication::overrideCursor())
307 QApplication::restoreOverrideCursor();
309 QString context("updating ");
310 if (git->isThrowOnStopRaised(i, context + metaObject()->className())) {
311 EM_THROW_PENDING;
312 return;
314 if (isThrowOnDeleteRaised(i, context + metaObject()->className())) {
315 EM_THROW_PENDING;
316 return;
318 if (i == exCancelRequest)
319 EM_THROW_PENDING;
320 // do not return
321 else {
322 const QString info("Exception \'" + EM_DESC(i) + "\' "
323 "not handled in init...re-throw");
324 dbs(info);
325 throw;
328 bool nextRequestPending = flushQueue();
330 if (!nextRequestPending && !statusBarRequest.isEmpty()) {
331 // update status bar when we are sure no more work is pending
332 QApplication::postEvent(m(), new MessageEvent(statusBarRequest));
333 statusBarRequest = "";
335 if (!nextRequestPending && popupType)
336 sendPopupEvent();
339 void Domain::sendPopupEvent() {
341 // call an async context popup, must be executed
342 // after returning to event loop
343 DeferredPopupEvent* e = new DeferredPopupEvent(popupData, popupType);
344 QApplication::postEvent(m(), e);
345 popupType = 0;
348 void Domain::on_contextMenu(const QString& data, int type) {
350 popupType = type;
351 popupData = data;
353 if (busy)
354 return; // we are in the middle of an update
356 // if list view is already updated pop-up
357 // context menu, otherwise it means update()
358 // has still not been called, a pop-up request,
359 // will be fired up at the end of next update()
360 if ((type == POPUP_LIST_EV) && (data != st.sha()))
361 return;
363 sendPopupEvent();