Clean up context menu handling in the files window.
[kdbg.git] / kdbg / mainwndbase.cpp
blob42f3aa0aeb670e539149a1a63d77da40996ca7e0
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 "mainwndbase.h"
15 #include "debugger.h"
16 #include "gdbdriver.h"
17 #include "xsldbgdriver.h"
18 #include "prefdebugger.h"
19 #include "prefmisc.h"
20 #include "ttywnd.h"
21 #include "commandids.h"
22 #include "valarray.h"
23 #ifdef HAVE_CONFIG
24 #include "config.h"
25 #endif
26 #include "mydebug.h"
27 #ifdef HAVE_SYS_STAT_H
28 #include <sys/stat.h> /* mknod(2) */
29 #endif
30 #ifdef HAVE_FCNTL_H
31 #include <fcntl.h> /* open(2) */
32 #endif
33 #ifdef HAVE_UNISTD_H
34 #include <unistd.h> /* getpid, unlink etc. */
35 #endif
37 #define MAX_RECENT_FILES 4
39 WatchWindow::WatchWindow(QWidget* parent, const char* name, WFlags f) :
40 QWidget(parent, name, f),
41 m_watchEdit(this, "watch_edit"),
42 m_watchAdd(i18n(" Add "), this, "watch_add"),
43 m_watchDelete(i18n(" Del "), this, "watch_delete"),
44 m_watchVariables(this, i18n("Expression"), "watch_variables"),
45 m_watchV(this, 0),
46 m_watchH(0)
48 // setup the layout
49 m_watchAdd.setMinimumSize(m_watchAdd.sizeHint());
50 m_watchDelete.setMinimumSize(m_watchDelete.sizeHint());
51 m_watchV.addLayout(&m_watchH, 0);
52 m_watchV.addWidget(&m_watchVariables, 10);
53 m_watchH.addWidget(&m_watchEdit, 10);
54 m_watchH.addWidget(&m_watchAdd, 0);
55 m_watchH.addWidget(&m_watchDelete, 0);
57 connect(&m_watchEdit, SIGNAL(returnPressed()), SIGNAL(addWatch()));
58 connect(&m_watchAdd, SIGNAL(clicked()), SIGNAL(addWatch()));
59 connect(&m_watchDelete, SIGNAL(clicked()), SIGNAL(deleteWatch()));
60 connect(&m_watchVariables, SIGNAL(currentChanged(QListViewItem*)),
61 SLOT(slotWatchHighlighted()));
63 m_watchVariables.installEventFilter(this);
66 WatchWindow::~WatchWindow()
70 bool WatchWindow::eventFilter(QObject*, QEvent* ev)
72 if (ev->type() == QEvent::KeyPress)
74 QKeyEvent* kev = static_cast<QKeyEvent*>(ev);
75 if (kev->key() == Key_Delete) {
76 emit deleteWatch();
77 return true;
80 return false;
84 // place the text of the hightlighted watch expr in the edit field
85 void WatchWindow::slotWatchHighlighted()
87 VarTree* expr = m_watchVariables.selectedItem();
88 QString text = expr ? expr->computeExpr() : QString();
89 m_watchEdit.setText(text);
93 static void splitCmdStr(const QString& cmd, ValArray<QString>& parts)
95 QString str = cmd.simplifyWhiteSpace();
96 int start = 0;
97 int end;
98 while ((end = str.find(' ', start)) >= 0) {
99 parts.append(str.mid(start, end-start));
100 start = end+1;
102 parts.append(str.mid(start, str.length()-start));
106 const char defaultTermCmdStr[] = "xterm -name kdbgio -title %T -e sh -c %C";
107 const char defaultSourceFilter[] = "*.c *.cc *.cpp *.c++ *.C *.CC";
108 const char defaultHeaderFilter[] = "*.h *.hh *.hpp *.h++";
111 DebuggerMainWndBase::DebuggerMainWndBase() :
112 m_outputTermCmdStr(defaultTermCmdStr),
113 m_outputTermProc(0),
114 m_ttyLevel(-1), /* no tty yet */
115 #ifdef GDB_TRANSCRIPT
116 m_transcriptFile(GDB_TRANSCRIPT),
117 #endif
118 m_popForeground(false),
119 m_backTimeout(1000),
120 m_tabWidth(0),
121 m_sourceFilter(defaultSourceFilter),
122 m_headerFilter(defaultHeaderFilter),
123 m_debugger(0)
125 m_statusActive = i18n("active");
128 DebuggerMainWndBase::~DebuggerMainWndBase()
130 delete m_debugger;
132 // if the output window is open, close it
133 if (m_outputTermProc != 0) {
134 m_outputTermProc->disconnect(); /* ignore signals */
135 m_outputTermProc->kill();
136 shutdownTermWindow();
140 void DebuggerMainWndBase::setupDebugger(QWidget* parent,
141 ExprWnd* localVars,
142 ExprWnd* watchVars,
143 QListBox* backtrace)
145 m_debugger = new KDebugger(parent, localVars, watchVars, backtrace);
147 QObject::connect(m_debugger, SIGNAL(updateStatusMessage()),
148 parent, SLOT(slotNewStatusMsg()));
149 QObject::connect(m_debugger, SIGNAL(updateUI()),
150 parent, SLOT(updateUI()));
152 QObject::connect(m_debugger, SIGNAL(breakpointsChanged()),
153 parent, SLOT(updateLineItems()));
155 QObject::connect(m_debugger, SIGNAL(debuggerStarting()),
156 parent, SLOT(slotDebuggerStarting()));
160 void DebuggerMainWndBase::setCoreFile(const QString& corefile)
162 assert(m_debugger != 0);
163 m_debugger->useCoreFile(corefile, true);
166 void DebuggerMainWndBase::setRemoteDevice(const QString& remoteDevice)
168 if (m_debugger != 0) {
169 m_debugger->setRemoteDevice(remoteDevice);
173 void DebuggerMainWndBase::setTranscript(const char* name)
175 m_transcriptFile = name;
176 if (m_debugger != 0 && m_debugger->driver() != 0)
177 m_debugger->driver()->setLogFileName(m_transcriptFile);
180 const char OutputWindowGroup[] = "OutputWindow";
181 const char TermCmdStr[] = "TermCmdStr";
182 const char KeepScript[] = "KeepScript";
183 const char DebuggerGroup[] = "Debugger";
184 const char DebuggerCmdStr[] = "DebuggerCmdStr";
185 const char PreferencesGroup[] = "Preferences";
186 const char PopForeground[] = "PopForeground";
187 const char BackTimeout[] = "BackTimeout";
188 const char TabWidth[] = "TabWidth";
189 const char FilesGroup[] = "Files";
190 const char SourceFileFilter[] = "SourceFileFilter";
191 const char HeaderFileFilter[] = "HeaderFileFilter";
192 const char GeneralGroup[] = "General";
194 void DebuggerMainWndBase::saveSettings(KConfig* config)
196 if (m_debugger != 0) {
197 m_debugger->saveSettings(config);
200 KConfigGroupSaver g(config, OutputWindowGroup);
201 config->writeEntry(TermCmdStr, m_outputTermCmdStr);
203 config->setGroup(DebuggerGroup);
204 config->writeEntry(DebuggerCmdStr, m_debuggerCmdStr);
206 config->setGroup(PreferencesGroup);
207 config->writeEntry(PopForeground, m_popForeground);
208 config->writeEntry(BackTimeout, m_backTimeout);
209 config->writeEntry(TabWidth, m_tabWidth);
210 config->writeEntry(SourceFileFilter, m_sourceFilter);
211 config->writeEntry(HeaderFileFilter, m_headerFilter);
214 void DebuggerMainWndBase::restoreSettings(KConfig* config)
216 if (m_debugger != 0) {
217 m_debugger->restoreSettings(config);
220 KConfigGroupSaver g(config, OutputWindowGroup);
222 * For debugging and emergency purposes, let the config file override
223 * the shell script that is used to keep the output window open. This
224 * string must have EXACTLY 1 %s sequence in it.
226 setTerminalCmd(config->readEntry(TermCmdStr, defaultTermCmdStr));
227 m_outputTermKeepScript = config->readEntry(KeepScript);
229 config->setGroup(DebuggerGroup);
230 setDebuggerCmdStr(config->readEntry(DebuggerCmdStr));
232 config->setGroup(PreferencesGroup);
233 m_popForeground = config->readBoolEntry(PopForeground, false);
234 m_backTimeout = config->readNumEntry(BackTimeout, 1000);
235 m_tabWidth = config->readNumEntry(TabWidth, 0);
236 m_sourceFilter = config->readEntry(SourceFileFilter, m_sourceFilter);
237 m_headerFilter = config->readEntry(HeaderFileFilter, m_headerFilter);
240 void DebuggerMainWndBase::setAttachPid(const QString& pid)
242 assert(m_debugger != 0);
243 m_debugger->setAttachPid(pid);
246 bool DebuggerMainWndBase::debugProgram(const QString& executable,
247 QCString lang, QWidget* parent)
249 assert(m_debugger != 0);
251 TRACE(QString().sprintf("trying language '%s'...", lang.data()));
252 DebuggerDriver* driver = driverFromLang(lang);
254 if (driver == 0)
256 // see if there is a language in the per-program config file
257 QString configName = m_debugger->getConfigForExe(executable);
258 if (QFile::exists(configName))
260 KSimpleConfig c(configName, true); // read-only
261 c.setGroup(GeneralGroup);
263 // Using "GDB" as default here is for backwards compatibility:
264 // The config file exists but doesn't have an entry,
265 // so it must have been created by an old version of KDbg
266 // that had only the GDB driver.
267 lang = c.readEntry(KDebugger::DriverNameEntry, "GDB").latin1();
269 TRACE(QString().sprintf("...bad, trying config driver %s...",
270 lang.data()));
271 driver = driverFromLang(lang);
275 if (driver == 0)
277 QCString name = driverNameFromFile(executable);
279 TRACE(QString().sprintf("...no luck, trying %s derived"
280 " from file contents", name.data()));
281 driver = driverFromLang(name);
283 if (driver == 0)
285 // oops
286 QString msg = i18n("Don't know how to debug language `%1'");
287 KMessageBox::sorry(parent, msg.arg(lang));
288 return false;
291 driver->setLogFileName(m_transcriptFile);
293 bool success = m_debugger->debugProgram(executable, driver);
295 if (!success)
297 delete driver;
299 QString msg = i18n("Could not start the debugger process.\n"
300 "Please shut down KDbg and resolve the problem.");
301 KMessageBox::sorry(parent, msg);
304 return success;
307 // derive driver from language
308 DebuggerDriver* DebuggerMainWndBase::driverFromLang(QCString lang)
310 // lang is needed in all lowercase
311 lang = lang.lower();
313 // The following table relates languages and debugger drivers
314 static const struct L {
315 const char* shortest; // abbreviated to this is still unique
316 const char* full; // full name of language
317 int driver;
318 } langs[] = {
319 { "c", "c++", 1 },
320 { "f", "fortran", 1 },
321 { "p", "python", 3 },
322 { "x", "xslt", 2 },
323 // the following are actually driver names
324 { "gdb", "gdb", 1 },
325 { "xsldbg", "xsldbg", 2 },
327 const int N = sizeof(langs)/sizeof(langs[0]);
329 // lookup the language name
330 int driverID = 0;
331 for (int i = 0; i < N; i++)
333 const L& l = langs[i];
335 // shortest must match
336 if (strncmp(l.shortest, lang, strlen(l.shortest)) != 0)
337 continue;
339 // lang must not be longer than the full name, and it must match
340 if (lang.length() <= strlen(l.full) &&
341 strncmp(l.full, lang, lang.length()) == 0)
343 driverID = l.driver;
344 break;
347 DebuggerDriver* driver = 0;
348 switch (driverID) {
349 case 1:
351 GdbDriver* gdb = new GdbDriver;
352 gdb->setDefaultInvocation(m_debuggerCmdStr);
353 driver = gdb;
355 break;
356 case 2:
357 driver = new XsldbgDriver;
358 break;
359 default:
360 // unknown language
361 break;
363 return driver;
367 * Try to guess the language to use from the contents of the file.
369 QCString DebuggerMainWndBase::driverNameFromFile(const QString& exe)
371 /* Inprecise but simple test to see if file is in XSLT language */
372 if (exe.right(4).lower() == ".xsl")
373 return "XSLT";
375 return "GDB";
378 // helper that gets a file name (it only differs in the caption of the dialog)
379 QString DebuggerMainWndBase::myGetFileName(QString caption,
380 QString dir, QString filter,
381 QWidget* parent)
383 QString filename;
384 KFileDialog dlg(dir, filter, parent, "filedialog", true);
386 dlg.setCaption(caption);
388 if (dlg.exec() == QDialog::Accepted)
389 filename = dlg.selectedFile();
391 return filename;
394 void DebuggerMainWndBase::newStatusMsg(KStatusBar* statusbar)
396 QString msg = m_debugger->statusMessage();
397 statusbar->changeItem(msg, ID_STATUS_MSG);
400 void DebuggerMainWndBase::doGlobalOptions(QWidget* parent)
402 QTabDialog dlg(parent, "global_options", true);
403 QString title = kapp->caption();
404 title += i18n(": Global options");
405 dlg.setCaption(title);
406 dlg.setCancelButton(i18n("Cancel"));
407 dlg.setOKButton(i18n("OK"));
409 PrefDebugger prefDebugger(&dlg);
410 prefDebugger.setDebuggerCmd(m_debuggerCmdStr.isEmpty() ?
411 GdbDriver::defaultGdb() : m_debuggerCmdStr);
412 prefDebugger.setTerminal(m_outputTermCmdStr);
414 PrefMisc prefMisc(&dlg);
415 prefMisc.setPopIntoForeground(m_popForeground);
416 prefMisc.setBackTimeout(m_backTimeout);
417 prefMisc.setTabWidth(m_tabWidth);
418 prefMisc.setSourceFilter(m_sourceFilter);
419 prefMisc.setHeaderFilter(m_headerFilter);
421 dlg.addTab(&prefDebugger, i18n("&Debugger"));
422 dlg.addTab(&prefMisc, i18n("&Miscellaneous"));
423 if (dlg.exec() == QDialog::Accepted)
425 setDebuggerCmdStr(prefDebugger.debuggerCmd());
426 setTerminalCmd(prefDebugger.terminal());
427 m_popForeground = prefMisc.popIntoForeground();
428 m_backTimeout = prefMisc.backTimeout();
429 m_tabWidth = prefMisc.tabWidth();
430 m_sourceFilter = prefMisc.sourceFilter();
431 if (m_sourceFilter.isEmpty())
432 m_sourceFilter = defaultSourceFilter;
433 m_headerFilter = prefMisc.headerFilter();
434 if (m_headerFilter.isEmpty())
435 m_headerFilter = defaultHeaderFilter;
439 const char fifoNameBase[] = "/tmp/kdbgttywin%05d";
442 * We use the scope operator :: in this function, so that we don't
443 * accidentally use the wrong close() function (I've been bitten ;-),
444 * outch!) (We use it for all the libc functions, to be consistent...)
446 QString DebuggerMainWndBase::createOutputWindow()
448 // create a name for a fifo
449 QString fifoName;
450 fifoName.sprintf(fifoNameBase, ::getpid());
452 // create a fifo that will pass in the tty name
453 ::unlink(fifoName); /* remove remnants */
454 #ifdef HAVE_MKFIFO
455 if (::mkfifo(fifoName, S_IRUSR|S_IWUSR) < 0) {
456 // failed
457 TRACE("mkfifo " + fifoName + " failed");
458 return QString();
460 #else
461 if (::mknod(fifoName, S_IFIFO | S_IRUSR|S_IWUSR, 0) < 0) {
462 // failed
463 TRACE("mknod " + fifoName + " failed");
464 return QString();
466 #endif
468 m_outputTermProc = new KProcess;
472 * Spawn an xterm that in turn runs a shell script that passes us
473 * back the terminal name and then only sits and waits.
475 static const char shellScriptFmt[] =
476 "tty>%s;"
477 "trap \"\" INT QUIT TSTP;" /* ignore various signals */
478 "exec<&-;exec>&-;" /* close stdin and stdout */
479 "while :;do sleep 3600;done";
480 // let config file override this script
481 const char* fmt = shellScriptFmt;
482 if (m_outputTermKeepScript.length() != 0) {
483 fmt = m_outputTermKeepScript.data();
486 QString shellScript;
487 shellScript.sprintf(fmt, fifoName.data());
488 TRACE("output window script is " + shellScript);
490 QString title = kapp->caption();
491 title += i18n(": Program output");
493 // parse the command line specified in the preferences
494 ValArray<QString> cmdParts;
495 splitCmdStr(m_outputTermCmdStr, cmdParts);
498 * Build the argv array. Thereby substitute special sequences:
500 struct {
501 char seq[4];
502 QString replace;
503 } substitute[] = {
504 { "%T", title },
505 { "%C", shellScript }
508 for (int i = 0; i < cmdParts.size(); i++) {
509 QString& str = cmdParts[i];
510 for (int j = sizeof(substitute)/sizeof(substitute[0])-1; j >= 0; j--) {
511 int pos = str.find(substitute[j].seq);
512 if (pos >= 0) {
513 str.replace(pos, 2, substitute[j].replace);
514 break; /* substitute only one sequence */
517 *m_outputTermProc << str;
522 if (m_outputTermProc->start())
524 // read the ttyname from the fifo
525 int f = ::open(fifoName, O_RDONLY);
526 if (f < 0) {
527 // error
528 ::unlink(fifoName);
529 return QString();
532 char ttyname[50];
533 int n = ::read(f, ttyname, sizeof(ttyname)-sizeof(char)); /* leave space for '\0' */
535 ::close(f);
536 ::unlink(fifoName);
538 if (n < 0) {
539 // error
540 return QString();
543 // remove whitespace
544 ttyname[n] = '\0';
545 QString tty = QString(ttyname).stripWhiteSpace();
546 TRACE("tty=" + tty);
547 return tty;
549 else
551 // error, could not start xterm
552 TRACE("fork failed for fifo " + fifoName);
553 ::unlink(fifoName);
554 shutdownTermWindow();
555 return QString();
559 void DebuggerMainWndBase::shutdownTermWindow()
561 delete m_outputTermProc;
562 m_outputTermProc = 0;
565 void DebuggerMainWndBase::setTerminalCmd(const QString& cmd)
567 m_outputTermCmdStr = cmd;
568 // revert to default if empty
569 if (m_outputTermCmdStr.isEmpty()) {
570 m_outputTermCmdStr = defaultTermCmdStr;
574 void DebuggerMainWndBase::slotDebuggerStarting()
576 if (m_debugger == 0) /* paranoia check */
577 return;
579 if (m_ttyLevel == m_debugger->ttyLevel())
582 else
584 // shut down terminal emulations we will not need
585 switch (m_ttyLevel) {
586 case KDebugger::ttySimpleOutputOnly:
587 ttyWindow()->deactivate();
588 break;
589 case KDebugger::ttyFull:
590 if (m_outputTermProc != 0) {
591 m_outputTermProc->kill();
592 // will be deleted in slot
594 break;
595 default: break;
598 m_ttyLevel = m_debugger->ttyLevel();
600 QString ttyName;
601 switch (m_ttyLevel) {
602 case KDebugger::ttySimpleOutputOnly:
603 ttyName = ttyWindow()->activate();
604 break;
605 case KDebugger::ttyFull:
606 if (m_outputTermProc == 0) {
607 // create an output window
608 ttyName = createOutputWindow();
609 TRACE(ttyName.isEmpty() ?
610 "createOuputWindow failed" : "successfully created output window");
612 break;
613 default: break;
616 m_debugger->setTerminal(ttyName);
620 void DebuggerMainWndBase::setDebuggerCmdStr(const QString& cmd)
622 m_debuggerCmdStr = cmd;
623 // make empty if it is the default
624 if (m_debuggerCmdStr == GdbDriver::defaultGdb()) {
625 m_debuggerCmdStr = QString();
630 #include "mainwndbase.moc"