Don't crash on Del in the Watches window while no program is loaded.
[kdbg.git] / kdbg / mainwndbase.cpp
bloba753e148c1f353f8e50b23d2c7d228c736c0ca32
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 <kstatusbar.h>
11 #include <kfiledialog.h>
12 #include <qtabdialog.h>
13 #include <qfile.h>
14 #include <qdragobject.h>
15 #include "mainwndbase.h"
16 #include "debugger.h"
17 #include "gdbdriver.h"
18 #include "xsldbgdriver.h"
19 #include "prefdebugger.h"
20 #include "prefmisc.h"
21 #include "ttywnd.h"
22 #include "commandids.h"
23 #include "valarray.h"
24 #ifdef HAVE_CONFIG
25 #include "config.h"
26 #endif
27 #include "mydebug.h"
28 #ifdef HAVE_SYS_STAT_H
29 #include <sys/stat.h> /* mknod(2) */
30 #endif
31 #ifdef HAVE_FCNTL_H
32 #include <fcntl.h> /* open(2) */
33 #endif
34 #ifdef HAVE_UNISTD_H
35 #include <unistd.h> /* getpid, unlink etc. */
36 #endif
38 #define MAX_RECENT_FILES 4
40 WatchWindow::WatchWindow(QWidget* parent, const char* name, WFlags f) :
41 QWidget(parent, name, f),
42 m_watchEdit(this, "watch_edit"),
43 m_watchAdd(i18n(" Add "), this, "watch_add"),
44 m_watchDelete(i18n(" Del "), this, "watch_delete"),
45 m_watchVariables(this, i18n("Expression"), "watch_variables"),
46 m_watchV(this, 0),
47 m_watchH(0)
49 // setup the layout
50 m_watchAdd.setMinimumSize(m_watchAdd.sizeHint());
51 m_watchDelete.setMinimumSize(m_watchDelete.sizeHint());
52 m_watchV.addLayout(&m_watchH, 0);
53 m_watchV.addWidget(&m_watchVariables, 10);
54 m_watchH.addWidget(&m_watchEdit, 10);
55 m_watchH.addWidget(&m_watchAdd, 0);
56 m_watchH.addWidget(&m_watchDelete, 0);
58 connect(&m_watchEdit, SIGNAL(returnPressed()), SIGNAL(addWatch()));
59 connect(&m_watchAdd, SIGNAL(clicked()), SIGNAL(addWatch()));
60 connect(&m_watchDelete, SIGNAL(clicked()), SIGNAL(deleteWatch()));
61 connect(&m_watchVariables, SIGNAL(currentChanged(QListViewItem*)),
62 SLOT(slotWatchHighlighted()));
64 m_watchVariables.installEventFilter(this);
65 setAcceptDrops(true);
68 WatchWindow::~WatchWindow()
72 bool WatchWindow::eventFilter(QObject*, QEvent* ev)
74 if (ev->type() == QEvent::KeyPress)
76 QKeyEvent* kev = static_cast<QKeyEvent*>(ev);
77 if (kev->key() == Key_Delete) {
78 emit deleteWatch();
79 return true;
82 return false;
85 void WatchWindow::dragEnterEvent(QDragEnterEvent* event)
87 event->accept(QTextDrag::canDecode(event));
90 void WatchWindow::dropEvent(QDropEvent* event)
92 QString text;
93 if (QTextDrag::decode(event, text))
95 // pick only the first line
96 text = text.stripWhiteSpace();
97 int pos = text.find('\n');
98 if (pos > 0)
99 text.truncate(pos);
100 text = text.stripWhiteSpace();
101 if (!text.isEmpty())
102 emit textDropped(text);
107 // place the text of the hightlighted watch expr in the edit field
108 void WatchWindow::slotWatchHighlighted()
110 VarTree* expr = m_watchVariables.selectedItem();
111 QString text = expr ? expr->computeExpr() : QString();
112 m_watchEdit.setText(text);
116 static void splitCmdStr(const QString& cmd, ValArray<QString>& parts)
118 QString str = cmd.simplifyWhiteSpace();
119 int start = 0;
120 int end;
121 while ((end = str.find(' ', start)) >= 0) {
122 parts.append(str.mid(start, end-start));
123 start = end+1;
125 parts.append(str.mid(start, str.length()-start));
129 const char defaultTermCmdStr[] = "xterm -name kdbgio -title %T -e sh -c %C";
130 const char defaultSourceFilter[] = "*.c *.cc *.cpp *.c++ *.C *.CC";
131 const char defaultHeaderFilter[] = "*.h *.hh *.hpp *.h++";
134 DebuggerMainWndBase::DebuggerMainWndBase() :
135 m_outputTermCmdStr(defaultTermCmdStr),
136 m_outputTermProc(0),
137 m_ttyLevel(-1), /* no tty yet */
138 #ifdef GDB_TRANSCRIPT
139 m_transcriptFile(GDB_TRANSCRIPT),
140 #endif
141 m_popForeground(false),
142 m_backTimeout(1000),
143 m_tabWidth(0),
144 m_sourceFilter(defaultSourceFilter),
145 m_headerFilter(defaultHeaderFilter),
146 m_debugger(0)
148 m_statusActive = i18n("active");
151 DebuggerMainWndBase::~DebuggerMainWndBase()
153 delete m_debugger;
155 // if the output window is open, close it
156 if (m_outputTermProc != 0) {
157 m_outputTermProc->disconnect(); /* ignore signals */
158 m_outputTermProc->kill();
159 shutdownTermWindow();
163 void DebuggerMainWndBase::setupDebugger(QWidget* parent,
164 ExprWnd* localVars,
165 ExprWnd* watchVars,
166 QListBox* backtrace)
168 m_debugger = new KDebugger(parent, localVars, watchVars, backtrace);
170 QObject::connect(m_debugger, SIGNAL(updateStatusMessage()),
171 parent, SLOT(slotNewStatusMsg()));
172 QObject::connect(m_debugger, SIGNAL(updateUI()),
173 parent, SLOT(updateUI()));
175 QObject::connect(m_debugger, SIGNAL(breakpointsChanged()),
176 parent, SLOT(updateLineItems()));
178 QObject::connect(m_debugger, SIGNAL(debuggerStarting()),
179 parent, SLOT(slotDebuggerStarting()));
183 void DebuggerMainWndBase::setCoreFile(const QString& corefile)
185 assert(m_debugger != 0);
186 m_debugger->useCoreFile(corefile, true);
189 void DebuggerMainWndBase::setRemoteDevice(const QString& remoteDevice)
191 if (m_debugger != 0) {
192 m_debugger->setRemoteDevice(remoteDevice);
196 void DebuggerMainWndBase::setTranscript(const char* name)
198 m_transcriptFile = name;
199 if (m_debugger != 0 && m_debugger->driver() != 0)
200 m_debugger->driver()->setLogFileName(m_transcriptFile);
203 const char OutputWindowGroup[] = "OutputWindow";
204 const char TermCmdStr[] = "TermCmdStr";
205 const char KeepScript[] = "KeepScript";
206 const char DebuggerGroup[] = "Debugger";
207 const char DebuggerCmdStr[] = "DebuggerCmdStr";
208 const char PreferencesGroup[] = "Preferences";
209 const char PopForeground[] = "PopForeground";
210 const char BackTimeout[] = "BackTimeout";
211 const char TabWidth[] = "TabWidth";
212 const char FilesGroup[] = "Files";
213 const char SourceFileFilter[] = "SourceFileFilter";
214 const char HeaderFileFilter[] = "HeaderFileFilter";
215 const char GeneralGroup[] = "General";
217 void DebuggerMainWndBase::saveSettings(KConfig* config)
219 if (m_debugger != 0) {
220 m_debugger->saveSettings(config);
223 KConfigGroupSaver g(config, OutputWindowGroup);
224 config->writeEntry(TermCmdStr, m_outputTermCmdStr);
226 config->setGroup(DebuggerGroup);
227 config->writeEntry(DebuggerCmdStr, m_debuggerCmdStr);
229 config->setGroup(PreferencesGroup);
230 config->writeEntry(PopForeground, m_popForeground);
231 config->writeEntry(BackTimeout, m_backTimeout);
232 config->writeEntry(TabWidth, m_tabWidth);
233 config->writeEntry(SourceFileFilter, m_sourceFilter);
234 config->writeEntry(HeaderFileFilter, m_headerFilter);
237 void DebuggerMainWndBase::restoreSettings(KConfig* config)
239 if (m_debugger != 0) {
240 m_debugger->restoreSettings(config);
243 KConfigGroupSaver g(config, OutputWindowGroup);
245 * For debugging and emergency purposes, let the config file override
246 * the shell script that is used to keep the output window open. This
247 * string must have EXACTLY 1 %s sequence in it.
249 setTerminalCmd(config->readEntry(TermCmdStr, defaultTermCmdStr));
250 m_outputTermKeepScript = config->readEntry(KeepScript);
252 config->setGroup(DebuggerGroup);
253 setDebuggerCmdStr(config->readEntry(DebuggerCmdStr));
255 config->setGroup(PreferencesGroup);
256 m_popForeground = config->readBoolEntry(PopForeground, false);
257 m_backTimeout = config->readNumEntry(BackTimeout, 1000);
258 m_tabWidth = config->readNumEntry(TabWidth, 0);
259 m_sourceFilter = config->readEntry(SourceFileFilter, m_sourceFilter);
260 m_headerFilter = config->readEntry(HeaderFileFilter, m_headerFilter);
263 void DebuggerMainWndBase::setAttachPid(const QString& pid)
265 assert(m_debugger != 0);
266 m_debugger->setAttachPid(pid);
269 bool DebuggerMainWndBase::debugProgram(const QString& executable,
270 QCString lang, QWidget* parent)
272 assert(m_debugger != 0);
274 TRACE(QString().sprintf("trying language '%s'...", lang.data()));
275 DebuggerDriver* driver = driverFromLang(lang);
277 if (driver == 0)
279 // see if there is a language in the per-program config file
280 QString configName = m_debugger->getConfigForExe(executable);
281 if (QFile::exists(configName))
283 KSimpleConfig c(configName, true); // read-only
284 c.setGroup(GeneralGroup);
286 // Using "GDB" as default here is for backwards compatibility:
287 // The config file exists but doesn't have an entry,
288 // so it must have been created by an old version of KDbg
289 // that had only the GDB driver.
290 lang = c.readEntry(KDebugger::DriverNameEntry, "GDB").latin1();
292 TRACE(QString().sprintf("...bad, trying config driver %s...",
293 lang.data()));
294 driver = driverFromLang(lang);
298 if (driver == 0)
300 QCString name = driverNameFromFile(executable);
302 TRACE(QString().sprintf("...no luck, trying %s derived"
303 " from file contents", name.data()));
304 driver = driverFromLang(name);
306 if (driver == 0)
308 // oops
309 QString msg = i18n("Don't know how to debug language `%1'");
310 KMessageBox::sorry(parent, msg.arg(lang));
311 return false;
314 driver->setLogFileName(m_transcriptFile);
316 bool success = m_debugger->debugProgram(executable, driver);
318 if (!success)
320 delete driver;
322 QString msg = i18n("Could not start the debugger process.\n"
323 "Please shut down KDbg and resolve the problem.");
324 KMessageBox::sorry(parent, msg);
327 return success;
330 // derive driver from language
331 DebuggerDriver* DebuggerMainWndBase::driverFromLang(QCString lang)
333 // lang is needed in all lowercase
334 lang = lang.lower();
336 // The following table relates languages and debugger drivers
337 static const struct L {
338 const char* shortest; // abbreviated to this is still unique
339 const char* full; // full name of language
340 int driver;
341 } langs[] = {
342 { "c", "c++", 1 },
343 { "f", "fortran", 1 },
344 { "p", "python", 3 },
345 { "x", "xslt", 2 },
346 // the following are actually driver names
347 { "gdb", "gdb", 1 },
348 { "xsldbg", "xsldbg", 2 },
350 const int N = sizeof(langs)/sizeof(langs[0]);
352 // lookup the language name
353 int driverID = 0;
354 for (int i = 0; i < N; i++)
356 const L& l = langs[i];
358 // shortest must match
359 if (strncmp(l.shortest, lang, strlen(l.shortest)) != 0)
360 continue;
362 // lang must not be longer than the full name, and it must match
363 if (lang.length() <= strlen(l.full) &&
364 strncmp(l.full, lang, lang.length()) == 0)
366 driverID = l.driver;
367 break;
370 DebuggerDriver* driver = 0;
371 switch (driverID) {
372 case 1:
374 GdbDriver* gdb = new GdbDriver;
375 gdb->setDefaultInvocation(m_debuggerCmdStr);
376 driver = gdb;
378 break;
379 case 2:
380 driver = new XsldbgDriver;
381 break;
382 default:
383 // unknown language
384 break;
386 return driver;
390 * Try to guess the language to use from the contents of the file.
392 QCString DebuggerMainWndBase::driverNameFromFile(const QString& exe)
394 /* Inprecise but simple test to see if file is in XSLT language */
395 if (exe.right(4).lower() == ".xsl")
396 return "XSLT";
398 return "GDB";
401 // helper that gets a file name (it only differs in the caption of the dialog)
402 QString DebuggerMainWndBase::myGetFileName(QString caption,
403 QString dir, QString filter,
404 QWidget* parent)
406 QString filename;
407 KFileDialog dlg(dir, filter, parent, "filedialog", true);
409 dlg.setCaption(caption);
411 if (dlg.exec() == QDialog::Accepted)
412 filename = dlg.selectedFile();
414 return filename;
417 void DebuggerMainWndBase::newStatusMsg(KStatusBar* statusbar)
419 QString msg = m_debugger->statusMessage();
420 statusbar->changeItem(msg, ID_STATUS_MSG);
423 void DebuggerMainWndBase::doGlobalOptions(QWidget* parent)
425 QTabDialog dlg(parent, "global_options", true);
426 QString title = kapp->caption();
427 title += i18n(": Global options");
428 dlg.setCaption(title);
429 dlg.setCancelButton(i18n("Cancel"));
430 dlg.setOKButton(i18n("OK"));
432 PrefDebugger prefDebugger(&dlg);
433 prefDebugger.setDebuggerCmd(m_debuggerCmdStr.isEmpty() ?
434 GdbDriver::defaultGdb() : m_debuggerCmdStr);
435 prefDebugger.setTerminal(m_outputTermCmdStr);
437 PrefMisc prefMisc(&dlg);
438 prefMisc.setPopIntoForeground(m_popForeground);
439 prefMisc.setBackTimeout(m_backTimeout);
440 prefMisc.setTabWidth(m_tabWidth);
441 prefMisc.setSourceFilter(m_sourceFilter);
442 prefMisc.setHeaderFilter(m_headerFilter);
444 dlg.addTab(&prefDebugger, i18n("&Debugger"));
445 dlg.addTab(&prefMisc, i18n("&Miscellaneous"));
446 if (dlg.exec() == QDialog::Accepted)
448 setDebuggerCmdStr(prefDebugger.debuggerCmd());
449 setTerminalCmd(prefDebugger.terminal());
450 m_popForeground = prefMisc.popIntoForeground();
451 m_backTimeout = prefMisc.backTimeout();
452 m_tabWidth = prefMisc.tabWidth();
453 m_sourceFilter = prefMisc.sourceFilter();
454 if (m_sourceFilter.isEmpty())
455 m_sourceFilter = defaultSourceFilter;
456 m_headerFilter = prefMisc.headerFilter();
457 if (m_headerFilter.isEmpty())
458 m_headerFilter = defaultHeaderFilter;
462 const char fifoNameBase[] = "/tmp/kdbgttywin%05d";
465 * We use the scope operator :: in this function, so that we don't
466 * accidentally use the wrong close() function (I've been bitten ;-),
467 * outch!) (We use it for all the libc functions, to be consistent...)
469 QString DebuggerMainWndBase::createOutputWindow()
471 // create a name for a fifo
472 QString fifoName;
473 fifoName.sprintf(fifoNameBase, ::getpid());
475 // create a fifo that will pass in the tty name
476 ::unlink(fifoName); /* remove remnants */
477 #ifdef HAVE_MKFIFO
478 if (::mkfifo(fifoName, S_IRUSR|S_IWUSR) < 0) {
479 // failed
480 TRACE("mkfifo " + fifoName + " failed");
481 return QString();
483 #else
484 if (::mknod(fifoName, S_IFIFO | S_IRUSR|S_IWUSR, 0) < 0) {
485 // failed
486 TRACE("mknod " + fifoName + " failed");
487 return QString();
489 #endif
491 m_outputTermProc = new KProcess;
495 * Spawn an xterm that in turn runs a shell script that passes us
496 * back the terminal name and then only sits and waits.
498 static const char shellScriptFmt[] =
499 "tty>%s;"
500 "trap \"\" INT QUIT TSTP;" /* ignore various signals */
501 "exec<&-;exec>&-;" /* close stdin and stdout */
502 "while :;do sleep 3600;done";
503 // let config file override this script
504 const char* fmt = shellScriptFmt;
505 if (m_outputTermKeepScript.length() != 0) {
506 fmt = m_outputTermKeepScript.data();
509 QString shellScript;
510 shellScript.sprintf(fmt, fifoName.data());
511 TRACE("output window script is " + shellScript);
513 QString title = kapp->caption();
514 title += i18n(": Program output");
516 // parse the command line specified in the preferences
517 ValArray<QString> cmdParts;
518 splitCmdStr(m_outputTermCmdStr, cmdParts);
521 * Build the argv array. Thereby substitute special sequences:
523 struct {
524 char seq[4];
525 QString replace;
526 } substitute[] = {
527 { "%T", title },
528 { "%C", shellScript }
531 for (int i = 0; i < cmdParts.size(); i++) {
532 QString& str = cmdParts[i];
533 for (int j = sizeof(substitute)/sizeof(substitute[0])-1; j >= 0; j--) {
534 int pos = str.find(substitute[j].seq);
535 if (pos >= 0) {
536 str.replace(pos, 2, substitute[j].replace);
537 break; /* substitute only one sequence */
540 *m_outputTermProc << str;
545 if (m_outputTermProc->start())
547 // read the ttyname from the fifo
548 int f = ::open(fifoName, O_RDONLY);
549 if (f < 0) {
550 // error
551 ::unlink(fifoName);
552 return QString();
555 char ttyname[50];
556 int n = ::read(f, ttyname, sizeof(ttyname)-sizeof(char)); /* leave space for '\0' */
558 ::close(f);
559 ::unlink(fifoName);
561 if (n < 0) {
562 // error
563 return QString();
566 // remove whitespace
567 ttyname[n] = '\0';
568 QString tty = QString(ttyname).stripWhiteSpace();
569 TRACE("tty=" + tty);
570 return tty;
572 else
574 // error, could not start xterm
575 TRACE("fork failed for fifo " + fifoName);
576 ::unlink(fifoName);
577 shutdownTermWindow();
578 return QString();
582 void DebuggerMainWndBase::shutdownTermWindow()
584 delete m_outputTermProc;
585 m_outputTermProc = 0;
588 void DebuggerMainWndBase::setTerminalCmd(const QString& cmd)
590 m_outputTermCmdStr = cmd;
591 // revert to default if empty
592 if (m_outputTermCmdStr.isEmpty()) {
593 m_outputTermCmdStr = defaultTermCmdStr;
597 void DebuggerMainWndBase::slotDebuggerStarting()
599 if (m_debugger == 0) /* paranoia check */
600 return;
602 if (m_ttyLevel == m_debugger->ttyLevel())
605 else
607 // shut down terminal emulations we will not need
608 switch (m_ttyLevel) {
609 case KDebugger::ttySimpleOutputOnly:
610 ttyWindow()->deactivate();
611 break;
612 case KDebugger::ttyFull:
613 if (m_outputTermProc != 0) {
614 m_outputTermProc->kill();
615 // will be deleted in slot
617 break;
618 default: break;
621 m_ttyLevel = m_debugger->ttyLevel();
623 QString ttyName;
624 switch (m_ttyLevel) {
625 case KDebugger::ttySimpleOutputOnly:
626 ttyName = ttyWindow()->activate();
627 break;
628 case KDebugger::ttyFull:
629 if (m_outputTermProc == 0) {
630 // create an output window
631 ttyName = createOutputWindow();
632 TRACE(ttyName.isEmpty() ?
633 "createOuputWindow failed" : "successfully created output window");
635 break;
636 default: break;
639 m_debugger->setTerminal(ttyName);
643 void DebuggerMainWndBase::setDebuggerCmdStr(const QString& cmd)
645 m_debuggerCmdStr = cmd;
646 // make empty if it is the default
647 if (m_debuggerCmdStr == GdbDriver::defaultGdb()) {
648 m_debuggerCmdStr = QString();
653 #include "mainwndbase.moc"