The driver name is now stored in the per-program config file.
[kdbg.git] / kdbg / mainwndbase.cpp
blobb7d5bfe3f3e5f2f2739b3cdcc8ed31bf51063839
1 // $Id$
3 // Copyright by Johannes Sixt
4 // This file is under GPL, the GNU General Public Licence
6 #include <kapp.h>
7 #include <klocale.h> /* i18n */
8 #include <kconfig.h>
9 #include <kmessagebox.h>
10 #include <kiconloader.h>
11 #include <kstatusbar.h>
12 #include <ktoolbar.h>
13 #include <kfiledialog.h>
14 #include <qpainter.h>
15 #include <qtabdialog.h>
16 #include <qfile.h>
17 #include "mainwndbase.h"
18 #include "debugger.h"
19 #include "gdbdriver.h"
20 #include "xsldbgdriver.h"
21 #include "prefdebugger.h"
22 #include "prefmisc.h"
23 #include "ttywnd.h"
24 #include "updateui.h"
25 #include "commandids.h"
26 #include "valarray.h"
27 #ifdef HAVE_CONFIG
28 #include "config.h"
29 #endif
30 #include "mydebug.h"
31 #ifdef HAVE_SYS_STAT_H
32 #include <sys/stat.h> /* mknod(2) */
33 #endif
34 #ifdef HAVE_FCNTL_H
35 #include <fcntl.h> /* open(2) */
36 #endif
37 #ifdef HAVE_UNISTD_H
38 #include <unistd.h> /* getpid, unlink etc. */
39 #endif
41 #define MAX_RECENT_FILES 4
43 WatchWindow::WatchWindow(QWidget* parent, const char* name, WFlags f) :
44 QWidget(parent, name, f),
45 m_watchEdit(this, "watch_edit"),
46 m_watchAdd(i18n(" Add "), this, "watch_add"),
47 m_watchDelete(i18n(" Del "), this, "watch_delete"),
48 m_watchVariables(this, "watch_variables"),
49 m_watchV(this, 0),
50 m_watchH(0)
52 // setup the layout
53 m_watchAdd.setMinimumSize(m_watchAdd.sizeHint());
54 m_watchDelete.setMinimumSize(m_watchDelete.sizeHint());
55 m_watchV.addLayout(&m_watchH, 0);
56 m_watchV.addWidget(&m_watchVariables, 10);
57 m_watchH.addWidget(&m_watchEdit, 10);
58 m_watchH.addWidget(&m_watchAdd, 0);
59 m_watchH.addWidget(&m_watchDelete, 0);
61 connect(&m_watchEdit, SIGNAL(returnPressed()), SIGNAL(addWatch()));
62 connect(&m_watchAdd, SIGNAL(clicked()), SIGNAL(addWatch()));
63 connect(&m_watchDelete, SIGNAL(clicked()), SIGNAL(deleteWatch()));
64 connect(&m_watchVariables, SIGNAL(highlighted(int)), SLOT(slotWatchHighlighted(int)));
66 m_watchVariables.setMoveCurrentToSibling(true);
67 m_watchVariables.installEventFilter(this);
70 WatchWindow::~WatchWindow()
74 bool WatchWindow::eventFilter(QObject*, QEvent* ev)
76 if (ev->type() == QEvent::KeyPress)
78 QKeyEvent* kev = static_cast<QKeyEvent*>(ev);
79 if (kev->key() == Key_Delete) {
80 emit deleteWatch();
81 return true;
84 return false;
88 // place the text of the hightlighted watch expr in the edit field
89 void WatchWindow::slotWatchHighlighted(int idx)
91 QString text = m_watchVariables.exprStringAt(idx);
92 m_watchEdit.setText(text);
96 static void splitCmdStr(const QString& cmd, ValArray<QString>& parts)
98 QString str = cmd.simplifyWhiteSpace();
99 int start = 0;
100 int end;
101 while ((end = str.find(' ', start)) >= 0) {
102 parts.append(str.mid(start, end-start));
103 start = end+1;
105 parts.append(str.mid(start, str.length()-start));
109 const char defaultTermCmdStr[] = "xterm -name kdbgio -title %T -e sh -c %C";
110 const char defaultSourceFilter[] = "*.c *.cc *.cpp *.c++ *.C *.CC";
111 const char defaultHeaderFilter[] = "*.h *.hh *.hpp *.h++";
114 DebuggerMainWndBase::DebuggerMainWndBase() :
115 m_animationCounter(0),
116 m_outputTermCmdStr(defaultTermCmdStr),
117 m_outputTermProc(0),
118 m_ttyLevel(-1), /* no tty yet */
119 #ifdef GDB_TRANSCRIPT
120 m_transcriptFile(GDB_TRANSCRIPT),
121 #endif
122 m_popForeground(false),
123 m_backTimeout(1000),
124 m_tabWidth(0),
125 m_sourceFilter(defaultSourceFilter),
126 m_headerFilter(defaultHeaderFilter),
127 m_debugger(0)
129 m_statusActive = i18n("active");
130 m_recentExecList.setAutoDelete(true);
133 DebuggerMainWndBase::~DebuggerMainWndBase()
135 delete m_debugger;
137 // if the output window is open, close it
138 if (m_outputTermProc != 0) {
139 m_outputTermProc->disconnect(); /* ignore signals */
140 m_outputTermProc->kill();
141 shutdownTermWindow();
145 void DebuggerMainWndBase::setupDebugger(QWidget* parent,
146 ExprWnd* localVars,
147 ExprWnd* watchVars,
148 QListBox* backtrace)
150 m_debugger = new KDebugger(parent, localVars, watchVars, backtrace);
152 QObject::connect(m_debugger, SIGNAL(updateStatusMessage()),
153 parent, SLOT(slotNewStatusMsg()));
154 QObject::connect(m_debugger, SIGNAL(updateUI()),
155 parent, SLOT(updateUI()));
157 QObject::connect(m_debugger, SIGNAL(lineItemsChanged()),
158 parent, SLOT(updateLineItems()));
160 QObject::connect(m_debugger, SIGNAL(animationTimeout()),
161 parent, SLOT(slotAnimationTimeout()));
162 QObject::connect(m_debugger, SIGNAL(debuggerStarting()),
163 parent, SLOT(slotDebuggerStarting()));
167 void DebuggerMainWndBase::setCoreFile(const QString& corefile)
169 assert(m_debugger != 0);
170 m_debugger->useCoreFile(corefile, true);
173 void DebuggerMainWndBase::setRemoteDevice(const QString& remoteDevice)
175 if (m_debugger != 0) {
176 m_debugger->setRemoteDevice(remoteDevice);
180 void DebuggerMainWndBase::setTranscript(const char* name)
182 m_transcriptFile = name;
183 if (m_debugger != 0 && m_debugger->driver() != 0)
184 m_debugger->driver()->setLogFileName(m_transcriptFile);
187 const char OutputWindowGroup[] = "OutputWindow";
188 const char TermCmdStr[] = "TermCmdStr";
189 const char KeepScript[] = "KeepScript";
190 const char DebuggerGroup[] = "Debugger";
191 const char DebuggerCmdStr[] = "DebuggerCmdStr";
192 const char PreferencesGroup[] = "Preferences";
193 const char PopForeground[] = "PopForeground";
194 const char BackTimeout[] = "BackTimeout";
195 const char TabWidth[] = "TabWidth";
196 const char FilesGroup[] = "Files";
197 const char RecentExecutables[] = "RecentExecutables";
198 const char SourceFileFilter[] = "SourceFileFilter";
199 const char HeaderFileFilter[] = "HeaderFileFilter";
200 const char GeneralGroup[] = "General";
202 void DebuggerMainWndBase::saveSettings(KConfig* config)
204 if (m_debugger != 0) {
205 m_debugger->saveSettings(config);
208 KConfigGroupSaver g(config, OutputWindowGroup);
209 config->writeEntry(TermCmdStr, m_outputTermCmdStr);
211 config->setGroup(DebuggerGroup);
212 config->writeEntry(DebuggerCmdStr, m_debuggerCmdStr);
214 config->setGroup(PreferencesGroup);
215 config->writeEntry(PopForeground, m_popForeground);
216 config->writeEntry(BackTimeout, m_backTimeout);
217 config->writeEntry(TabWidth, m_tabWidth);
218 config->writeEntry(SourceFileFilter, m_sourceFilter);
219 config->writeEntry(HeaderFileFilter, m_headerFilter);
221 config->setGroup(FilesGroup);
222 config->writeEntry(RecentExecutables, m_recentExecList, ',');
225 void DebuggerMainWndBase::restoreSettings(KConfig* config)
227 if (m_debugger != 0) {
228 m_debugger->restoreSettings(config);
231 KConfigGroupSaver g(config, OutputWindowGroup);
233 * For debugging and emergency purposes, let the config file override
234 * the shell script that is used to keep the output window open. This
235 * string must have EXACTLY 1 %s sequence in it.
237 setTerminalCmd(config->readEntry(TermCmdStr, defaultTermCmdStr));
238 m_outputTermKeepScript = config->readEntry(KeepScript);
240 config->setGroup(DebuggerGroup);
241 setDebuggerCmdStr(config->readEntry(DebuggerCmdStr));
243 config->setGroup(PreferencesGroup);
244 m_popForeground = config->readBoolEntry(PopForeground, false);
245 m_backTimeout = config->readNumEntry(BackTimeout, 1000);
246 m_tabWidth = config->readNumEntry(TabWidth, 0);
247 m_sourceFilter = config->readEntry(SourceFileFilter, m_sourceFilter);
248 m_headerFilter = config->readEntry(HeaderFileFilter, m_headerFilter);
250 config->setGroup(FilesGroup);
251 config->readListEntry(RecentExecutables, m_recentExecList,',');
254 bool DebuggerMainWndBase::debugProgram(const QString& executable,
255 QCString lang, QWidget* parent)
257 assert(m_debugger != 0);
259 TRACE(QString().sprintf("trying language '%s'...", lang.data()));
260 DebuggerDriver* driver = driverFromLang(lang);
262 if (driver == 0)
264 // see if there is a language in the per-program config file
265 QString configName = m_debugger->getConfigForExe(executable);
266 if (QFile::exists(configName))
268 KSimpleConfig c(configName, true); // read-only
269 c.setGroup(GeneralGroup);
271 // Using "GDB" as default here is for backwards compatibility:
272 // The config file exists but doesn't have an entry,
273 // so it must have been created by an old version of KDbg
274 // that had only the GDB driver.
275 lang = c.readEntry(KDebugger::DriverNameEntry, "GDB").latin1();
277 TRACE(QString().sprintf("...bad, trying config driver %s...",
278 lang.data()));
279 driver = driverFromLang(lang);
283 if (driver == 0)
285 QCString name = driverNameFromFile(executable);
287 TRACE(QString().sprintf("...no luck, trying %s derived"
288 " from file contents", name.data()));
289 driver = driverFromLang(name);
291 if (driver == 0)
293 // oops
294 QString msg = i18n("Don't know how to debug language `%1'");
295 KMessageBox::sorry(parent, msg.arg(lang));
296 return false;
299 driver->setLogFileName(m_transcriptFile);
301 bool success = m_debugger->debugProgram(executable, driver);
303 if (!success)
305 delete driver;
307 QString msg = i18n("Could not start the debugger process.\n"
308 "Please shut down KDbg and resolve the problem.");
309 KMessageBox::sorry(parent, msg);
312 return success;
315 // derive driver from language
316 DebuggerDriver* DebuggerMainWndBase::driverFromLang(QCString lang)
318 // lang is needed in all lowercase
319 lang = lang.lower();
321 // The following table relates languages and debugger drivers
322 static const struct L {
323 const char* shortest; // abbreviated to this is still unique
324 const char* full; // full name of language
325 int driver;
326 } langs[] = {
327 { "c", "c++", 1 },
328 { "f", "fortran", 1 },
329 { "p", "python", 3 },
330 { "x", "xslt", 2 },
331 // the following are actually driver names
332 { "gdb", "gdb", 1 },
333 { "xsldbg", "xsldbg", 2 },
335 const int N = sizeof(langs)/sizeof(langs[0]);
337 // lookup the language name
338 int driverID = 0;
339 for (int i = 0; i < N; i++)
341 const L& l = langs[i];
343 // shortest must match
344 if (strncmp(l.shortest, lang, strlen(l.shortest)) != 0)
345 continue;
347 // lang must not be longer than the full name, and it must match
348 if (lang.length() <= strlen(l.full) &&
349 strncmp(l.full, lang, lang.length()) == 0)
351 driverID = l.driver;
352 break;
355 DebuggerDriver* driver = 0;
356 switch (driverID) {
357 case 1:
359 GdbDriver* gdb = new GdbDriver;
360 gdb->setDefaultInvocation(m_debuggerCmdStr);
361 driver = gdb;
363 break;
364 case 2:
365 driver = new XsldbgDriver;
366 break;
367 default:
368 // unknown language
369 break;
371 return driver;
374 QCString DebuggerMainWndBase::driverNameFromFile(const QString& exe)
376 return "GDB";
379 // helper that gets a file name (it only differs in the caption of the dialog)
380 QString DebuggerMainWndBase::myGetFileName(QString caption,
381 QString dir, QString filter,
382 QWidget* parent)
384 QString filename;
385 KFileDialog dlg(dir, filter, parent, "filedialog", true);
387 dlg.setCaption(caption);
389 if (dlg.exec() == QDialog::Accepted)
390 filename = dlg.selectedFile();
392 return filename;
395 void DebuggerMainWndBase::updateUIItem(UpdateUI* item)
397 switch (item->id) {
398 case ID_FILE_EXECUTABLE:
399 item->enable(m_debugger->isIdle());
400 break;
401 case ID_FILE_PROG_SETTINGS:
402 item->enable(m_debugger->haveExecutable());
403 break;
404 case ID_FILE_COREFILE:
405 item->enable(m_debugger->canUseCoreFile());
406 break;
407 case ID_PROGRAM_STEP:
408 case ID_PROGRAM_STEPI:
409 case ID_PROGRAM_NEXT:
410 case ID_PROGRAM_NEXTI:
411 case ID_PROGRAM_FINISH:
412 case ID_PROGRAM_UNTIL:
413 case ID_PROGRAM_RUN_AGAIN:
414 item->enable(m_debugger->canSingleStep());
415 break;
416 case ID_PROGRAM_ATTACH:
417 case ID_PROGRAM_RUN:
418 item->enable(m_debugger->isReady());
419 break;
420 case ID_PROGRAM_KILL:
421 item->enable(m_debugger->haveExecutable() && m_debugger->isProgramActive());
422 break;
423 case ID_PROGRAM_BREAK:
424 item->enable(m_debugger->isProgramRunning());
425 break;
426 case ID_PROGRAM_ARGS:
427 item->enable(m_debugger->haveExecutable());
428 break;
431 // update statusbar
432 dbgStatusBar()->changeItem(m_debugger->isProgramActive() ?
433 static_cast<const char*>(m_statusActive) : "",
434 ID_STATUS_ACTIVE);
437 void DebuggerMainWndBase::updateLineItems()
441 void DebuggerMainWndBase::initAnimation()
443 QPixmap pixmap = BarIcon("kde1");
444 int numPix = 6;
446 KToolBar* toolbar = dbgToolBar();
447 toolbar->insertButton(pixmap, ID_STATUS_BUSY);
448 toolbar->alignItemRight(ID_STATUS_BUSY, true);
450 // Load animated logo
451 m_animation.setAutoDelete(true);
452 QString n;
453 for (int i = 1; i <= numPix; i++) {
454 n.sprintf("kde%d", i);
455 QPixmap* p = new QPixmap(BarIcon(n));
456 if (!p->isNull()) {
457 m_animation.append(p);
458 } else {
459 delete p;
462 // safeguard: if we did not find a single icon, add a dummy
463 if (m_animation.count() == 0) {
464 QPixmap* pix = new QPixmap(2,2);
465 QPainter p(pix);
466 p.fillRect(0,0,2,2,QBrush(Qt::white));
467 m_animation.append(pix);
471 void DebuggerMainWndBase::slotAnimationTimeout()
473 assert(m_animation.count() > 0); /* must have been initialized */
474 m_animationCounter++;
475 if (m_animationCounter == m_animation.count())
476 m_animationCounter = 0;
477 dbgToolBar()->setButtonPixmap(ID_STATUS_BUSY,
478 *m_animation.at(m_animationCounter));
481 void DebuggerMainWndBase::slotNewStatusMsg()
483 QString msg = m_debugger->statusMessage();
484 dbgStatusBar()->changeItem(msg, ID_STATUS_MSG);
487 void DebuggerMainWndBase::doGlobalOptions(QWidget* parent)
489 QTabDialog dlg(parent, "global_options", true);
490 QString title = kapp->caption();
491 title += i18n(": Global options");
492 dlg.setCaption(title);
493 dlg.setCancelButton(i18n("Cancel"));
494 dlg.setOKButton(i18n("OK"));
496 PrefDebugger prefDebugger(&dlg);
497 prefDebugger.setDebuggerCmd(m_debuggerCmdStr.isEmpty() ?
498 GdbDriver::defaultGdb() : m_debuggerCmdStr);
499 prefDebugger.setTerminal(m_outputTermCmdStr);
501 PrefMisc prefMisc(&dlg);
502 prefMisc.setPopIntoForeground(m_popForeground);
503 prefMisc.setBackTimeout(m_backTimeout);
504 prefMisc.setTabWidth(m_tabWidth);
505 prefMisc.setSourceFilter(m_sourceFilter);
506 prefMisc.setHeaderFilter(m_headerFilter);
508 dlg.addTab(&prefDebugger, i18n("&Debugger"));
509 dlg.addTab(&prefMisc, i18n("&Miscellaneous"));
510 if (dlg.exec() == QDialog::Accepted)
512 setDebuggerCmdStr(prefDebugger.debuggerCmd());
513 setTerminalCmd(prefDebugger.terminal());
514 m_popForeground = prefMisc.popIntoForeground();
515 m_backTimeout = prefMisc.backTimeout();
516 m_tabWidth = prefMisc.tabWidth();
517 m_sourceFilter = prefMisc.sourceFilter();
518 if (m_sourceFilter.isEmpty())
519 m_sourceFilter = defaultSourceFilter;
520 m_headerFilter = prefMisc.headerFilter();
521 if (m_headerFilter.isEmpty())
522 m_headerFilter = defaultHeaderFilter;
526 const char fifoNameBase[] = "/tmp/kdbgttywin%05d";
529 * We use the scope operator :: in this function, so that we don't
530 * accidentally use the wrong close() function (I've been bitten ;-),
531 * outch!) (We use it for all the libc functions, to be consistent...)
533 QString DebuggerMainWndBase::createOutputWindow()
535 // create a name for a fifo
536 QString fifoName;
537 fifoName.sprintf(fifoNameBase, ::getpid());
539 // create a fifo that will pass in the tty name
540 ::unlink(fifoName); /* remove remnants */
541 #ifdef HAVE_MKFIFO
542 if (::mkfifo(fifoName, S_IRUSR|S_IWUSR) < 0) {
543 // failed
544 TRACE("mkfifo " + fifoName + " failed");
545 return QString();
547 #else
548 if (::mknod(fifoName, S_IFIFO | S_IRUSR|S_IWUSR, 0) < 0) {
549 // failed
550 TRACE("mknod " + fifoName + " failed");
551 return QString();
553 #endif
555 m_outputTermProc = new KProcess;
559 * Spawn an xterm that in turn runs a shell script that passes us
560 * back the terminal name and then only sits and waits.
562 static const char shellScriptFmt[] =
563 "tty>%s;"
564 "trap \"\" INT QUIT TSTP;" /* ignore various signals */
565 "exec<&-;exec>&-;" /* close stdin and stdout */
566 "while :;do sleep 3600;done";
567 // let config file override this script
568 const char* fmt = shellScriptFmt;
569 if (m_outputTermKeepScript.length() != 0) {
570 fmt = m_outputTermKeepScript.data();
573 QString shellScript;
574 shellScript.sprintf(fmt, fifoName.data());
575 TRACE("output window script is " + shellScript);
577 QString title = kapp->caption();
578 title += i18n(": Program output");
580 // parse the command line specified in the preferences
581 ValArray<QString> cmdParts;
582 splitCmdStr(m_outputTermCmdStr, cmdParts);
585 * Build the argv array. Thereby substitute special sequences:
587 struct {
588 char seq[4];
589 QString replace;
590 } substitute[] = {
591 { "%T", title },
592 { "%C", shellScript }
595 for (int i = 0; i < cmdParts.size(); i++) {
596 QString& str = cmdParts[i];
597 for (int j = sizeof(substitute)/sizeof(substitute[0])-1; j >= 0; j--) {
598 int pos = str.find(substitute[j].seq);
599 if (pos >= 0) {
600 str.replace(pos, 2, substitute[j].replace);
601 break; /* substitute only one sequence */
604 *m_outputTermProc << str;
609 if (m_outputTermProc->start())
611 // read the ttyname from the fifo
612 int f = ::open(fifoName, O_RDONLY);
613 if (f < 0) {
614 // error
615 ::unlink(fifoName);
616 return QString();
619 char ttyname[50];
620 int n = ::read(f, ttyname, sizeof(ttyname)-sizeof(char)); /* leave space for '\0' */
622 ::close(f);
623 ::unlink(fifoName);
625 if (n < 0) {
626 // error
627 return QString();
630 // remove whitespace
631 ttyname[n] = '\0';
632 QString tty = QString(ttyname).stripWhiteSpace();
633 TRACE("tty=" + tty);
634 return tty;
636 else
638 // error, could not start xterm
639 TRACE("fork failed for fifo " + fifoName);
640 ::unlink(fifoName);
641 shutdownTermWindow();
642 return QString();
646 void DebuggerMainWndBase::shutdownTermWindow()
648 delete m_outputTermProc;
649 m_outputTermProc = 0;
652 void DebuggerMainWndBase::setTerminalCmd(const QString& cmd)
654 m_outputTermCmdStr = cmd;
655 // revert to default if empty
656 if (m_outputTermCmdStr.isEmpty()) {
657 m_outputTermCmdStr = defaultTermCmdStr;
661 void DebuggerMainWndBase::slotDebuggerStarting()
663 if (m_debugger == 0) /* paranoia check */
664 return;
666 if (m_ttyLevel == m_debugger->ttyLevel())
669 else
671 // shut down terminal emulations we will not need
672 switch (m_ttyLevel) {
673 case KDebugger::ttySimpleOutputOnly:
674 ttyWindow()->deactivate();
675 break;
676 case KDebugger::ttyFull:
677 if (m_outputTermProc != 0) {
678 m_outputTermProc->kill();
679 // will be deleted in slot
681 break;
682 default: break;
685 m_ttyLevel = m_debugger->ttyLevel();
687 QString ttyName;
688 switch (m_ttyLevel) {
689 case KDebugger::ttySimpleOutputOnly:
690 ttyName = ttyWindow()->activate();
691 break;
692 case KDebugger::ttyFull:
693 if (m_outputTermProc == 0) {
694 // create an output window
695 ttyName = createOutputWindow();
696 TRACE(ttyName.isEmpty() ?
697 "createOuputWindow failed" : "successfully created output window");
699 break;
700 default: break;
703 m_debugger->setTerminal(ttyName);
707 void DebuggerMainWndBase::setDebuggerCmdStr(const QString& cmd)
709 m_debuggerCmdStr = cmd;
710 // make empty if it is the default
711 if (m_debuggerCmdStr == GdbDriver::defaultGdb()) {
712 m_debuggerCmdStr = QString();
716 void DebuggerMainWndBase::addRecentExec(const QString& executable)
718 int pos = m_recentExecList.find(executable);
719 if (pos != 0) {
720 // move to top
721 if (pos > 0)
722 m_recentExecList.remove(pos);
723 // else entry is new
725 // insert on top
726 m_recentExecList.insert(0, executable);
727 } // else pos == 0, which means we dont need to change the list
729 // shorten list
730 while (m_recentExecList.count() > MAX_RECENT_FILES) {
731 m_recentExecList.remove(MAX_RECENT_FILES);
735 void DebuggerMainWndBase::removeRecentExec(const QString& executable)
737 int pos = m_recentExecList.find(executable);
738 if (pos >= 0) {
739 m_recentExecList.remove(pos);
744 #include "mainwndbase.moc"