Code cleanup of the treatment of display expressions.
[kdbg.git] / kdbg / mainwndbase.cpp
blob6f39a242aa103d1bcb953d3a76b3d34860d9a6bf
1 /*
2 * Copyright Johannes Sixt
3 * This file is licensed under the GNU General Public License Version 2.
4 * See the file COPYING in the toplevel directory of the source directory.
5 */
7 #include <kapp.h>
8 #include <klocale.h> /* i18n */
9 #include <kconfig.h>
10 #include <kmessagebox.h>
11 #include <kstatusbar.h>
12 #include <kfiledialog.h>
13 #include <qtabdialog.h>
14 #include <qfile.h>
15 #include <qdragobject.h>
16 #include "mainwndbase.h"
17 #include "debugger.h"
18 #include "gdbdriver.h"
19 #include "xsldbgdriver.h"
20 #include "prefdebugger.h"
21 #include "prefmisc.h"
22 #include "ttywnd.h"
23 #include "commandids.h"
24 #include "valarray.h"
25 #ifdef HAVE_CONFIG
26 #include "config.h"
27 #endif
28 #include "mydebug.h"
29 #ifdef HAVE_SYS_STAT_H
30 #include <sys/stat.h> /* mknod(2) */
31 #endif
32 #ifdef HAVE_FCNTL_H
33 #include <fcntl.h> /* open(2) */
34 #endif
35 #ifdef HAVE_UNISTD_H
36 #include <unistd.h> /* getpid, unlink etc. */
37 #endif
39 #define MAX_RECENT_FILES 4
41 WatchWindow::WatchWindow(QWidget* parent, const char* name, WFlags f) :
42 QWidget(parent, name, f),
43 m_watchEdit(this, "watch_edit"),
44 m_watchAdd(i18n(" Add "), this, "watch_add"),
45 m_watchDelete(i18n(" Del "), this, "watch_delete"),
46 m_watchVariables(this, i18n("Expression"), "watch_variables"),
47 m_watchV(this, 0),
48 m_watchH(0)
50 // setup the layout
51 m_watchAdd.setMinimumSize(m_watchAdd.sizeHint());
52 m_watchDelete.setMinimumSize(m_watchDelete.sizeHint());
53 m_watchV.addLayout(&m_watchH, 0);
54 m_watchV.addWidget(&m_watchVariables, 10);
55 m_watchH.addWidget(&m_watchEdit, 10);
56 m_watchH.addWidget(&m_watchAdd, 0);
57 m_watchH.addWidget(&m_watchDelete, 0);
59 connect(&m_watchEdit, SIGNAL(returnPressed()), SIGNAL(addWatch()));
60 connect(&m_watchAdd, SIGNAL(clicked()), SIGNAL(addWatch()));
61 connect(&m_watchDelete, SIGNAL(clicked()), SIGNAL(deleteWatch()));
62 connect(&m_watchVariables, SIGNAL(currentChanged(QListViewItem*)),
63 SLOT(slotWatchHighlighted()));
65 m_watchVariables.installEventFilter(this);
66 setAcceptDrops(true);
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;
86 void WatchWindow::dragEnterEvent(QDragEnterEvent* event)
88 event->accept(QTextDrag::canDecode(event));
91 void WatchWindow::dropEvent(QDropEvent* event)
93 QString text;
94 if (QTextDrag::decode(event, text))
96 // pick only the first line
97 text = text.stripWhiteSpace();
98 int pos = text.find('\n');
99 if (pos > 0)
100 text.truncate(pos);
101 text = text.stripWhiteSpace();
102 if (!text.isEmpty())
103 emit textDropped(text);
108 // place the text of the hightlighted watch expr in the edit field
109 void WatchWindow::slotWatchHighlighted()
111 VarTree* expr = m_watchVariables.selectedItem();
112 QString text = expr ? expr->computeExpr() : QString();
113 m_watchEdit.setText(text);
117 static void splitCmdStr(const QString& cmd, ValArray<QString>& parts)
119 QString str = cmd.simplifyWhiteSpace();
120 int start = 0;
121 int end;
122 while ((end = str.find(' ', start)) >= 0) {
123 parts.append(str.mid(start, end-start));
124 start = end+1;
126 parts.append(str.mid(start, str.length()-start));
130 const char defaultTermCmdStr[] = "xterm -name kdbgio -title %T -e sh -c %C";
131 const char defaultSourceFilter[] = "*.c *.cc *.cpp *.c++ *.C *.CC";
132 const char defaultHeaderFilter[] = "*.h *.hh *.hpp *.h++";
135 DebuggerMainWndBase::DebuggerMainWndBase() :
136 m_outputTermCmdStr(defaultTermCmdStr),
137 m_outputTermProc(0),
138 m_ttyLevel(-1), /* no tty yet */
139 #ifdef GDB_TRANSCRIPT
140 m_transcriptFile(GDB_TRANSCRIPT),
141 #endif
142 m_popForeground(false),
143 m_backTimeout(1000),
144 m_tabWidth(0),
145 m_sourceFilter(defaultSourceFilter),
146 m_headerFilter(defaultHeaderFilter),
147 m_debugger(0)
149 m_statusActive = i18n("active");
152 DebuggerMainWndBase::~DebuggerMainWndBase()
154 delete m_debugger;
156 // if the output window is open, close it
157 if (m_outputTermProc != 0) {
158 m_outputTermProc->disconnect(); /* ignore signals */
159 m_outputTermProc->kill();
160 shutdownTermWindow();
164 void DebuggerMainWndBase::setupDebugger(QWidget* parent,
165 ExprWnd* localVars,
166 ExprWnd* watchVars,
167 QListBox* backtrace)
169 m_debugger = new KDebugger(parent, localVars, watchVars, backtrace);
171 QObject::connect(m_debugger, SIGNAL(updateStatusMessage()),
172 parent, SLOT(slotNewStatusMsg()));
173 QObject::connect(m_debugger, SIGNAL(updateUI()),
174 parent, SLOT(updateUI()));
176 QObject::connect(m_debugger, SIGNAL(breakpointsChanged()),
177 parent, SLOT(updateLineItems()));
179 QObject::connect(m_debugger, SIGNAL(debuggerStarting()),
180 parent, SLOT(slotDebuggerStarting()));
184 void DebuggerMainWndBase::setCoreFile(const QString& corefile)
186 assert(m_debugger != 0);
187 m_debugger->useCoreFile(corefile, true);
190 void DebuggerMainWndBase::setRemoteDevice(const QString& remoteDevice)
192 if (m_debugger != 0) {
193 m_debugger->setRemoteDevice(remoteDevice);
197 void DebuggerMainWndBase::setTranscript(const char* name)
199 m_transcriptFile = name;
200 if (m_debugger != 0 && m_debugger->driver() != 0)
201 m_debugger->driver()->setLogFileName(m_transcriptFile);
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 SourceFileFilter[] = "SourceFileFilter";
215 const char HeaderFileFilter[] = "HeaderFileFilter";
216 const char GeneralGroup[] = "General";
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);
238 void DebuggerMainWndBase::restoreSettings(KConfig* config)
240 if (m_debugger != 0) {
241 m_debugger->restoreSettings(config);
244 KConfigGroupSaver g(config, OutputWindowGroup);
246 * For debugging and emergency purposes, let the config file override
247 * the shell script that is used to keep the output window open. This
248 * string must have EXACTLY 1 %s sequence in it.
250 setTerminalCmd(config->readEntry(TermCmdStr, defaultTermCmdStr));
251 m_outputTermKeepScript = config->readEntry(KeepScript);
253 config->setGroup(DebuggerGroup);
254 setDebuggerCmdStr(config->readEntry(DebuggerCmdStr));
256 config->setGroup(PreferencesGroup);
257 m_popForeground = config->readBoolEntry(PopForeground, false);
258 m_backTimeout = config->readNumEntry(BackTimeout, 1000);
259 m_tabWidth = config->readNumEntry(TabWidth, 0);
260 m_sourceFilter = config->readEntry(SourceFileFilter, m_sourceFilter);
261 m_headerFilter = config->readEntry(HeaderFileFilter, m_headerFilter);
264 void DebuggerMainWndBase::setAttachPid(const QString& pid)
266 assert(m_debugger != 0);
267 m_debugger->setAttachPid(pid);
270 bool DebuggerMainWndBase::debugProgram(const QString& executable,
271 QCString lang, QWidget* parent)
273 assert(m_debugger != 0);
275 TRACE(QString().sprintf("trying language '%s'...", lang.data()));
276 DebuggerDriver* driver = driverFromLang(lang);
278 if (driver == 0)
280 // see if there is a language in the per-program config file
281 QString configName = m_debugger->getConfigForExe(executable);
282 if (QFile::exists(configName))
284 KSimpleConfig c(configName, true); // read-only
285 c.setGroup(GeneralGroup);
287 // Using "GDB" as default here is for backwards compatibility:
288 // The config file exists but doesn't have an entry,
289 // so it must have been created by an old version of KDbg
290 // that had only the GDB driver.
291 lang = c.readEntry(KDebugger::DriverNameEntry, "GDB").latin1();
293 TRACE(QString().sprintf("...bad, trying config driver %s...",
294 lang.data()));
295 driver = driverFromLang(lang);
299 if (driver == 0)
301 QCString name = driverNameFromFile(executable);
303 TRACE(QString().sprintf("...no luck, trying %s derived"
304 " from file contents", name.data()));
305 driver = driverFromLang(name);
307 if (driver == 0)
309 // oops
310 QString msg = i18n("Don't know how to debug language `%1'");
311 KMessageBox::sorry(parent, msg.arg(lang));
312 return false;
315 driver->setLogFileName(m_transcriptFile);
317 bool success = m_debugger->debugProgram(executable, driver);
319 if (!success)
321 delete driver;
323 QString msg = i18n("Could not start the debugger process.\n"
324 "Please shut down KDbg and resolve the problem.");
325 KMessageBox::sorry(parent, msg);
328 return success;
331 // derive driver from language
332 DebuggerDriver* DebuggerMainWndBase::driverFromLang(QCString lang)
334 // lang is needed in all lowercase
335 lang = lang.lower();
337 // The following table relates languages and debugger drivers
338 static const struct L {
339 const char* shortest; // abbreviated to this is still unique
340 const char* full; // full name of language
341 int driver;
342 } langs[] = {
343 { "c", "c++", 1 },
344 { "f", "fortran", 1 },
345 { "p", "python", 3 },
346 { "x", "xslt", 2 },
347 // the following are actually driver names
348 { "gdb", "gdb", 1 },
349 { "xsldbg", "xsldbg", 2 },
351 const int N = sizeof(langs)/sizeof(langs[0]);
353 // lookup the language name
354 int driverID = 0;
355 for (int i = 0; i < N; i++)
357 const L& l = langs[i];
359 // shortest must match
360 if (strncmp(l.shortest, lang, strlen(l.shortest)) != 0)
361 continue;
363 // lang must not be longer than the full name, and it must match
364 if (lang.length() <= strlen(l.full) &&
365 strncmp(l.full, lang, lang.length()) == 0)
367 driverID = l.driver;
368 break;
371 DebuggerDriver* driver = 0;
372 switch (driverID) {
373 case 1:
375 GdbDriver* gdb = new GdbDriver;
376 gdb->setDefaultInvocation(m_debuggerCmdStr);
377 driver = gdb;
379 break;
380 case 2:
381 driver = new XsldbgDriver;
382 break;
383 default:
384 // unknown language
385 break;
387 return driver;
391 * Try to guess the language to use from the contents of the file.
393 QCString DebuggerMainWndBase::driverNameFromFile(const QString& exe)
395 /* Inprecise but simple test to see if file is in XSLT language */
396 if (exe.right(4).lower() == ".xsl")
397 return "XSLT";
399 return "GDB";
402 // helper that gets a file name (it only differs in the caption of the dialog)
403 QString DebuggerMainWndBase::myGetFileName(QString caption,
404 QString dir, QString filter,
405 QWidget* parent)
407 QString filename;
408 KFileDialog dlg(dir, filter, parent, "filedialog", true);
410 dlg.setCaption(caption);
412 if (dlg.exec() == QDialog::Accepted)
413 filename = dlg.selectedFile();
415 return filename;
418 void DebuggerMainWndBase::newStatusMsg(KStatusBar* statusbar)
420 QString msg = m_debugger->statusMessage();
421 statusbar->changeItem(msg, ID_STATUS_MSG);
424 void DebuggerMainWndBase::doGlobalOptions(QWidget* parent)
426 QTabDialog dlg(parent, "global_options", true);
427 QString title = kapp->caption();
428 title += i18n(": Global options");
429 dlg.setCaption(title);
430 dlg.setCancelButton(i18n("Cancel"));
431 dlg.setOKButton(i18n("OK"));
433 PrefDebugger prefDebugger(&dlg);
434 prefDebugger.setDebuggerCmd(m_debuggerCmdStr.isEmpty() ?
435 GdbDriver::defaultGdb() : m_debuggerCmdStr);
436 prefDebugger.setTerminal(m_outputTermCmdStr);
438 PrefMisc prefMisc(&dlg);
439 prefMisc.setPopIntoForeground(m_popForeground);
440 prefMisc.setBackTimeout(m_backTimeout);
441 prefMisc.setTabWidth(m_tabWidth);
442 prefMisc.setSourceFilter(m_sourceFilter);
443 prefMisc.setHeaderFilter(m_headerFilter);
445 dlg.addTab(&prefDebugger, i18n("&Debugger"));
446 dlg.addTab(&prefMisc, i18n("&Miscellaneous"));
447 if (dlg.exec() == QDialog::Accepted)
449 setDebuggerCmdStr(prefDebugger.debuggerCmd());
450 setTerminalCmd(prefDebugger.terminal());
451 m_popForeground = prefMisc.popIntoForeground();
452 m_backTimeout = prefMisc.backTimeout();
453 m_tabWidth = prefMisc.tabWidth();
454 m_sourceFilter = prefMisc.sourceFilter();
455 if (m_sourceFilter.isEmpty())
456 m_sourceFilter = defaultSourceFilter;
457 m_headerFilter = prefMisc.headerFilter();
458 if (m_headerFilter.isEmpty())
459 m_headerFilter = defaultHeaderFilter;
463 const char fifoNameBase[] = "/tmp/kdbgttywin%05d";
466 * We use the scope operator :: in this function, so that we don't
467 * accidentally use the wrong close() function (I've been bitten ;-),
468 * outch!) (We use it for all the libc functions, to be consistent...)
470 QString DebuggerMainWndBase::createOutputWindow()
472 // create a name for a fifo
473 QString fifoName;
474 fifoName.sprintf(fifoNameBase, ::getpid());
476 // create a fifo that will pass in the tty name
477 ::unlink(fifoName); /* remove remnants */
478 #ifdef HAVE_MKFIFO
479 if (::mkfifo(fifoName, S_IRUSR|S_IWUSR) < 0) {
480 // failed
481 TRACE("mkfifo " + fifoName + " failed");
482 return QString();
484 #else
485 if (::mknod(fifoName, S_IFIFO | S_IRUSR|S_IWUSR, 0) < 0) {
486 // failed
487 TRACE("mknod " + fifoName + " failed");
488 return QString();
490 #endif
492 m_outputTermProc = new KProcess;
496 * Spawn an xterm that in turn runs a shell script that passes us
497 * back the terminal name and then only sits and waits.
499 static const char shellScriptFmt[] =
500 "tty>%s;"
501 "trap \"\" INT QUIT TSTP;" /* ignore various signals */
502 "exec<&-;exec>&-;" /* close stdin and stdout */
503 "while :;do sleep 3600;done";
504 // let config file override this script
505 const char* fmt = shellScriptFmt;
506 if (m_outputTermKeepScript.length() != 0) {
507 fmt = m_outputTermKeepScript.data();
510 QString shellScript;
511 shellScript.sprintf(fmt, fifoName.data());
512 TRACE("output window script is " + shellScript);
514 QString title = kapp->caption();
515 title += i18n(": Program output");
517 // parse the command line specified in the preferences
518 ValArray<QString> cmdParts;
519 splitCmdStr(m_outputTermCmdStr, cmdParts);
522 * Build the argv array. Thereby substitute special sequences:
524 struct {
525 char seq[4];
526 QString replace;
527 } substitute[] = {
528 { "%T", title },
529 { "%C", shellScript }
532 for (int i = 0; i < cmdParts.size(); i++) {
533 QString& str = cmdParts[i];
534 for (int j = sizeof(substitute)/sizeof(substitute[0])-1; j >= 0; j--) {
535 int pos = str.find(substitute[j].seq);
536 if (pos >= 0) {
537 str.replace(pos, 2, substitute[j].replace);
538 break; /* substitute only one sequence */
541 *m_outputTermProc << str;
546 if (m_outputTermProc->start())
548 // read the ttyname from the fifo
549 int f = ::open(fifoName, O_RDONLY);
550 if (f < 0) {
551 // error
552 ::unlink(fifoName);
553 return QString();
556 char ttyname[50];
557 int n = ::read(f, ttyname, sizeof(ttyname)-sizeof(char)); /* leave space for '\0' */
559 ::close(f);
560 ::unlink(fifoName);
562 if (n < 0) {
563 // error
564 return QString();
567 // remove whitespace
568 ttyname[n] = '\0';
569 QString tty = QString(ttyname).stripWhiteSpace();
570 TRACE("tty=" + tty);
571 return tty;
573 else
575 // error, could not start xterm
576 TRACE("fork failed for fifo " + fifoName);
577 ::unlink(fifoName);
578 shutdownTermWindow();
579 return QString();
583 void DebuggerMainWndBase::shutdownTermWindow()
585 delete m_outputTermProc;
586 m_outputTermProc = 0;
589 void DebuggerMainWndBase::setTerminalCmd(const QString& cmd)
591 m_outputTermCmdStr = cmd;
592 // revert to default if empty
593 if (m_outputTermCmdStr.isEmpty()) {
594 m_outputTermCmdStr = defaultTermCmdStr;
598 void DebuggerMainWndBase::slotDebuggerStarting()
600 if (m_debugger == 0) /* paranoia check */
601 return;
603 if (m_ttyLevel == m_debugger->ttyLevel())
606 else
608 // shut down terminal emulations we will not need
609 switch (m_ttyLevel) {
610 case KDebugger::ttySimpleOutputOnly:
611 ttyWindow()->deactivate();
612 break;
613 case KDebugger::ttyFull:
614 if (m_outputTermProc != 0) {
615 m_outputTermProc->kill();
616 // will be deleted in slot
618 break;
619 default: break;
622 m_ttyLevel = m_debugger->ttyLevel();
624 QString ttyName;
625 switch (m_ttyLevel) {
626 case KDebugger::ttySimpleOutputOnly:
627 ttyName = ttyWindow()->activate();
628 break;
629 case KDebugger::ttyFull:
630 if (m_outputTermProc == 0) {
631 // create an output window
632 ttyName = createOutputWindow();
633 TRACE(ttyName.isEmpty() ?
634 "createOuputWindow failed" : "successfully created output window");
636 break;
637 default: break;
640 m_debugger->setTerminal(ttyName);
644 void DebuggerMainWndBase::setDebuggerCmdStr(const QString& cmd)
646 m_debuggerCmdStr = cmd;
647 // make empty if it is the default
648 if (m_debuggerCmdStr == GdbDriver::defaultGdb()) {
649 m_debuggerCmdStr = QString();
654 #include "mainwndbase.moc"