3 // Copyright by Johannes Sixt
4 // This file is under GPL, the GNU General Public Licence
7 #include <klocale.h> /* i18n */
9 #include <kmessagebox.h>
10 #include <kiconloader.h>
11 #include <kstatusbar.h>
13 #include <kfiledialog.h>
15 #include <qtabdialog.h>
17 #include "mainwndbase.h"
19 #include "gdbdriver.h"
20 #include "xsldbgdriver.h"
21 #include "prefdebugger.h"
25 #include "commandids.h"
31 #ifdef HAVE_SYS_STAT_H
32 #include <sys/stat.h> /* mknod(2) */
35 #include <fcntl.h> /* open(2) */
38 #include <unistd.h> /* getpid, unlink etc. */
41 #define MAX_RECENT_FILES 4
43 WatchWindow::WatchWindow(QWidget
* parent
, const char* name
, WFlags f
) :
44 QWidget(parent
, name
, f
),
45 m_watchEdit(this, "watch_edit"),
46 m_watchAdd(i18n(" Add "), this, "watch_add"),
47 m_watchDelete(i18n(" Del "), this, "watch_delete"),
48 m_watchVariables(this, "watch_variables"),
53 m_watchAdd
.setMinimumSize(m_watchAdd
.sizeHint());
54 m_watchDelete
.setMinimumSize(m_watchDelete
.sizeHint());
55 m_watchV
.addLayout(&m_watchH
, 0);
56 m_watchV
.addWidget(&m_watchVariables
, 10);
57 m_watchH
.addWidget(&m_watchEdit
, 10);
58 m_watchH
.addWidget(&m_watchAdd
, 0);
59 m_watchH
.addWidget(&m_watchDelete
, 0);
61 connect(&m_watchEdit
, SIGNAL(returnPressed()), SIGNAL(addWatch()));
62 connect(&m_watchAdd
, SIGNAL(clicked()), SIGNAL(addWatch()));
63 connect(&m_watchDelete
, SIGNAL(clicked()), SIGNAL(deleteWatch()));
64 connect(&m_watchVariables
, SIGNAL(highlighted(int)), SLOT(slotWatchHighlighted(int)));
66 m_watchVariables
.setMoveCurrentToSibling(true);
67 m_watchVariables
.installEventFilter(this);
70 WatchWindow::~WatchWindow()
74 bool WatchWindow::eventFilter(QObject
*, QEvent
* ev
)
76 if (ev
->type() == QEvent::KeyPress
)
78 QKeyEvent
* kev
= static_cast<QKeyEvent
*>(ev
);
79 if (kev
->key() == Key_Delete
) {
88 // place the text of the hightlighted watch expr in the edit field
89 void WatchWindow::slotWatchHighlighted(int idx
)
91 QString text
= m_watchVariables
.exprStringAt(idx
);
92 m_watchEdit
.setText(text
);
96 static void splitCmdStr(const QString
& cmd
, ValArray
<QString
>& parts
)
98 QString str
= cmd
.simplifyWhiteSpace();
101 while ((end
= str
.find(' ', start
)) >= 0) {
102 parts
.append(str
.mid(start
, end
-start
));
105 parts
.append(str
.mid(start
, str
.length()-start
));
109 const char defaultTermCmdStr
[] = "xterm -name kdbgio -title %T -e sh -c %C";
110 const char defaultSourceFilter
[] = "*.c *.cc *.cpp *.c++ *.C *.CC";
111 const char defaultHeaderFilter
[] = "*.h *.hh *.hpp *.h++";
114 DebuggerMainWndBase::DebuggerMainWndBase() :
115 m_animationCounter(0),
116 m_outputTermCmdStr(defaultTermCmdStr
),
118 m_ttyLevel(-1), /* no tty yet */
119 #ifdef GDB_TRANSCRIPT
120 m_transcriptFile(GDB_TRANSCRIPT
),
122 m_popForeground(false),
125 m_sourceFilter(defaultSourceFilter
),
126 m_headerFilter(defaultHeaderFilter
),
129 m_statusActive
= i18n("active");
130 m_recentExecList
.setAutoDelete(true);
133 DebuggerMainWndBase::~DebuggerMainWndBase()
137 // if the output window is open, close it
138 if (m_outputTermProc
!= 0) {
139 m_outputTermProc
->disconnect(); /* ignore signals */
140 m_outputTermProc
->kill();
141 shutdownTermWindow();
145 void DebuggerMainWndBase::setupDebugger(QWidget
* parent
,
150 m_debugger
= new KDebugger(parent
, localVars
, watchVars
, backtrace
);
152 QObject::connect(m_debugger
, SIGNAL(updateStatusMessage()),
153 parent
, SLOT(slotNewStatusMsg()));
154 QObject::connect(m_debugger
, SIGNAL(updateUI()),
155 parent
, SLOT(updateUI()));
157 QObject::connect(m_debugger
, SIGNAL(lineItemsChanged()),
158 parent
, SLOT(updateLineItems()));
160 QObject::connect(m_debugger
, SIGNAL(animationTimeout()),
161 parent
, SLOT(slotAnimationTimeout()));
162 QObject::connect(m_debugger
, SIGNAL(debuggerStarting()),
163 parent
, SLOT(slotDebuggerStarting()));
167 void DebuggerMainWndBase::setCoreFile(const QString
& corefile
)
169 assert(m_debugger
!= 0);
170 m_debugger
->useCoreFile(corefile
, true);
173 void DebuggerMainWndBase::setRemoteDevice(const QString
& remoteDevice
)
175 if (m_debugger
!= 0) {
176 m_debugger
->setRemoteDevice(remoteDevice
);
180 void DebuggerMainWndBase::setTranscript(const char* name
)
182 m_transcriptFile
= name
;
183 if (m_debugger
!= 0 && m_debugger
->driver() != 0)
184 m_debugger
->driver()->setLogFileName(m_transcriptFile
);
187 const char OutputWindowGroup
[] = "OutputWindow";
188 const char TermCmdStr
[] = "TermCmdStr";
189 const char KeepScript
[] = "KeepScript";
190 const char DebuggerGroup
[] = "Debugger";
191 const char DebuggerCmdStr
[] = "DebuggerCmdStr";
192 const char PreferencesGroup
[] = "Preferences";
193 const char PopForeground
[] = "PopForeground";
194 const char BackTimeout
[] = "BackTimeout";
195 const char TabWidth
[] = "TabWidth";
196 const char FilesGroup
[] = "Files";
197 const char RecentExecutables
[] = "RecentExecutables";
198 const char SourceFileFilter
[] = "SourceFileFilter";
199 const char HeaderFileFilter
[] = "HeaderFileFilter";
200 const char GeneralGroup
[] = "General";
202 void DebuggerMainWndBase::saveSettings(KConfig
* config
)
204 if (m_debugger
!= 0) {
205 m_debugger
->saveSettings(config
);
208 KConfigGroupSaver
g(config
, OutputWindowGroup
);
209 config
->writeEntry(TermCmdStr
, m_outputTermCmdStr
);
211 config
->setGroup(DebuggerGroup
);
212 config
->writeEntry(DebuggerCmdStr
, m_debuggerCmdStr
);
214 config
->setGroup(PreferencesGroup
);
215 config
->writeEntry(PopForeground
, m_popForeground
);
216 config
->writeEntry(BackTimeout
, m_backTimeout
);
217 config
->writeEntry(TabWidth
, m_tabWidth
);
218 config
->writeEntry(SourceFileFilter
, m_sourceFilter
);
219 config
->writeEntry(HeaderFileFilter
, m_headerFilter
);
221 config
->setGroup(FilesGroup
);
222 config
->writeEntry(RecentExecutables
, m_recentExecList
, ',');
225 void DebuggerMainWndBase::restoreSettings(KConfig
* config
)
227 if (m_debugger
!= 0) {
228 m_debugger
->restoreSettings(config
);
231 KConfigGroupSaver
g(config
, OutputWindowGroup
);
233 * For debugging and emergency purposes, let the config file override
234 * the shell script that is used to keep the output window open. This
235 * string must have EXACTLY 1 %s sequence in it.
237 setTerminalCmd(config
->readEntry(TermCmdStr
, defaultTermCmdStr
));
238 m_outputTermKeepScript
= config
->readEntry(KeepScript
);
240 config
->setGroup(DebuggerGroup
);
241 setDebuggerCmdStr(config
->readEntry(DebuggerCmdStr
));
243 config
->setGroup(PreferencesGroup
);
244 m_popForeground
= config
->readBoolEntry(PopForeground
, false);
245 m_backTimeout
= config
->readNumEntry(BackTimeout
, 1000);
246 m_tabWidth
= config
->readNumEntry(TabWidth
, 0);
247 m_sourceFilter
= config
->readEntry(SourceFileFilter
, m_sourceFilter
);
248 m_headerFilter
= config
->readEntry(HeaderFileFilter
, m_headerFilter
);
250 config
->setGroup(FilesGroup
);
251 config
->readListEntry(RecentExecutables
, m_recentExecList
,',');
254 bool DebuggerMainWndBase::debugProgram(const QString
& executable
,
255 QCString lang
, QWidget
* parent
)
257 assert(m_debugger
!= 0);
259 TRACE(QString().sprintf("trying language '%s'...", lang
.data()));
260 DebuggerDriver
* driver
= driverFromLang(lang
);
264 // see if there is a language in the per-program config file
265 QString configName
= m_debugger
->getConfigForExe(executable
);
266 if (QFile::exists(configName
))
268 KSimpleConfig
c(configName
, true); // read-only
269 c
.setGroup(GeneralGroup
);
271 // Using "GDB" as default here is for backwards compatibility:
272 // The config file exists but doesn't have an entry,
273 // so it must have been created by an old version of KDbg
274 // that had only the GDB driver.
275 lang
= c
.readEntry(KDebugger::DriverNameEntry
, "GDB").latin1();
277 TRACE(QString().sprintf("...bad, trying config driver %s...",
279 driver
= driverFromLang(lang
);
285 QCString name
= driverNameFromFile(executable
);
287 TRACE(QString().sprintf("...no luck, trying %s derived"
288 " from file contents", name
.data()));
289 driver
= driverFromLang(name
);
294 QString msg
= i18n("Don't know how to debug language `%1'");
295 KMessageBox::sorry(parent
, msg
.arg(lang
));
299 driver
->setLogFileName(m_transcriptFile
);
301 bool success
= m_debugger
->debugProgram(executable
, driver
);
307 QString msg
= i18n("Could not start the debugger process.\n"
308 "Please shut down KDbg and resolve the problem.");
309 KMessageBox::sorry(parent
, msg
);
315 // derive driver from language
316 DebuggerDriver
* DebuggerMainWndBase::driverFromLang(QCString lang
)
318 // lang is needed in all lowercase
321 // The following table relates languages and debugger drivers
322 static const struct L
{
323 const char* shortest
; // abbreviated to this is still unique
324 const char* full
; // full name of language
328 { "f", "fortran", 1 },
329 { "p", "python", 3 },
331 // the following are actually driver names
333 { "xsldbg", "xsldbg", 2 },
335 const int N
= sizeof(langs
)/sizeof(langs
[0]);
337 // lookup the language name
339 for (int i
= 0; i
< N
; i
++)
341 const L
& l
= langs
[i
];
343 // shortest must match
344 if (strncmp(l
.shortest
, lang
, strlen(l
.shortest
)) != 0)
347 // lang must not be longer than the full name, and it must match
348 if (lang
.length() <= strlen(l
.full
) &&
349 strncmp(l
.full
, lang
, lang
.length()) == 0)
355 DebuggerDriver
* driver
= 0;
359 GdbDriver
* gdb
= new GdbDriver
;
360 gdb
->setDefaultInvocation(m_debuggerCmdStr
);
365 driver
= new XsldbgDriver
;
374 QCString
DebuggerMainWndBase::driverNameFromFile(const QString
& exe
)
379 // helper that gets a file name (it only differs in the caption of the dialog)
380 QString
DebuggerMainWndBase::myGetFileName(QString caption
,
381 QString dir
, QString filter
,
385 KFileDialog
dlg(dir
, filter
, parent
, "filedialog", true);
387 dlg
.setCaption(caption
);
389 if (dlg
.exec() == QDialog::Accepted
)
390 filename
= dlg
.selectedFile();
395 void DebuggerMainWndBase::updateUIItem(UpdateUI
* item
)
398 case ID_FILE_EXECUTABLE
:
399 item
->enable(m_debugger
->isIdle());
401 case ID_FILE_PROG_SETTINGS
:
402 item
->enable(m_debugger
->haveExecutable());
404 case ID_FILE_COREFILE
:
405 item
->enable(m_debugger
->canUseCoreFile());
407 case ID_PROGRAM_STEP
:
408 case ID_PROGRAM_STEPI
:
409 case ID_PROGRAM_NEXT
:
410 case ID_PROGRAM_NEXTI
:
411 case ID_PROGRAM_FINISH
:
412 case ID_PROGRAM_UNTIL
:
413 case ID_PROGRAM_RUN_AGAIN
:
414 item
->enable(m_debugger
->canSingleStep());
416 case ID_PROGRAM_ATTACH
:
418 item
->enable(m_debugger
->isReady());
420 case ID_PROGRAM_KILL
:
421 item
->enable(m_debugger
->haveExecutable() && m_debugger
->isProgramActive());
423 case ID_PROGRAM_BREAK
:
424 item
->enable(m_debugger
->isProgramRunning());
426 case ID_PROGRAM_ARGS
:
427 item
->enable(m_debugger
->haveExecutable());
432 dbgStatusBar()->changeItem(m_debugger
->isProgramActive() ?
433 static_cast<const char*>(m_statusActive
) : "",
437 void DebuggerMainWndBase::updateLineItems()
441 void DebuggerMainWndBase::initAnimation()
443 QPixmap pixmap
= BarIcon("kde1");
446 KToolBar
* toolbar
= dbgToolBar();
447 toolbar
->insertButton(pixmap
, ID_STATUS_BUSY
);
448 toolbar
->alignItemRight(ID_STATUS_BUSY
, true);
450 // Load animated logo
451 m_animation
.setAutoDelete(true);
453 for (int i
= 1; i
<= numPix
; i
++) {
454 n
.sprintf("kde%d", i
);
455 QPixmap
* p
= new QPixmap(BarIcon(n
));
457 m_animation
.append(p
);
462 // safeguard: if we did not find a single icon, add a dummy
463 if (m_animation
.count() == 0) {
464 QPixmap
* pix
= new QPixmap(2,2);
466 p
.fillRect(0,0,2,2,QBrush(Qt::white
));
467 m_animation
.append(pix
);
471 void DebuggerMainWndBase::slotAnimationTimeout()
473 assert(m_animation
.count() > 0); /* must have been initialized */
474 m_animationCounter
++;
475 if (m_animationCounter
== m_animation
.count())
476 m_animationCounter
= 0;
477 dbgToolBar()->setButtonPixmap(ID_STATUS_BUSY
,
478 *m_animation
.at(m_animationCounter
));
481 void DebuggerMainWndBase::slotNewStatusMsg()
483 QString msg
= m_debugger
->statusMessage();
484 dbgStatusBar()->changeItem(msg
, ID_STATUS_MSG
);
487 void DebuggerMainWndBase::doGlobalOptions(QWidget
* parent
)
489 QTabDialog
dlg(parent
, "global_options", true);
490 QString title
= kapp
->caption();
491 title
+= i18n(": Global options");
492 dlg
.setCaption(title
);
493 dlg
.setCancelButton(i18n("Cancel"));
494 dlg
.setOKButton(i18n("OK"));
496 PrefDebugger
prefDebugger(&dlg
);
497 prefDebugger
.setDebuggerCmd(m_debuggerCmdStr
.isEmpty() ?
498 GdbDriver::defaultGdb() : m_debuggerCmdStr
);
499 prefDebugger
.setTerminal(m_outputTermCmdStr
);
501 PrefMisc
prefMisc(&dlg
);
502 prefMisc
.setPopIntoForeground(m_popForeground
);
503 prefMisc
.setBackTimeout(m_backTimeout
);
504 prefMisc
.setTabWidth(m_tabWidth
);
505 prefMisc
.setSourceFilter(m_sourceFilter
);
506 prefMisc
.setHeaderFilter(m_headerFilter
);
508 dlg
.addTab(&prefDebugger
, i18n("&Debugger"));
509 dlg
.addTab(&prefMisc
, i18n("&Miscellaneous"));
510 if (dlg
.exec() == QDialog::Accepted
)
512 setDebuggerCmdStr(prefDebugger
.debuggerCmd());
513 setTerminalCmd(prefDebugger
.terminal());
514 m_popForeground
= prefMisc
.popIntoForeground();
515 m_backTimeout
= prefMisc
.backTimeout();
516 m_tabWidth
= prefMisc
.tabWidth();
517 m_sourceFilter
= prefMisc
.sourceFilter();
518 if (m_sourceFilter
.isEmpty())
519 m_sourceFilter
= defaultSourceFilter
;
520 m_headerFilter
= prefMisc
.headerFilter();
521 if (m_headerFilter
.isEmpty())
522 m_headerFilter
= defaultHeaderFilter
;
526 const char fifoNameBase
[] = "/tmp/kdbgttywin%05d";
529 * We use the scope operator :: in this function, so that we don't
530 * accidentally use the wrong close() function (I've been bitten ;-),
531 * outch!) (We use it for all the libc functions, to be consistent...)
533 QString
DebuggerMainWndBase::createOutputWindow()
535 // create a name for a fifo
537 fifoName
.sprintf(fifoNameBase
, ::getpid());
539 // create a fifo that will pass in the tty name
540 ::unlink(fifoName
); /* remove remnants */
542 if (::mkfifo(fifoName
, S_IRUSR
|S_IWUSR
) < 0) {
544 TRACE("mkfifo " + fifoName
+ " failed");
548 if (::mknod(fifoName
, S_IFIFO
| S_IRUSR
|S_IWUSR
, 0) < 0) {
550 TRACE("mknod " + fifoName
+ " failed");
555 m_outputTermProc
= new KProcess
;
559 * Spawn an xterm that in turn runs a shell script that passes us
560 * back the terminal name and then only sits and waits.
562 static const char shellScriptFmt
[] =
564 "trap \"\" INT QUIT TSTP;" /* ignore various signals */
565 "exec<&-;exec>&-;" /* close stdin and stdout */
566 "while :;do sleep 3600;done";
567 // let config file override this script
568 const char* fmt
= shellScriptFmt
;
569 if (m_outputTermKeepScript
.length() != 0) {
570 fmt
= m_outputTermKeepScript
.data();
574 shellScript
.sprintf(fmt
, fifoName
.data());
575 TRACE("output window script is " + shellScript
);
577 QString title
= kapp
->caption();
578 title
+= i18n(": Program output");
580 // parse the command line specified in the preferences
581 ValArray
<QString
> cmdParts
;
582 splitCmdStr(m_outputTermCmdStr
, cmdParts
);
585 * Build the argv array. Thereby substitute special sequences:
592 { "%C", shellScript
}
595 for (int i
= 0; i
< cmdParts
.size(); i
++) {
596 QString
& str
= cmdParts
[i
];
597 for (int j
= sizeof(substitute
)/sizeof(substitute
[0])-1; j
>= 0; j
--) {
598 int pos
= str
.find(substitute
[j
].seq
);
600 str
.replace(pos
, 2, substitute
[j
].replace
);
601 break; /* substitute only one sequence */
604 *m_outputTermProc
<< str
;
609 if (m_outputTermProc
->start())
611 // read the ttyname from the fifo
612 int f
= ::open(fifoName
, O_RDONLY
);
620 int n
= ::read(f
, ttyname
, sizeof(ttyname
)-sizeof(char)); /* leave space for '\0' */
632 QString tty
= QString(ttyname
).stripWhiteSpace();
638 // error, could not start xterm
639 TRACE("fork failed for fifo " + fifoName
);
641 shutdownTermWindow();
646 void DebuggerMainWndBase::shutdownTermWindow()
648 delete m_outputTermProc
;
649 m_outputTermProc
= 0;
652 void DebuggerMainWndBase::setTerminalCmd(const QString
& cmd
)
654 m_outputTermCmdStr
= cmd
;
655 // revert to default if empty
656 if (m_outputTermCmdStr
.isEmpty()) {
657 m_outputTermCmdStr
= defaultTermCmdStr
;
661 void DebuggerMainWndBase::slotDebuggerStarting()
663 if (m_debugger
== 0) /* paranoia check */
666 if (m_ttyLevel
== m_debugger
->ttyLevel())
671 // shut down terminal emulations we will not need
672 switch (m_ttyLevel
) {
673 case KDebugger::ttySimpleOutputOnly
:
674 ttyWindow()->deactivate();
676 case KDebugger::ttyFull
:
677 if (m_outputTermProc
!= 0) {
678 m_outputTermProc
->kill();
679 // will be deleted in slot
685 m_ttyLevel
= m_debugger
->ttyLevel();
688 switch (m_ttyLevel
) {
689 case KDebugger::ttySimpleOutputOnly
:
690 ttyName
= ttyWindow()->activate();
692 case KDebugger::ttyFull
:
693 if (m_outputTermProc
== 0) {
694 // create an output window
695 ttyName
= createOutputWindow();
696 TRACE(ttyName
.isEmpty() ?
697 "createOuputWindow failed" : "successfully created output window");
703 m_debugger
->setTerminal(ttyName
);
707 void DebuggerMainWndBase::setDebuggerCmdStr(const QString
& cmd
)
709 m_debuggerCmdStr
= cmd
;
710 // make empty if it is the default
711 if (m_debuggerCmdStr
== GdbDriver::defaultGdb()) {
712 m_debuggerCmdStr
= QString();
716 void DebuggerMainWndBase::addRecentExec(const QString
& executable
)
718 int pos
= m_recentExecList
.find(executable
);
722 m_recentExecList
.remove(pos
);
726 m_recentExecList
.insert(0, executable
);
727 } // else pos == 0, which means we dont need to change the list
730 while (m_recentExecList
.count() > MAX_RECENT_FILES
) {
731 m_recentExecList
.remove(MAX_RECENT_FILES
);
735 void DebuggerMainWndBase::removeRecentExec(const QString
& executable
)
737 int pos
= m_recentExecList
.find(executable
);
739 m_recentExecList
.remove(pos
);
744 #include "mainwndbase.moc"