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.
7 #include <kapplication.h>
8 #include <klocale.h> /* i18n */
10 #include <kmessagebox.h>
11 #include <kstatusbar.h>
12 #include <kfiledialog.h>
13 #include <qtabdialog.h>
15 #include <qdragobject.h>
16 #include "mainwndbase.h"
18 #include "gdbdriver.h"
19 #include "xsldbgdriver.h"
20 #include "prefdebugger.h"
23 #include "commandids.h"
28 #ifdef HAVE_SYS_STAT_H
29 #include <sys/stat.h> /* mknod(2) */
32 #include <fcntl.h> /* open(2) */
35 #include <unistd.h> /* getpid, unlink etc. */
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"),
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);
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
) {
85 void WatchWindow::dragEnterEvent(QDragEnterEvent
* event
)
87 event
->accept(QTextDrag::canDecode(event
));
90 void WatchWindow::dropEvent(QDropEvent
* event
)
93 if (QTextDrag::decode(event
, text
))
95 // pick only the first line
96 text
= text
.stripWhiteSpace();
97 int pos
= text
.find('\n');
100 text
= text
.stripWhiteSpace();
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 const char defaultTermCmdStr
[] = "xterm -name kdbgio -title %T -e sh -c %C";
117 const char defaultSourceFilter
[] = "*.c *.cc *.cpp *.c++ *.C *.CC";
118 const char defaultHeaderFilter
[] = "*.h *.hh *.hpp *.h++";
121 DebuggerMainWndBase::DebuggerMainWndBase() :
122 m_outputTermCmdStr(defaultTermCmdStr
),
124 m_ttyLevel(-1), /* no tty yet */
125 #ifdef GDB_TRANSCRIPT
126 m_transcriptFile(GDB_TRANSCRIPT
),
128 m_popForeground(false),
131 m_sourceFilter(defaultSourceFilter
),
132 m_headerFilter(defaultHeaderFilter
),
135 m_statusActive
= i18n("active");
138 DebuggerMainWndBase::~DebuggerMainWndBase()
142 // if the output window is open, close it
143 if (m_outputTermProc
!= 0) {
144 m_outputTermProc
->disconnect(); /* ignore signals */
145 m_outputTermProc
->kill();
146 shutdownTermWindow();
150 void DebuggerMainWndBase::setupDebugger(QWidget
* parent
,
155 m_debugger
= new KDebugger(parent
, localVars
, watchVars
, backtrace
);
157 QObject::connect(m_debugger
, SIGNAL(updateStatusMessage()),
158 parent
, SLOT(slotNewStatusMsg()));
159 QObject::connect(m_debugger
, SIGNAL(updateUI()),
160 parent
, SLOT(updateUI()));
162 QObject::connect(m_debugger
, SIGNAL(breakpointsChanged()),
163 parent
, SLOT(updateLineItems()));
165 QObject::connect(m_debugger
, SIGNAL(debuggerStarting()),
166 parent
, SLOT(slotDebuggerStarting()));
170 void DebuggerMainWndBase::setCoreFile(const QString
& corefile
)
172 assert(m_debugger
!= 0);
173 m_debugger
->useCoreFile(corefile
, true);
176 void DebuggerMainWndBase::setRemoteDevice(const QString
& remoteDevice
)
178 if (m_debugger
!= 0) {
179 m_debugger
->setRemoteDevice(remoteDevice
);
183 void DebuggerMainWndBase::overrideProgramArguments(const QString
& args
)
185 assert(m_debugger
!= 0);
186 m_debugger
->overrideProgramArguments(args
);
189 void DebuggerMainWndBase::setTranscript(const char* name
)
191 m_transcriptFile
= name
;
192 if (m_debugger
!= 0 && m_debugger
->driver() != 0)
193 m_debugger
->driver()->setLogFileName(m_transcriptFile
);
196 const char OutputWindowGroup
[] = "OutputWindow";
197 const char TermCmdStr
[] = "TermCmdStr";
198 const char KeepScript
[] = "KeepScript";
199 const char DebuggerGroup
[] = "Debugger";
200 const char DebuggerCmdStr
[] = "DebuggerCmdStr";
201 const char PreferencesGroup
[] = "Preferences";
202 const char PopForeground
[] = "PopForeground";
203 const char BackTimeout
[] = "BackTimeout";
204 const char TabWidth
[] = "TabWidth";
205 const char FilesGroup
[] = "Files";
206 const char SourceFileFilter
[] = "SourceFileFilter";
207 const char HeaderFileFilter
[] = "HeaderFileFilter";
208 const char GeneralGroup
[] = "General";
210 void DebuggerMainWndBase::saveSettings(KConfig
* config
)
212 if (m_debugger
!= 0) {
213 m_debugger
->saveSettings(config
);
216 KConfigGroupSaver
g(config
, OutputWindowGroup
);
217 config
->writeEntry(TermCmdStr
, m_outputTermCmdStr
);
219 config
->setGroup(DebuggerGroup
);
220 config
->writeEntry(DebuggerCmdStr
, m_debuggerCmdStr
);
222 config
->setGroup(PreferencesGroup
);
223 config
->writeEntry(PopForeground
, m_popForeground
);
224 config
->writeEntry(BackTimeout
, m_backTimeout
);
225 config
->writeEntry(TabWidth
, m_tabWidth
);
226 config
->writeEntry(SourceFileFilter
, m_sourceFilter
);
227 config
->writeEntry(HeaderFileFilter
, m_headerFilter
);
230 void DebuggerMainWndBase::restoreSettings(KConfig
* config
)
232 if (m_debugger
!= 0) {
233 m_debugger
->restoreSettings(config
);
236 KConfigGroupSaver
g(config
, OutputWindowGroup
);
238 * For debugging and emergency purposes, let the config file override
239 * the shell script that is used to keep the output window open. This
240 * string must have EXACTLY 1 %s sequence in it.
242 setTerminalCmd(config
->readEntry(TermCmdStr
, defaultTermCmdStr
));
243 m_outputTermKeepScript
= config
->readEntry(KeepScript
);
245 config
->setGroup(DebuggerGroup
);
246 setDebuggerCmdStr(config
->readEntry(DebuggerCmdStr
));
248 config
->setGroup(PreferencesGroup
);
249 m_popForeground
= config
->readBoolEntry(PopForeground
, false);
250 m_backTimeout
= config
->readNumEntry(BackTimeout
, 1000);
251 m_tabWidth
= config
->readNumEntry(TabWidth
, 0);
252 m_sourceFilter
= config
->readEntry(SourceFileFilter
, m_sourceFilter
);
253 m_headerFilter
= config
->readEntry(HeaderFileFilter
, m_headerFilter
);
256 void DebuggerMainWndBase::setAttachPid(const QString
& pid
)
258 assert(m_debugger
!= 0);
259 m_debugger
->setAttachPid(pid
);
262 bool DebuggerMainWndBase::debugProgram(const QString
& executable
,
263 QCString lang
, QWidget
* parent
)
265 assert(m_debugger
!= 0);
267 TRACE(QString().sprintf("trying language '%s'...", lang
.data()));
268 DebuggerDriver
* driver
= driverFromLang(lang
);
272 // see if there is a language in the per-program config file
273 QString configName
= m_debugger
->getConfigForExe(executable
);
274 if (QFile::exists(configName
))
276 KSimpleConfig
c(configName
, true); // read-only
277 c
.setGroup(GeneralGroup
);
279 // Using "GDB" as default here is for backwards compatibility:
280 // The config file exists but doesn't have an entry,
281 // so it must have been created by an old version of KDbg
282 // that had only the GDB driver.
283 lang
= c
.readEntry(KDebugger::DriverNameEntry
, "GDB").latin1();
285 TRACE(QString().sprintf("...bad, trying config driver %s...",
287 driver
= driverFromLang(lang
);
293 QCString name
= driverNameFromFile(executable
);
295 TRACE(QString().sprintf("...no luck, trying %s derived"
296 " from file contents", name
.data()));
297 driver
= driverFromLang(name
);
302 QString msg
= i18n("Don't know how to debug language `%1'");
303 KMessageBox::sorry(parent
, msg
.arg(lang
));
307 driver
->setLogFileName(m_transcriptFile
);
309 bool success
= m_debugger
->debugProgram(executable
, driver
);
315 QString msg
= i18n("Could not start the debugger process.\n"
316 "Please shut down KDbg and resolve the problem.");
317 KMessageBox::sorry(parent
, msg
);
323 // derive driver from language
324 DebuggerDriver
* DebuggerMainWndBase::driverFromLang(QCString lang
)
326 // lang is needed in all lowercase
329 // The following table relates languages and debugger drivers
330 static const struct L
{
331 const char* shortest
; // abbreviated to this is still unique
332 const char* full
; // full name of language
336 { "f", "fortran", 1 },
337 { "p", "python", 3 },
339 // the following are actually driver names
341 { "xsldbg", "xsldbg", 2 },
343 const int N
= sizeof(langs
)/sizeof(langs
[0]);
345 // lookup the language name
347 for (int i
= 0; i
< N
; i
++)
349 const L
& l
= langs
[i
];
351 // shortest must match
352 if (strncmp(l
.shortest
, lang
, strlen(l
.shortest
)) != 0)
355 // lang must not be longer than the full name, and it must match
356 if (lang
.length() <= strlen(l
.full
) &&
357 strncmp(l
.full
, lang
, lang
.length()) == 0)
363 DebuggerDriver
* driver
= 0;
367 GdbDriver
* gdb
= new GdbDriver
;
368 gdb
->setDefaultInvocation(m_debuggerCmdStr
);
373 driver
= new XsldbgDriver
;
383 * Try to guess the language to use from the contents of the file.
385 QCString
DebuggerMainWndBase::driverNameFromFile(const QString
& exe
)
387 /* Inprecise but simple test to see if file is in XSLT language */
388 if (exe
.right(4).lower() == ".xsl")
394 // helper that gets a file name (it only differs in the caption of the dialog)
395 QString
DebuggerMainWndBase::myGetFileName(QString caption
,
396 QString dir
, QString filter
,
400 KFileDialog
dlg(dir
, filter
, parent
, "filedialog", true);
402 dlg
.setCaption(caption
);
404 if (dlg
.exec() == QDialog::Accepted
)
405 filename
= dlg
.selectedFile();
410 void DebuggerMainWndBase::newStatusMsg(KStatusBar
* statusbar
)
412 QString msg
= m_debugger
->statusMessage();
413 statusbar
->changeItem(msg
, ID_STATUS_MSG
);
416 void DebuggerMainWndBase::doGlobalOptions(QWidget
* parent
)
418 QTabDialog
dlg(parent
, "global_options", true);
419 QString title
= kapp
->caption();
420 title
+= i18n(": Global options");
421 dlg
.setCaption(title
);
422 dlg
.setCancelButton(i18n("Cancel"));
423 dlg
.setOKButton(i18n("OK"));
425 PrefDebugger
prefDebugger(&dlg
);
426 prefDebugger
.setDebuggerCmd(m_debuggerCmdStr
.isEmpty() ?
427 GdbDriver::defaultGdb() : m_debuggerCmdStr
);
428 prefDebugger
.setTerminal(m_outputTermCmdStr
);
430 PrefMisc
prefMisc(&dlg
);
431 prefMisc
.setPopIntoForeground(m_popForeground
);
432 prefMisc
.setBackTimeout(m_backTimeout
);
433 prefMisc
.setTabWidth(m_tabWidth
);
434 prefMisc
.setSourceFilter(m_sourceFilter
);
435 prefMisc
.setHeaderFilter(m_headerFilter
);
437 dlg
.addTab(&prefDebugger
, i18n("&Debugger"));
438 dlg
.addTab(&prefMisc
, i18n("&Miscellaneous"));
439 if (dlg
.exec() == QDialog::Accepted
)
441 setDebuggerCmdStr(prefDebugger
.debuggerCmd());
442 setTerminalCmd(prefDebugger
.terminal());
443 m_popForeground
= prefMisc
.popIntoForeground();
444 m_backTimeout
= prefMisc
.backTimeout();
445 m_tabWidth
= prefMisc
.tabWidth();
446 m_sourceFilter
= prefMisc
.sourceFilter();
447 if (m_sourceFilter
.isEmpty())
448 m_sourceFilter
= defaultSourceFilter
;
449 m_headerFilter
= prefMisc
.headerFilter();
450 if (m_headerFilter
.isEmpty())
451 m_headerFilter
= defaultHeaderFilter
;
455 const char fifoNameBase
[] = "/tmp/kdbgttywin%05d";
458 * We use the scope operator :: in this function, so that we don't
459 * accidentally use the wrong close() function (I've been bitten ;-),
460 * outch!) (We use it for all the libc functions, to be consistent...)
462 QString
DebuggerMainWndBase::createOutputWindow()
464 // create a name for a fifo
466 fifoName
.sprintf(fifoNameBase
, ::getpid());
468 // create a fifo that will pass in the tty name
469 ::unlink(fifoName
); /* remove remnants */
471 if (::mkfifo(fifoName
, S_IRUSR
|S_IWUSR
) < 0) {
473 TRACE("mkfifo " + fifoName
+ " failed");
477 if (::mknod(fifoName
, S_IFIFO
| S_IRUSR
|S_IWUSR
, 0) < 0) {
479 TRACE("mknod " + fifoName
+ " failed");
484 m_outputTermProc
= new KProcess
;
488 * Spawn an xterm that in turn runs a shell script that passes us
489 * back the terminal name and then only sits and waits.
491 static const char shellScriptFmt
[] =
493 "trap \"\" INT QUIT TSTP;" /* ignore various signals */
494 "exec<&-;exec>&-;" /* close stdin and stdout */
495 "while :;do sleep 3600;done";
496 // let config file override this script
497 const char* fmt
= shellScriptFmt
;
498 if (m_outputTermKeepScript
.length() != 0) {
499 fmt
= m_outputTermKeepScript
.data();
503 shellScript
.sprintf(fmt
, fifoName
.data());
504 TRACE("output window script is " + shellScript
);
506 QString title
= kapp
->caption();
507 title
+= i18n(": Program output");
509 // parse the command line specified in the preferences
510 QStringList cmdParts
= QStringList::split(' ', m_outputTermCmdStr
);
513 * Build the argv array. Thereby substitute special sequences:
520 { "%C", shellScript
}
523 for (QStringList::iterator i
= cmdParts
.begin(); i
!= cmdParts
.end(); ++i
)
526 for (int j
= sizeof(substitute
)/sizeof(substitute
[0])-1; j
>= 0; j
--) {
527 int pos
= str
.find(substitute
[j
].seq
);
529 str
.replace(pos
, 2, substitute
[j
].replace
);
530 break; /* substitute only one sequence */
533 *m_outputTermProc
<< str
;
538 if (m_outputTermProc
->start())
540 // read the ttyname from the fifo
541 int f
= ::open(fifoName
, O_RDONLY
);
549 int n
= ::read(f
, ttyname
, sizeof(ttyname
)-sizeof(char)); /* leave space for '\0' */
561 QString tty
= QString(ttyname
).stripWhiteSpace();
567 // error, could not start xterm
568 TRACE("fork failed for fifo " + fifoName
);
570 shutdownTermWindow();
575 void DebuggerMainWndBase::shutdownTermWindow()
577 delete m_outputTermProc
;
578 m_outputTermProc
= 0;
581 void DebuggerMainWndBase::setTerminalCmd(const QString
& cmd
)
583 m_outputTermCmdStr
= cmd
;
584 // revert to default if empty
585 if (m_outputTermCmdStr
.isEmpty()) {
586 m_outputTermCmdStr
= defaultTermCmdStr
;
590 void DebuggerMainWndBase::slotDebuggerStarting()
592 if (m_debugger
== 0) /* paranoia check */
595 if (m_ttyLevel
== m_debugger
->ttyLevel())
600 // shut down terminal emulations we will not need
601 switch (m_ttyLevel
) {
602 case KDebugger::ttySimpleOutputOnly
:
603 ttyWindow()->deactivate();
605 case KDebugger::ttyFull
:
606 if (m_outputTermProc
!= 0) {
607 m_outputTermProc
->kill();
608 // will be deleted in slot
614 m_ttyLevel
= m_debugger
->ttyLevel();
617 switch (m_ttyLevel
) {
618 case KDebugger::ttySimpleOutputOnly
:
619 ttyName
= ttyWindow()->activate();
621 case KDebugger::ttyFull
:
622 if (m_outputTermProc
== 0) {
623 // create an output window
624 ttyName
= createOutputWindow();
625 TRACE(ttyName
.isEmpty() ?
626 "createOuputWindow failed" : "successfully created output window");
632 m_debugger
->setTerminal(ttyName
);
636 void DebuggerMainWndBase::setDebuggerCmdStr(const QString
& cmd
)
638 m_debuggerCmdStr
= cmd
;
639 // make empty if it is the default
640 if (m_debuggerCmdStr
== GdbDriver::defaultGdb()) {
641 m_debuggerCmdStr
= QString();
646 #include "mainwndbase.moc"