Added a framework to specify different debugger drivers on the command line.
[kdbg.git] / kdbg / mainwndbase.cpp
blob2942bc99cca509f059f86aff2d8b769c6a87921e
1 // $Id$
3 // Copyright by Johannes Sixt
4 // This file is under GPL, the GNU General Public Licence
6 #include <kapp.h>
7 #if QT_VERSION >= 200
8 #include <klocale.h> /* i18n */
9 #include <kconfig.h>
10 #include <kmessagebox.h>
11 #else
12 #include <kmsgbox.h>
13 #include <qkeycode.h>
14 #endif
15 #include <kiconloader.h>
16 #include <kstatusbar.h>
17 #include <ktoolbar.h>
18 #include <kfiledialog.h>
19 #include <qpainter.h>
20 #include <qtabdialog.h>
21 #include <qfileinfo.h>
22 #include "mainwndbase.h"
23 #include "debugger.h"
24 #include "gdbdriver.h"
25 #include "prefdebugger.h"
26 #include "prefmisc.h"
27 #include "ttywnd.h"
28 #include "updateui.h"
29 #include "commandids.h"
30 #include "valarray.h"
31 #ifdef HAVE_CONFIG
32 #include "config.h"
33 #endif
34 #include "mydebug.h"
35 #ifdef HAVE_SYS_STAT_H
36 #include <sys/stat.h> /* mknod(2) */
37 #endif
38 #ifdef HAVE_FCNTL_H
39 #include <fcntl.h> /* open(2) */
40 #endif
41 #ifdef HAVE_UNISTD_H
42 #include <unistd.h> /* getpid, unlink etc. */
43 #endif
45 #define MAX_RECENT_FILES 4
47 WatchWindow::WatchWindow(QWidget* parent, const char* name, WFlags f) :
48 QWidget(parent, name, f),
49 m_watchEdit(this, "watch_edit"),
50 m_watchAdd(i18n(" Add "), this, "watch_add"),
51 m_watchDelete(i18n(" Del "), this, "watch_delete"),
52 m_watchVariables(this, "watch_variables"),
53 m_watchV(this, 0),
54 m_watchH(0)
56 // setup the layout
57 m_watchAdd.setMinimumSize(m_watchAdd.sizeHint());
58 m_watchDelete.setMinimumSize(m_watchDelete.sizeHint());
59 m_watchV.addLayout(&m_watchH, 0);
60 m_watchV.addWidget(&m_watchVariables, 10);
61 m_watchH.addWidget(&m_watchEdit, 10);
62 m_watchH.addWidget(&m_watchAdd, 0);
63 m_watchH.addWidget(&m_watchDelete, 0);
65 connect(&m_watchEdit, SIGNAL(returnPressed()), SIGNAL(addWatch()));
66 connect(&m_watchAdd, SIGNAL(clicked()), SIGNAL(addWatch()));
67 connect(&m_watchDelete, SIGNAL(clicked()), SIGNAL(deleteWatch()));
68 connect(&m_watchVariables, SIGNAL(highlighted(int)), SLOT(slotWatchHighlighted(int)));
70 m_watchVariables.setMoveCurrentToSibling(true);
71 m_watchVariables.installEventFilter(this);
74 WatchWindow::~WatchWindow()
78 bool WatchWindow::eventFilter(QObject*, QEvent* ev)
80 if (ev->type() ==
81 #if QT_VERSION < 200
82 Event_KeyPress
83 #else
84 QEvent::KeyPress
85 #endif
88 QKeyEvent* kev = static_cast<QKeyEvent*>(ev);
89 if (kev->key() == Key_Delete) {
90 emit deleteWatch();
91 return true;
94 return false;
98 // place the text of the hightlighted watch expr in the edit field
99 void WatchWindow::slotWatchHighlighted(int idx)
101 QString text = m_watchVariables.exprStringAt(idx);
102 m_watchEdit.setText(text);
106 static void splitCmdStr(const QString& cmd, ValArray<QString>& parts)
108 QString str = cmd.simplifyWhiteSpace();
109 int start = 0;
110 int end;
111 while ((end = str.find(' ', start)) >= 0) {
112 parts.append(str.mid(start, end-start));
113 start = end+1;
115 parts.append(str.mid(start, str.length()-start));
119 const char defaultTermCmdStr[] = "xterm -name kdbgio -title %T -e sh -c %C";
120 const char defaultSourceFilter[] = "*.c *.cc *.cpp *.c++ *.C *.CC";
121 const char defaultHeaderFilter[] = "*.h *.hh *.hpp *.h++";
124 DebuggerMainWndBase::DebuggerMainWndBase() :
125 m_animationCounter(0),
126 m_outputTermCmdStr(defaultTermCmdStr),
127 m_outputTermProc(0),
128 m_ttyLevel(-1), /* no tty yet */
129 #ifdef GDB_TRANSCRIPT
130 m_transcriptFile(GDB_TRANSCRIPT),
131 #endif
132 m_popForeground(false),
133 m_backTimeout(1000),
134 m_tabWidth(0),
135 m_sourceFilter(defaultSourceFilter),
136 m_headerFilter(defaultHeaderFilter),
137 m_debugger(0)
139 m_statusActive = i18n("active");
140 m_recentExecList.setAutoDelete(true);
143 DebuggerMainWndBase::~DebuggerMainWndBase()
145 delete m_debugger;
147 // if the output window is open, close it
148 if (m_outputTermProc != 0) {
149 m_outputTermProc->disconnect(); /* ignore signals */
150 m_outputTermProc->kill();
151 shutdownTermWindow();
155 void DebuggerMainWndBase::setupDebugger(QWidget* parent,
156 ExprWnd* localVars,
157 ExprWnd* watchVars,
158 QListBox* backtrace)
160 m_debugger = new KDebugger(parent, localVars, watchVars, backtrace);
162 QObject::connect(m_debugger, SIGNAL(updateStatusMessage()),
163 parent, SLOT(slotNewStatusMsg()));
164 QObject::connect(m_debugger, SIGNAL(updateUI()),
165 parent, SLOT(updateUI()));
167 QObject::connect(m_debugger, SIGNAL(lineItemsChanged()),
168 parent, SLOT(updateLineItems()));
170 QObject::connect(m_debugger, SIGNAL(animationTimeout()),
171 parent, SLOT(slotAnimationTimeout()));
172 QObject::connect(m_debugger, SIGNAL(debuggerStarting()),
173 parent, SLOT(slotDebuggerStarting()));
175 m_debugger->setDebuggerCmd(m_debuggerCmdStr);
179 void DebuggerMainWndBase::setCoreFile(const QString& corefile)
181 assert(m_debugger != 0);
182 m_debugger->useCoreFile(corefile, true);
185 void DebuggerMainWndBase::setRemoteDevice(const QString& remoteDevice)
187 if (m_debugger != 0) {
188 m_debugger->setRemoteDevice(remoteDevice);
192 void DebuggerMainWndBase::setTranscript(const char* name)
194 m_transcriptFile = name;
195 if (m_debugger != 0 && m_debugger->driver() != 0)
196 m_debugger->driver()->setLogFileName(m_transcriptFile);
199 void DebuggerMainWndBase::setLanguage(const QCString& lang)
201 m_language = lang.lower();
204 const char OutputWindowGroup[] = "OutputWindow";
205 const char TermCmdStr[] = "TermCmdStr";
206 const char KeepScript[] = "KeepScript";
207 const char DebuggerGroup[] = "Debugger";
208 const char DebuggerCmdStr[] = "DebuggerCmdStr";
209 const char PreferencesGroup[] = "Preferences";
210 const char PopForeground[] = "PopForeground";
211 const char BackTimeout[] = "BackTimeout";
212 const char TabWidth[] = "TabWidth";
213 const char FilesGroup[] = "Files";
214 const char RecentExecutables[] = "RecentExecutables";
215 const char SourceFileFilter[] = "SourceFileFilter";
216 const char HeaderFileFilter[] = "HeaderFileFilter";
218 void DebuggerMainWndBase::saveSettings(KConfig* config)
220 if (m_debugger != 0) {
221 m_debugger->saveSettings(config);
224 KConfigGroupSaver g(config, OutputWindowGroup);
225 config->writeEntry(TermCmdStr, m_outputTermCmdStr);
227 config->setGroup(DebuggerGroup);
228 config->writeEntry(DebuggerCmdStr, m_debuggerCmdStr);
230 config->setGroup(PreferencesGroup);
231 config->writeEntry(PopForeground, m_popForeground);
232 config->writeEntry(BackTimeout, m_backTimeout);
233 config->writeEntry(TabWidth, m_tabWidth);
234 config->writeEntry(SourceFileFilter, m_sourceFilter);
235 config->writeEntry(HeaderFileFilter, m_headerFilter);
237 config->setGroup(FilesGroup);
238 config->writeEntry(RecentExecutables, m_recentExecList, ',');
241 void DebuggerMainWndBase::restoreSettings(KConfig* config)
243 if (m_debugger != 0) {
244 m_debugger->restoreSettings(config);
247 KConfigGroupSaver g(config, OutputWindowGroup);
249 * For debugging and emergency purposes, let the config file override
250 * the shell script that is used to keep the output window open. This
251 * string must have EXACTLY 1 %s sequence in it.
253 setTerminalCmd(config->readEntry(TermCmdStr, defaultTermCmdStr));
254 m_outputTermKeepScript = config->readEntry(KeepScript);
256 config->setGroup(DebuggerGroup);
257 setDebuggerCmdStr(config->readEntry(DebuggerCmdStr));
259 config->setGroup(PreferencesGroup);
260 m_popForeground = config->readBoolEntry(PopForeground, false);
261 m_backTimeout = config->readNumEntry(BackTimeout, 1000);
262 m_tabWidth = config->readNumEntry(TabWidth, 0);
263 m_sourceFilter = config->readEntry(SourceFileFilter, m_sourceFilter);
264 m_headerFilter = config->readEntry(HeaderFileFilter, m_headerFilter);
266 config->setGroup(FilesGroup);
267 config->readListEntry(RecentExecutables, m_recentExecList,',');
270 bool DebuggerMainWndBase::debugProgram(const QString& executable, QWidget* parent)
272 assert(m_debugger != 0);
274 DebuggerDriver* driver = driverFromLang(m_language);
275 if (driver == 0)
277 // oops
278 QString msg = i18n("Don't know how to debug language `%1'");
279 KMessageBox::sorry(parent, msg.arg(m_language));
280 return false;
283 driver->setLogFileName(m_transcriptFile);
285 bool success = m_debugger->debugProgram(executable, driver);
287 if (!success)
289 delete driver;
291 QString msg = i18n("Could not start the debugger process.\n"
292 "Please shut down KDbg and resolve the problem.");
293 KMessageBox::sorry(parent, msg);
296 return success;
299 // derive driver from language
300 DebuggerDriver* DebuggerMainWndBase::driverFromLang(const QCString& lang)
302 // lang must be in all lowercase
303 assert(lang == lang.lower());
305 // The following table relates languages and debugger drivers
306 static const struct L {
307 const char* shortest; // abbreviated to this is still unique
308 const char* full; // full name of language
309 int driver;
310 } langs[] = {
311 { "c", "c++", 1 },
312 { "f", "fortran", 1 },
313 { "p", "python", 3 },
314 { "x", "xsl", 2 },
316 const int N = sizeof(langs)/sizeof(langs[0]);
318 // lookup the language name
319 // note that it has been set to lower-case in setLanguage()
320 int driverID = 0;
321 for (int i = 0; i < N; i++)
323 const L& l = langs[i];
325 // shortest must match
326 if (strncmp(l.shortest, lang, strlen(l.shortest)) != 0)
327 continue;
329 // lang must not be longer than the full name, and it must match
330 if (lang.length() <= strlen(l.full) &&
331 strncmp(l.full, lang, lang.length()) == 0)
333 driverID = l.driver;
334 break;
337 DebuggerDriver* driver = 0;
338 switch (driverID) {
339 case 1:
340 driver = new GdbDriver;
341 break;
342 default:
343 // unknown language
344 break;
346 return driver;
349 // helper that gets a file name (it only differs in the caption of the dialog)
350 QString DebuggerMainWndBase::myGetFileName(QString caption,
351 QString dir, QString filter,
352 QWidget* parent)
354 QString filename;
355 KFileDialog dlg(dir, filter, parent, "filedialog", true);
357 dlg.setCaption(caption);
359 if (dlg.exec() == QDialog::Accepted)
360 filename = dlg.selectedFile();
362 return filename;
365 void DebuggerMainWndBase::updateUIItem(UpdateUI* item)
367 switch (item->id) {
368 case ID_FILE_EXECUTABLE:
369 item->enable(m_debugger->isIdle());
370 break;
371 case ID_FILE_PROG_SETTINGS:
372 item->enable(m_debugger->haveExecutable());
373 break;
374 case ID_FILE_COREFILE:
375 item->enable(m_debugger->canUseCoreFile());
376 break;
377 case ID_PROGRAM_STEP:
378 case ID_PROGRAM_STEPI:
379 case ID_PROGRAM_NEXT:
380 case ID_PROGRAM_NEXTI:
381 case ID_PROGRAM_FINISH:
382 case ID_PROGRAM_UNTIL:
383 case ID_PROGRAM_RUN_AGAIN:
384 item->enable(m_debugger->canSingleStep());
385 break;
386 case ID_PROGRAM_ATTACH:
387 case ID_PROGRAM_RUN:
388 item->enable(m_debugger->isReady());
389 break;
390 case ID_PROGRAM_KILL:
391 item->enable(m_debugger->haveExecutable() && m_debugger->isProgramActive());
392 break;
393 case ID_PROGRAM_BREAK:
394 item->enable(m_debugger->isProgramRunning());
395 break;
396 case ID_PROGRAM_ARGS:
397 item->enable(m_debugger->haveExecutable());
398 break;
401 // update statusbar
402 dbgStatusBar()->changeItem(m_debugger->isProgramActive() ?
403 static_cast<const char*>(m_statusActive) : "",
404 ID_STATUS_ACTIVE);
407 void DebuggerMainWndBase::updateLineItems()
411 void DebuggerMainWndBase::initAnimation()
413 #if QT_VERSION < 200
414 QString path = kapp->kde_datadir() + "/kfm/pics/";
415 QPixmap pixmap;
416 pixmap.load(path + "/kde1.xpm");
417 int numPix = 9;
418 #else
419 QPixmap pixmap = BarIcon("kde1");
420 int numPix = 6;
421 #endif
423 KToolBar* toolbar = dbgToolBar();
424 toolbar->insertButton(pixmap, ID_STATUS_BUSY);
425 toolbar->alignItemRight(ID_STATUS_BUSY, true);
427 // Load animated logo
428 m_animation.setAutoDelete(true);
429 QString n;
430 for (int i = 1; i <= numPix; i++) {
431 #if QT_VERSION < 200
432 n.sprintf("/kde%d.xpm", i);
433 QPixmap* p = new QPixmap();
434 p->load(path + n);
435 #else
436 n.sprintf("kde%d", i);
437 QPixmap* p = new QPixmap(BarIcon(n));
438 #endif
439 if (!p->isNull()) {
440 m_animation.append(p);
441 } else {
442 delete p;
445 // safeguard: if we did not find a single icon, add a dummy
446 if (m_animation.count() == 0) {
447 QPixmap* pix = new QPixmap(2,2);
448 QPainter p(pix);
449 #if QT_VERSION < 200
450 p.fillRect(0,0,2,2,QBrush(white));
451 #else
452 p.fillRect(0,0,2,2,QBrush(Qt::white));
453 #endif
454 m_animation.append(pix);
458 void DebuggerMainWndBase::slotAnimationTimeout()
460 assert(m_animation.count() > 0); /* must have been initialized */
461 m_animationCounter++;
462 if (m_animationCounter == m_animation.count())
463 m_animationCounter = 0;
464 dbgToolBar()->setButtonPixmap(ID_STATUS_BUSY,
465 *m_animation.at(m_animationCounter));
468 void DebuggerMainWndBase::slotNewStatusMsg()
470 QString msg = m_debugger->statusMessage();
471 dbgStatusBar()->changeItem(msg, ID_STATUS_MSG);
474 void DebuggerMainWndBase::doGlobalOptions(QWidget* parent)
476 QTabDialog dlg(parent, "global_options", true);
477 QString title = kapp->getCaption();
478 title += i18n(": Global options");
479 dlg.setCaption(title);
480 dlg.setCancelButton(i18n("Cancel"));
481 dlg.setOKButton(i18n("OK"));
483 PrefDebugger prefDebugger(&dlg);
484 prefDebugger.setDebuggerCmd(m_debuggerCmdStr.isEmpty() ?
485 GdbDriver::defaultGdb() : m_debuggerCmdStr);
486 prefDebugger.setTerminal(m_outputTermCmdStr);
488 PrefMisc prefMisc(&dlg);
489 prefMisc.setPopIntoForeground(m_popForeground);
490 prefMisc.setBackTimeout(m_backTimeout);
491 prefMisc.setTabWidth(m_tabWidth);
492 prefMisc.setSourceFilter(m_sourceFilter);
493 prefMisc.setHeaderFilter(m_headerFilter);
495 dlg.addTab(&prefDebugger, i18n("&Debugger"));
496 dlg.addTab(&prefMisc, i18n("&Miscellaneous"));
497 if (dlg.exec() == QDialog::Accepted)
499 setDebuggerCmdStr(prefDebugger.debuggerCmd());
500 setTerminalCmd(prefDebugger.terminal());
501 m_popForeground = prefMisc.popIntoForeground();
502 m_backTimeout = prefMisc.backTimeout();
503 m_tabWidth = prefMisc.tabWidth();
504 m_sourceFilter = prefMisc.sourceFilter();
505 if (m_sourceFilter.isEmpty())
506 m_sourceFilter = defaultSourceFilter;
507 m_headerFilter = prefMisc.headerFilter();
508 if (m_headerFilter.isEmpty())
509 m_headerFilter = defaultHeaderFilter;
513 const char fifoNameBase[] = "/tmp/kdbgttywin%05d";
516 * We use the scope operator :: in this function, so that we don't
517 * accidentally use the wrong close() function (I've been bitten ;-),
518 * outch!) (We use it for all the libc functions, to be consistent...)
520 QString DebuggerMainWndBase::createOutputWindow()
522 // create a name for a fifo
523 QString fifoName;
524 fifoName.sprintf(fifoNameBase, ::getpid());
526 // create a fifo that will pass in the tty name
527 ::unlink(fifoName); /* remove remnants */
528 #ifdef HAVE_MKFIFO
529 if (::mkfifo(fifoName, S_IRUSR|S_IWUSR) < 0) {
530 // failed
531 TRACE("mkfifo " + fifoName + " failed");
532 return QString();
534 #else
535 if (::mknod(fifoName, S_IFIFO | S_IRUSR|S_IWUSR, 0) < 0) {
536 // failed
537 TRACE("mknod " + fifoName + " failed");
538 return QString();
540 #endif
542 m_outputTermProc = new KProcess;
546 * Spawn an xterm that in turn runs a shell script that passes us
547 * back the terminal name and then only sits and waits.
549 static const char shellScriptFmt[] =
550 "tty>%s;"
551 "trap \"\" INT QUIT TSTP;" /* ignore various signals */
552 "exec<&-;exec>&-;" /* close stdin and stdout */
553 "while :;do sleep 3600;done";
554 // let config file override this script
555 const char* fmt = shellScriptFmt;
556 if (m_outputTermKeepScript.length() != 0) {
557 fmt = m_outputTermKeepScript.data();
559 #if QT_VERSION < 200
560 QString shellScript(strlen(fmt) + fifoName.length());
561 #else
562 QString shellScript;
563 #endif
564 shellScript.sprintf(fmt, fifoName.data());
565 TRACE("output window script is " + shellScript);
567 QString title = kapp->getCaption();
568 title += i18n(": Program output");
570 // parse the command line specified in the preferences
571 ValArray<QString> cmdParts;
572 splitCmdStr(m_outputTermCmdStr, cmdParts);
575 * Build the argv array. Thereby substitute special sequences:
577 struct {
578 char seq[4];
579 QString replace;
580 } substitute[] = {
581 { "%T", title },
582 { "%C", shellScript }
585 for (int i = 0; i < cmdParts.size(); i++) {
586 QString& str = cmdParts[i];
587 for (int j = sizeof(substitute)/sizeof(substitute[0])-1; j >= 0; j--) {
588 int pos = str.find(substitute[j].seq);
589 if (pos >= 0) {
590 str.replace(pos, 2, substitute[j].replace);
591 break; /* substitute only one sequence */
594 *m_outputTermProc << str;
599 if (m_outputTermProc->start())
601 // read the ttyname from the fifo
602 int f = ::open(fifoName, O_RDONLY);
603 if (f < 0) {
604 // error
605 ::unlink(fifoName);
606 return QString();
609 char ttyname[50];
610 int n = ::read(f, ttyname, sizeof(ttyname)-sizeof(char)); /* leave space for '\0' */
612 ::close(f);
613 ::unlink(fifoName);
615 if (n < 0) {
616 // error
617 return QString();
620 // remove whitespace
621 ttyname[n] = '\0';
622 QString tty = QString(ttyname).stripWhiteSpace();
623 TRACE("tty=" + tty);
624 return tty;
626 else
628 // error, could not start xterm
629 TRACE("fork failed for fifo " + fifoName);
630 ::unlink(fifoName);
631 shutdownTermWindow();
632 return QString();
636 void DebuggerMainWndBase::shutdownTermWindow()
638 delete m_outputTermProc;
639 m_outputTermProc = 0;
642 void DebuggerMainWndBase::setTerminalCmd(const QString& cmd)
644 m_outputTermCmdStr = cmd;
645 // revert to default if empty
646 if (m_outputTermCmdStr.isEmpty()) {
647 m_outputTermCmdStr = defaultTermCmdStr;
651 void DebuggerMainWndBase::slotDebuggerStarting()
653 if (m_debugger == 0) /* paranoia check */
654 return;
656 if (m_ttyLevel == m_debugger->ttyLevel())
659 else
661 // shut down terminal emulations we will not need
662 switch (m_ttyLevel) {
663 case KDebugger::ttySimpleOutputOnly:
664 ttyWindow()->deactivate();
665 break;
666 case KDebugger::ttyFull:
667 if (m_outputTermProc != 0) {
668 m_outputTermProc->kill();
669 // will be deleted in slot
671 break;
672 default: break;
675 m_ttyLevel = m_debugger->ttyLevel();
677 QString ttyName;
678 switch (m_ttyLevel) {
679 case KDebugger::ttySimpleOutputOnly:
680 ttyName = ttyWindow()->activate();
681 break;
682 case KDebugger::ttyFull:
683 if (m_outputTermProc == 0) {
684 // create an output window
685 ttyName = createOutputWindow();
686 TRACE(ttyName.isEmpty() ?
687 "createOuputWindow failed" : "successfully created output window");
689 break;
690 default: break;
693 m_debugger->setTerminal(ttyName);
697 void DebuggerMainWndBase::setDebuggerCmdStr(const QString& cmd)
699 m_debuggerCmdStr = cmd;
700 // make empty if it is the default
701 if (m_debuggerCmdStr == GdbDriver::defaultGdb()) {
702 m_debuggerCmdStr = QString();
704 if (m_debugger != 0) {
705 m_debugger->setDebuggerCmd(m_debuggerCmdStr);
709 void DebuggerMainWndBase::addRecentExec(const QString& executable)
711 int pos = m_recentExecList.find(executable);
712 if (pos != 0) {
713 // move to top
714 if (pos > 0)
715 m_recentExecList.remove(pos);
716 // else entry is new
718 // insert on top
719 m_recentExecList.insert(0, executable);
720 } // else pos == 0, which means we dont need to change the list
722 // shorten list
723 while (m_recentExecList.count() > MAX_RECENT_FILES) {
724 m_recentExecList.remove(MAX_RECENT_FILES);
728 void DebuggerMainWndBase::removeRecentExec(const QString& executable)
730 int pos = m_recentExecList.find(executable);
731 if (pos >= 0) {
732 m_recentExecList.remove(pos);
736 bool DebuggerMainWndBase::debugProgramInteractive(const QString& executable,
737 QWidget* parent)
739 // check the file name
740 QFileInfo fi(executable);
741 m_lastDirectory = fi.dirPath(true);
743 if (!fi.isFile()) {
744 QString msgFmt = i18n("`%s' is not a file or does not exist");
745 SIZED_QString(msg, msgFmt.length() + executable.length() + 20);
746 #if QT_VERSION < 200
747 msg.sprintf(msgFmt, executable.data());
748 KMsgBox::message(parent, kapp->appName(),
749 msg,
750 KMsgBox::STOP,
751 i18n("OK"));
752 #else
753 msg.sprintf(msgFmt, executable.latin1());
754 KMessageBox::sorry(parent, msg);
755 #endif
756 return false;
759 return debugProgram(executable, parent);
763 #include "mainwndbase.moc"