Don't try to list threads if there is no program.
[kdbg.git] / kdbg / mainwndbase.cpp
blobad7f482acea11523ca12850c3dda53d339495aac
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 "commandids.h"
25 #include "valarray.h"
26 #ifdef HAVE_CONFIG
27 #include "config.h"
28 #endif
29 #include "mydebug.h"
30 #ifdef HAVE_SYS_STAT_H
31 #include <sys/stat.h> /* mknod(2) */
32 #endif
33 #ifdef HAVE_FCNTL_H
34 #include <fcntl.h> /* open(2) */
35 #endif
36 #ifdef HAVE_UNISTD_H
37 #include <unistd.h> /* getpid, unlink etc. */
38 #endif
40 #define MAX_RECENT_FILES 4
42 WatchWindow::WatchWindow(QWidget* parent, const char* name, WFlags f) :
43 QWidget(parent, name, f),
44 m_watchEdit(this, "watch_edit"),
45 m_watchAdd(i18n(" Add "), this, "watch_add"),
46 m_watchDelete(i18n(" Del "), this, "watch_delete"),
47 m_watchVariables(this, "watch_variables"),
48 m_watchV(this, 0),
49 m_watchH(0)
51 // setup the layout
52 m_watchAdd.setMinimumSize(m_watchAdd.sizeHint());
53 m_watchDelete.setMinimumSize(m_watchDelete.sizeHint());
54 m_watchV.addLayout(&m_watchH, 0);
55 m_watchV.addWidget(&m_watchVariables, 10);
56 m_watchH.addWidget(&m_watchEdit, 10);
57 m_watchH.addWidget(&m_watchAdd, 0);
58 m_watchH.addWidget(&m_watchDelete, 0);
60 connect(&m_watchEdit, SIGNAL(returnPressed()), SIGNAL(addWatch()));
61 connect(&m_watchAdd, SIGNAL(clicked()), SIGNAL(addWatch()));
62 connect(&m_watchDelete, SIGNAL(clicked()), SIGNAL(deleteWatch()));
63 connect(&m_watchVariables, SIGNAL(highlighted(int)), SLOT(slotWatchHighlighted(int)));
65 m_watchVariables.setMoveCurrentToSibling(true);
66 m_watchVariables.installEventFilter(this);
69 WatchWindow::~WatchWindow()
73 bool WatchWindow::eventFilter(QObject*, QEvent* ev)
75 if (ev->type() == QEvent::KeyPress)
77 QKeyEvent* kev = static_cast<QKeyEvent*>(ev);
78 if (kev->key() == Key_Delete) {
79 emit deleteWatch();
80 return true;
83 return false;
87 // place the text of the hightlighted watch expr in the edit field
88 void WatchWindow::slotWatchHighlighted(int idx)
90 QString text = m_watchVariables.exprStringAt(idx);
91 m_watchEdit.setText(text);
95 static void splitCmdStr(const QString& cmd, ValArray<QString>& parts)
97 QString str = cmd.simplifyWhiteSpace();
98 int start = 0;
99 int end;
100 while ((end = str.find(' ', start)) >= 0) {
101 parts.append(str.mid(start, end-start));
102 start = end+1;
104 parts.append(str.mid(start, str.length()-start));
108 const char defaultTermCmdStr[] = "xterm -name kdbgio -title %T -e sh -c %C";
109 const char defaultSourceFilter[] = "*.c *.cc *.cpp *.c++ *.C *.CC";
110 const char defaultHeaderFilter[] = "*.h *.hh *.hpp *.h++";
113 DebuggerMainWndBase::DebuggerMainWndBase() :
114 m_animationCounter(0),
115 m_outputTermCmdStr(defaultTermCmdStr),
116 m_outputTermProc(0),
117 m_ttyLevel(-1), /* no tty yet */
118 #ifdef GDB_TRANSCRIPT
119 m_transcriptFile(GDB_TRANSCRIPT),
120 #endif
121 m_popForeground(false),
122 m_backTimeout(1000),
123 m_tabWidth(0),
124 m_sourceFilter(defaultSourceFilter),
125 m_headerFilter(defaultHeaderFilter),
126 m_debugger(0)
128 m_statusActive = i18n("active");
131 DebuggerMainWndBase::~DebuggerMainWndBase()
133 delete m_debugger;
135 // if the output window is open, close it
136 if (m_outputTermProc != 0) {
137 m_outputTermProc->disconnect(); /* ignore signals */
138 m_outputTermProc->kill();
139 shutdownTermWindow();
143 void DebuggerMainWndBase::setupDebugger(QWidget* parent,
144 ExprWnd* localVars,
145 ExprWnd* watchVars,
146 QListBox* backtrace)
148 m_debugger = new KDebugger(parent, localVars, watchVars, backtrace);
150 QObject::connect(m_debugger, SIGNAL(updateStatusMessage()),
151 parent, SLOT(slotNewStatusMsg()));
152 QObject::connect(m_debugger, SIGNAL(updateUI()),
153 parent, SLOT(updateUI()));
155 QObject::connect(m_debugger, SIGNAL(breakpointsChanged()),
156 parent, SLOT(updateLineItems()));
158 QObject::connect(m_debugger, SIGNAL(animationTimeout()),
159 parent, SLOT(slotAnimationTimeout()));
160 QObject::connect(m_debugger, SIGNAL(debuggerStarting()),
161 parent, SLOT(slotDebuggerStarting()));
165 void DebuggerMainWndBase::setCoreFile(const QString& corefile)
167 assert(m_debugger != 0);
168 m_debugger->useCoreFile(corefile, true);
171 void DebuggerMainWndBase::setRemoteDevice(const QString& remoteDevice)
173 if (m_debugger != 0) {
174 m_debugger->setRemoteDevice(remoteDevice);
178 void DebuggerMainWndBase::setTranscript(const char* name)
180 m_transcriptFile = name;
181 if (m_debugger != 0 && m_debugger->driver() != 0)
182 m_debugger->driver()->setLogFileName(m_transcriptFile);
185 const char OutputWindowGroup[] = "OutputWindow";
186 const char TermCmdStr[] = "TermCmdStr";
187 const char KeepScript[] = "KeepScript";
188 const char DebuggerGroup[] = "Debugger";
189 const char DebuggerCmdStr[] = "DebuggerCmdStr";
190 const char PreferencesGroup[] = "Preferences";
191 const char PopForeground[] = "PopForeground";
192 const char BackTimeout[] = "BackTimeout";
193 const char TabWidth[] = "TabWidth";
194 const char FilesGroup[] = "Files";
195 const char SourceFileFilter[] = "SourceFileFilter";
196 const char HeaderFileFilter[] = "HeaderFileFilter";
197 const char GeneralGroup[] = "General";
199 void DebuggerMainWndBase::saveSettings(KConfig* config)
201 if (m_debugger != 0) {
202 m_debugger->saveSettings(config);
205 KConfigGroupSaver g(config, OutputWindowGroup);
206 config->writeEntry(TermCmdStr, m_outputTermCmdStr);
208 config->setGroup(DebuggerGroup);
209 config->writeEntry(DebuggerCmdStr, m_debuggerCmdStr);
211 config->setGroup(PreferencesGroup);
212 config->writeEntry(PopForeground, m_popForeground);
213 config->writeEntry(BackTimeout, m_backTimeout);
214 config->writeEntry(TabWidth, m_tabWidth);
215 config->writeEntry(SourceFileFilter, m_sourceFilter);
216 config->writeEntry(HeaderFileFilter, m_headerFilter);
219 void DebuggerMainWndBase::restoreSettings(KConfig* config)
221 if (m_debugger != 0) {
222 m_debugger->restoreSettings(config);
225 KConfigGroupSaver g(config, OutputWindowGroup);
227 * For debugging and emergency purposes, let the config file override
228 * the shell script that is used to keep the output window open. This
229 * string must have EXACTLY 1 %s sequence in it.
231 setTerminalCmd(config->readEntry(TermCmdStr, defaultTermCmdStr));
232 m_outputTermKeepScript = config->readEntry(KeepScript);
234 config->setGroup(DebuggerGroup);
235 setDebuggerCmdStr(config->readEntry(DebuggerCmdStr));
237 config->setGroup(PreferencesGroup);
238 m_popForeground = config->readBoolEntry(PopForeground, false);
239 m_backTimeout = config->readNumEntry(BackTimeout, 1000);
240 m_tabWidth = config->readNumEntry(TabWidth, 0);
241 m_sourceFilter = config->readEntry(SourceFileFilter, m_sourceFilter);
242 m_headerFilter = config->readEntry(HeaderFileFilter, m_headerFilter);
245 void DebuggerMainWndBase::setAttachPid(const QString& pid)
247 assert(m_debugger != 0);
248 m_debugger->setAttachPid(pid);
251 bool DebuggerMainWndBase::debugProgram(const QString& executable,
252 QCString lang, QWidget* parent)
254 assert(m_debugger != 0);
256 TRACE(QString().sprintf("trying language '%s'...", lang.data()));
257 DebuggerDriver* driver = driverFromLang(lang);
259 if (driver == 0)
261 // see if there is a language in the per-program config file
262 QString configName = m_debugger->getConfigForExe(executable);
263 if (QFile::exists(configName))
265 KSimpleConfig c(configName, true); // read-only
266 c.setGroup(GeneralGroup);
268 // Using "GDB" as default here is for backwards compatibility:
269 // The config file exists but doesn't have an entry,
270 // so it must have been created by an old version of KDbg
271 // that had only the GDB driver.
272 lang = c.readEntry(KDebugger::DriverNameEntry, "GDB").latin1();
274 TRACE(QString().sprintf("...bad, trying config driver %s...",
275 lang.data()));
276 driver = driverFromLang(lang);
280 if (driver == 0)
282 QCString name = driverNameFromFile(executable);
284 TRACE(QString().sprintf("...no luck, trying %s derived"
285 " from file contents", name.data()));
286 driver = driverFromLang(name);
288 if (driver == 0)
290 // oops
291 QString msg = i18n("Don't know how to debug language `%1'");
292 KMessageBox::sorry(parent, msg.arg(lang));
293 return false;
296 driver->setLogFileName(m_transcriptFile);
298 bool success = m_debugger->debugProgram(executable, driver);
300 if (!success)
302 delete driver;
304 QString msg = i18n("Could not start the debugger process.\n"
305 "Please shut down KDbg and resolve the problem.");
306 KMessageBox::sorry(parent, msg);
309 return success;
312 // derive driver from language
313 DebuggerDriver* DebuggerMainWndBase::driverFromLang(QCString lang)
315 // lang is needed in all lowercase
316 lang = lang.lower();
318 // The following table relates languages and debugger drivers
319 static const struct L {
320 const char* shortest; // abbreviated to this is still unique
321 const char* full; // full name of language
322 int driver;
323 } langs[] = {
324 { "c", "c++", 1 },
325 { "f", "fortran", 1 },
326 { "p", "python", 3 },
327 { "x", "xslt", 2 },
328 // the following are actually driver names
329 { "gdb", "gdb", 1 },
330 { "xsldbg", "xsldbg", 2 },
332 const int N = sizeof(langs)/sizeof(langs[0]);
334 // lookup the language name
335 int driverID = 0;
336 for (int i = 0; i < N; i++)
338 const L& l = langs[i];
340 // shortest must match
341 if (strncmp(l.shortest, lang, strlen(l.shortest)) != 0)
342 continue;
344 // lang must not be longer than the full name, and it must match
345 if (lang.length() <= strlen(l.full) &&
346 strncmp(l.full, lang, lang.length()) == 0)
348 driverID = l.driver;
349 break;
352 DebuggerDriver* driver = 0;
353 switch (driverID) {
354 case 1:
356 GdbDriver* gdb = new GdbDriver;
357 gdb->setDefaultInvocation(m_debuggerCmdStr);
358 driver = gdb;
360 break;
361 case 2:
362 driver = new XsldbgDriver;
363 break;
364 default:
365 // unknown language
366 break;
368 return driver;
372 * Try to guess the language to use from the contents of the file.
374 QCString DebuggerMainWndBase::driverNameFromFile(const QString& exe)
376 /* Inprecise but simple test to see if file is in XSLT language */
377 if (exe.right(4).lower() == ".xsl")
378 return "XSLT";
380 return "GDB";
383 // helper that gets a file name (it only differs in the caption of the dialog)
384 QString DebuggerMainWndBase::myGetFileName(QString caption,
385 QString dir, QString filter,
386 QWidget* parent)
388 QString filename;
389 KFileDialog dlg(dir, filter, parent, "filedialog", true);
391 dlg.setCaption(caption);
393 if (dlg.exec() == QDialog::Accepted)
394 filename = dlg.selectedFile();
396 return filename;
399 void DebuggerMainWndBase::initAnimation(KToolBar* toolbar)
401 QPixmap pixmap = BarIcon("kde1");
402 int numPix = 6;
404 toolbar->insertButton(pixmap, ID_STATUS_BUSY);
405 toolbar->alignItemRight(ID_STATUS_BUSY, true);
407 // Load animated logo
408 m_animation.setAutoDelete(true);
409 QString n;
410 for (int i = 1; i <= numPix; i++) {
411 n.sprintf("kde%d", i);
412 QPixmap* p = new QPixmap(BarIcon(n));
413 if (!p->isNull()) {
414 m_animation.append(p);
415 } else {
416 delete p;
419 // safeguard: if we did not find a single icon, add a dummy
420 if (m_animation.count() == 0) {
421 QPixmap* pix = new QPixmap(2,2);
422 QPainter p(pix);
423 p.fillRect(0,0,2,2,QBrush(Qt::white));
424 m_animation.append(pix);
428 void DebuggerMainWndBase::nextAnimationFrame(KToolBar* toolbar)
430 assert(m_animation.count() > 0); /* must have been initialized */
431 m_animationCounter++;
432 if (m_animationCounter == m_animation.count())
433 m_animationCounter = 0;
434 toolbar->setButtonPixmap(ID_STATUS_BUSY,
435 *m_animation.at(m_animationCounter));
438 void DebuggerMainWndBase::newStatusMsg(KStatusBar* statusbar)
440 QString msg = m_debugger->statusMessage();
441 statusbar->changeItem(msg, ID_STATUS_MSG);
444 void DebuggerMainWndBase::doGlobalOptions(QWidget* parent)
446 QTabDialog dlg(parent, "global_options", true);
447 QString title = kapp->caption();
448 title += i18n(": Global options");
449 dlg.setCaption(title);
450 dlg.setCancelButton(i18n("Cancel"));
451 dlg.setOKButton(i18n("OK"));
453 PrefDebugger prefDebugger(&dlg);
454 prefDebugger.setDebuggerCmd(m_debuggerCmdStr.isEmpty() ?
455 GdbDriver::defaultGdb() : m_debuggerCmdStr);
456 prefDebugger.setTerminal(m_outputTermCmdStr);
458 PrefMisc prefMisc(&dlg);
459 prefMisc.setPopIntoForeground(m_popForeground);
460 prefMisc.setBackTimeout(m_backTimeout);
461 prefMisc.setTabWidth(m_tabWidth);
462 prefMisc.setSourceFilter(m_sourceFilter);
463 prefMisc.setHeaderFilter(m_headerFilter);
465 dlg.addTab(&prefDebugger, i18n("&Debugger"));
466 dlg.addTab(&prefMisc, i18n("&Miscellaneous"));
467 if (dlg.exec() == QDialog::Accepted)
469 setDebuggerCmdStr(prefDebugger.debuggerCmd());
470 setTerminalCmd(prefDebugger.terminal());
471 m_popForeground = prefMisc.popIntoForeground();
472 m_backTimeout = prefMisc.backTimeout();
473 m_tabWidth = prefMisc.tabWidth();
474 m_sourceFilter = prefMisc.sourceFilter();
475 if (m_sourceFilter.isEmpty())
476 m_sourceFilter = defaultSourceFilter;
477 m_headerFilter = prefMisc.headerFilter();
478 if (m_headerFilter.isEmpty())
479 m_headerFilter = defaultHeaderFilter;
483 const char fifoNameBase[] = "/tmp/kdbgttywin%05d";
486 * We use the scope operator :: in this function, so that we don't
487 * accidentally use the wrong close() function (I've been bitten ;-),
488 * outch!) (We use it for all the libc functions, to be consistent...)
490 QString DebuggerMainWndBase::createOutputWindow()
492 // create a name for a fifo
493 QString fifoName;
494 fifoName.sprintf(fifoNameBase, ::getpid());
496 // create a fifo that will pass in the tty name
497 ::unlink(fifoName); /* remove remnants */
498 #ifdef HAVE_MKFIFO
499 if (::mkfifo(fifoName, S_IRUSR|S_IWUSR) < 0) {
500 // failed
501 TRACE("mkfifo " + fifoName + " failed");
502 return QString();
504 #else
505 if (::mknod(fifoName, S_IFIFO | S_IRUSR|S_IWUSR, 0) < 0) {
506 // failed
507 TRACE("mknod " + fifoName + " failed");
508 return QString();
510 #endif
512 m_outputTermProc = new KProcess;
516 * Spawn an xterm that in turn runs a shell script that passes us
517 * back the terminal name and then only sits and waits.
519 static const char shellScriptFmt[] =
520 "tty>%s;"
521 "trap \"\" INT QUIT TSTP;" /* ignore various signals */
522 "exec<&-;exec>&-;" /* close stdin and stdout */
523 "while :;do sleep 3600;done";
524 // let config file override this script
525 const char* fmt = shellScriptFmt;
526 if (m_outputTermKeepScript.length() != 0) {
527 fmt = m_outputTermKeepScript.data();
530 QString shellScript;
531 shellScript.sprintf(fmt, fifoName.data());
532 TRACE("output window script is " + shellScript);
534 QString title = kapp->caption();
535 title += i18n(": Program output");
537 // parse the command line specified in the preferences
538 ValArray<QString> cmdParts;
539 splitCmdStr(m_outputTermCmdStr, cmdParts);
542 * Build the argv array. Thereby substitute special sequences:
544 struct {
545 char seq[4];
546 QString replace;
547 } substitute[] = {
548 { "%T", title },
549 { "%C", shellScript }
552 for (int i = 0; i < cmdParts.size(); i++) {
553 QString& str = cmdParts[i];
554 for (int j = sizeof(substitute)/sizeof(substitute[0])-1; j >= 0; j--) {
555 int pos = str.find(substitute[j].seq);
556 if (pos >= 0) {
557 str.replace(pos, 2, substitute[j].replace);
558 break; /* substitute only one sequence */
561 *m_outputTermProc << str;
566 if (m_outputTermProc->start())
568 // read the ttyname from the fifo
569 int f = ::open(fifoName, O_RDONLY);
570 if (f < 0) {
571 // error
572 ::unlink(fifoName);
573 return QString();
576 char ttyname[50];
577 int n = ::read(f, ttyname, sizeof(ttyname)-sizeof(char)); /* leave space for '\0' */
579 ::close(f);
580 ::unlink(fifoName);
582 if (n < 0) {
583 // error
584 return QString();
587 // remove whitespace
588 ttyname[n] = '\0';
589 QString tty = QString(ttyname).stripWhiteSpace();
590 TRACE("tty=" + tty);
591 return tty;
593 else
595 // error, could not start xterm
596 TRACE("fork failed for fifo " + fifoName);
597 ::unlink(fifoName);
598 shutdownTermWindow();
599 return QString();
603 void DebuggerMainWndBase::shutdownTermWindow()
605 delete m_outputTermProc;
606 m_outputTermProc = 0;
609 void DebuggerMainWndBase::setTerminalCmd(const QString& cmd)
611 m_outputTermCmdStr = cmd;
612 // revert to default if empty
613 if (m_outputTermCmdStr.isEmpty()) {
614 m_outputTermCmdStr = defaultTermCmdStr;
618 void DebuggerMainWndBase::slotDebuggerStarting()
620 if (m_debugger == 0) /* paranoia check */
621 return;
623 if (m_ttyLevel == m_debugger->ttyLevel())
626 else
628 // shut down terminal emulations we will not need
629 switch (m_ttyLevel) {
630 case KDebugger::ttySimpleOutputOnly:
631 ttyWindow()->deactivate();
632 break;
633 case KDebugger::ttyFull:
634 if (m_outputTermProc != 0) {
635 m_outputTermProc->kill();
636 // will be deleted in slot
638 break;
639 default: break;
642 m_ttyLevel = m_debugger->ttyLevel();
644 QString ttyName;
645 switch (m_ttyLevel) {
646 case KDebugger::ttySimpleOutputOnly:
647 ttyName = ttyWindow()->activate();
648 break;
649 case KDebugger::ttyFull:
650 if (m_outputTermProc == 0) {
651 // create an output window
652 ttyName = createOutputWindow();
653 TRACE(ttyName.isEmpty() ?
654 "createOuputWindow failed" : "successfully created output window");
656 break;
657 default: break;
660 m_debugger->setTerminal(ttyName);
664 void DebuggerMainWndBase::setDebuggerCmdStr(const QString& cmd)
666 m_debuggerCmdStr = cmd;
667 // make empty if it is the default
668 if (m_debuggerCmdStr == GdbDriver::defaultGdb()) {
669 m_debuggerCmdStr = QString();
674 #include "mainwndbase.moc"