3 // Copyright by Johannes Sixt
4 // This file is under GPL, the GNU General Public Licence
8 #include <klocale.h> /* i18n */
10 #include <kmessagebox.h>
15 #include <kiconloader.h>
16 #include <kstatusbar.h>
18 #include <kfiledialog.h>
20 #include <qtabdialog.h>
21 #include <qfileinfo.h>
22 #include "mainwndbase.h"
24 #include "gdbdriver.h"
25 #include "prefdebugger.h"
29 #include "commandids.h"
35 #ifdef HAVE_SYS_STAT_H
36 #include <sys/stat.h> /* mknod(2) */
39 #include <fcntl.h> /* open(2) */
42 #include <unistd.h> /* getpid, unlink etc. */
45 #define MAX_RECENT_FILES 4
47 WatchWindow::WatchWindow(QWidget
* parent
, const char* name
, WFlags f
) :
48 QWidget(parent
, name
, f
),
49 m_watchEdit(this, "watch_edit"),
50 m_watchAdd(i18n(" Add "), this, "watch_add"),
51 m_watchDelete(i18n(" Del "), this, "watch_delete"),
52 m_watchVariables(this, "watch_variables"),
57 m_watchAdd
.setMinimumSize(m_watchAdd
.sizeHint());
58 m_watchDelete
.setMinimumSize(m_watchDelete
.sizeHint());
59 m_watchV
.addLayout(&m_watchH
, 0);
60 m_watchV
.addWidget(&m_watchVariables
, 10);
61 m_watchH
.addWidget(&m_watchEdit
, 10);
62 m_watchH
.addWidget(&m_watchAdd
, 0);
63 m_watchH
.addWidget(&m_watchDelete
, 0);
65 connect(&m_watchEdit
, SIGNAL(returnPressed()), SIGNAL(addWatch()));
66 connect(&m_watchAdd
, SIGNAL(clicked()), SIGNAL(addWatch()));
67 connect(&m_watchDelete
, SIGNAL(clicked()), SIGNAL(deleteWatch()));
68 connect(&m_watchVariables
, SIGNAL(highlighted(int)), SLOT(slotWatchHighlighted(int)));
70 m_watchVariables
.setMoveCurrentToSibling(true);
71 m_watchVariables
.installEventFilter(this);
74 WatchWindow::~WatchWindow()
78 bool WatchWindow::eventFilter(QObject
*, QEvent
* ev
)
88 QKeyEvent
* kev
= static_cast<QKeyEvent
*>(ev
);
89 if (kev
->key() == Key_Delete
) {
98 // place the text of the hightlighted watch expr in the edit field
99 void WatchWindow::slotWatchHighlighted(int idx
)
101 QString text
= m_watchVariables
.exprStringAt(idx
);
102 m_watchEdit
.setText(text
);
106 static void splitCmdStr(const QString
& cmd
, ValArray
<QString
>& parts
)
108 QString str
= cmd
.simplifyWhiteSpace();
111 while ((end
= str
.find(' ', start
)) >= 0) {
112 parts
.append(str
.mid(start
, end
-start
));
115 parts
.append(str
.mid(start
, str
.length()-start
));
119 const char defaultTermCmdStr
[] = "xterm -name kdbgio -title %T -e sh -c %C";
120 const char defaultSourceFilter
[] = "*.c *.cc *.cpp *.c++ *.C *.CC";
121 const char defaultHeaderFilter
[] = "*.h *.hh *.hpp *.h++";
124 DebuggerMainWndBase::DebuggerMainWndBase() :
125 m_animationCounter(0),
126 m_outputTermCmdStr(defaultTermCmdStr
),
128 m_ttyLevel(-1), /* no tty yet */
129 #ifdef GDB_TRANSCRIPT
130 m_transcriptFile(GDB_TRANSCRIPT
),
132 m_popForeground(false),
135 m_sourceFilter(defaultSourceFilter
),
136 m_headerFilter(defaultHeaderFilter
),
139 m_statusActive
= i18n("active");
140 m_recentExecList
.setAutoDelete(true);
143 DebuggerMainWndBase::~DebuggerMainWndBase()
147 // if the output window is open, close it
148 if (m_outputTermProc
!= 0) {
149 m_outputTermProc
->disconnect(); /* ignore signals */
150 m_outputTermProc
->kill();
151 shutdownTermWindow();
155 void DebuggerMainWndBase::setupDebugger(QWidget
* parent
,
160 m_debugger
= new KDebugger(parent
, localVars
, watchVars
, backtrace
);
162 QObject::connect(m_debugger
, SIGNAL(updateStatusMessage()),
163 parent
, SLOT(slotNewStatusMsg()));
164 QObject::connect(m_debugger
, SIGNAL(updateUI()),
165 parent
, SLOT(updateUI()));
167 QObject::connect(m_debugger
, SIGNAL(lineItemsChanged()),
168 parent
, SLOT(updateLineItems()));
170 QObject::connect(m_debugger
, SIGNAL(animationTimeout()),
171 parent
, SLOT(slotAnimationTimeout()));
172 QObject::connect(m_debugger
, SIGNAL(debuggerStarting()),
173 parent
, SLOT(slotDebuggerStarting()));
175 m_debugger
->setDebuggerCmd(m_debuggerCmdStr
);
179 void DebuggerMainWndBase::setCoreFile(const QString
& corefile
)
181 assert(m_debugger
!= 0);
182 m_debugger
->useCoreFile(corefile
, true);
185 void DebuggerMainWndBase::setRemoteDevice(const QString
& remoteDevice
)
187 if (m_debugger
!= 0) {
188 m_debugger
->setRemoteDevice(remoteDevice
);
192 void DebuggerMainWndBase::setTranscript(const char* name
)
194 m_transcriptFile
= name
;
195 if (m_debugger
!= 0 && m_debugger
->driver() != 0)
196 m_debugger
->driver()->setLogFileName(m_transcriptFile
);
199 void DebuggerMainWndBase::setLanguage(const QCString
& lang
)
201 m_language
= lang
.lower();
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 RecentExecutables
[] = "RecentExecutables";
215 const char SourceFileFilter
[] = "SourceFileFilter";
216 const char HeaderFileFilter
[] = "HeaderFileFilter";
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
);
237 config
->setGroup(FilesGroup
);
238 config
->writeEntry(RecentExecutables
, m_recentExecList
, ',');
241 void DebuggerMainWndBase::restoreSettings(KConfig
* config
)
243 if (m_debugger
!= 0) {
244 m_debugger
->restoreSettings(config
);
247 KConfigGroupSaver
g(config
, OutputWindowGroup
);
249 * For debugging and emergency purposes, let the config file override
250 * the shell script that is used to keep the output window open. This
251 * string must have EXACTLY 1 %s sequence in it.
253 setTerminalCmd(config
->readEntry(TermCmdStr
, defaultTermCmdStr
));
254 m_outputTermKeepScript
= config
->readEntry(KeepScript
);
256 config
->setGroup(DebuggerGroup
);
257 setDebuggerCmdStr(config
->readEntry(DebuggerCmdStr
));
259 config
->setGroup(PreferencesGroup
);
260 m_popForeground
= config
->readBoolEntry(PopForeground
, false);
261 m_backTimeout
= config
->readNumEntry(BackTimeout
, 1000);
262 m_tabWidth
= config
->readNumEntry(TabWidth
, 0);
263 m_sourceFilter
= config
->readEntry(SourceFileFilter
, m_sourceFilter
);
264 m_headerFilter
= config
->readEntry(HeaderFileFilter
, m_headerFilter
);
266 config
->setGroup(FilesGroup
);
267 config
->readListEntry(RecentExecutables
, m_recentExecList
,',');
270 bool DebuggerMainWndBase::debugProgram(const QString
& executable
, QWidget
* parent
)
272 assert(m_debugger
!= 0);
274 DebuggerDriver
* driver
= driverFromLang(m_language
);
278 QString msg
= i18n("Don't know how to debug language `%1'");
279 KMessageBox::sorry(parent
, msg
.arg(m_language
));
283 driver
->setLogFileName(m_transcriptFile
);
285 bool success
= m_debugger
->debugProgram(executable
, driver
);
291 QString msg
= i18n("Could not start the debugger process.\n"
292 "Please shut down KDbg and resolve the problem.");
293 KMessageBox::sorry(parent
, msg
);
299 // derive driver from language
300 DebuggerDriver
* DebuggerMainWndBase::driverFromLang(const QCString
& lang
)
302 // lang must be in all lowercase
303 assert(lang
== lang
.lower());
305 // The following table relates languages and debugger drivers
306 static const struct L
{
307 const char* shortest
; // abbreviated to this is still unique
308 const char* full
; // full name of language
312 { "f", "fortran", 1 },
313 { "p", "python", 3 },
316 const int N
= sizeof(langs
)/sizeof(langs
[0]);
318 // lookup the language name
319 // note that it has been set to lower-case in setLanguage()
321 for (int i
= 0; i
< N
; i
++)
323 const L
& l
= langs
[i
];
325 // shortest must match
326 if (strncmp(l
.shortest
, lang
, strlen(l
.shortest
)) != 0)
329 // lang must not be longer than the full name, and it must match
330 if (lang
.length() <= strlen(l
.full
) &&
331 strncmp(l
.full
, lang
, lang
.length()) == 0)
337 DebuggerDriver
* driver
= 0;
340 driver
= new GdbDriver
;
349 // helper that gets a file name (it only differs in the caption of the dialog)
350 QString
DebuggerMainWndBase::myGetFileName(QString caption
,
351 QString dir
, QString filter
,
355 KFileDialog
dlg(dir
, filter
, parent
, "filedialog", true);
357 dlg
.setCaption(caption
);
359 if (dlg
.exec() == QDialog::Accepted
)
360 filename
= dlg
.selectedFile();
365 void DebuggerMainWndBase::updateUIItem(UpdateUI
* item
)
368 case ID_FILE_EXECUTABLE
:
369 item
->enable(m_debugger
->isIdle());
371 case ID_FILE_PROG_SETTINGS
:
372 item
->enable(m_debugger
->haveExecutable());
374 case ID_FILE_COREFILE
:
375 item
->enable(m_debugger
->canUseCoreFile());
377 case ID_PROGRAM_STEP
:
378 case ID_PROGRAM_STEPI
:
379 case ID_PROGRAM_NEXT
:
380 case ID_PROGRAM_NEXTI
:
381 case ID_PROGRAM_FINISH
:
382 case ID_PROGRAM_UNTIL
:
383 case ID_PROGRAM_RUN_AGAIN
:
384 item
->enable(m_debugger
->canSingleStep());
386 case ID_PROGRAM_ATTACH
:
388 item
->enable(m_debugger
->isReady());
390 case ID_PROGRAM_KILL
:
391 item
->enable(m_debugger
->haveExecutable() && m_debugger
->isProgramActive());
393 case ID_PROGRAM_BREAK
:
394 item
->enable(m_debugger
->isProgramRunning());
396 case ID_PROGRAM_ARGS
:
397 item
->enable(m_debugger
->haveExecutable());
402 dbgStatusBar()->changeItem(m_debugger
->isProgramActive() ?
403 static_cast<const char*>(m_statusActive
) : "",
407 void DebuggerMainWndBase::updateLineItems()
411 void DebuggerMainWndBase::initAnimation()
414 QString path
= kapp
->kde_datadir() + "/kfm/pics/";
416 pixmap
.load(path
+ "/kde1.xpm");
419 QPixmap pixmap
= BarIcon("kde1");
423 KToolBar
* toolbar
= dbgToolBar();
424 toolbar
->insertButton(pixmap
, ID_STATUS_BUSY
);
425 toolbar
->alignItemRight(ID_STATUS_BUSY
, true);
427 // Load animated logo
428 m_animation
.setAutoDelete(true);
430 for (int i
= 1; i
<= numPix
; i
++) {
432 n
.sprintf("/kde%d.xpm", i
);
433 QPixmap
* p
= new QPixmap();
436 n
.sprintf("kde%d", i
);
437 QPixmap
* p
= new QPixmap(BarIcon(n
));
440 m_animation
.append(p
);
445 // safeguard: if we did not find a single icon, add a dummy
446 if (m_animation
.count() == 0) {
447 QPixmap
* pix
= new QPixmap(2,2);
450 p
.fillRect(0,0,2,2,QBrush(white
));
452 p
.fillRect(0,0,2,2,QBrush(Qt::white
));
454 m_animation
.append(pix
);
458 void DebuggerMainWndBase::slotAnimationTimeout()
460 assert(m_animation
.count() > 0); /* must have been initialized */
461 m_animationCounter
++;
462 if (m_animationCounter
== m_animation
.count())
463 m_animationCounter
= 0;
464 dbgToolBar()->setButtonPixmap(ID_STATUS_BUSY
,
465 *m_animation
.at(m_animationCounter
));
468 void DebuggerMainWndBase::slotNewStatusMsg()
470 QString msg
= m_debugger
->statusMessage();
471 dbgStatusBar()->changeItem(msg
, ID_STATUS_MSG
);
474 void DebuggerMainWndBase::doGlobalOptions(QWidget
* parent
)
476 QTabDialog
dlg(parent
, "global_options", true);
477 QString title
= kapp
->getCaption();
478 title
+= i18n(": Global options");
479 dlg
.setCaption(title
);
480 dlg
.setCancelButton(i18n("Cancel"));
481 dlg
.setOKButton(i18n("OK"));
483 PrefDebugger
prefDebugger(&dlg
);
484 prefDebugger
.setDebuggerCmd(m_debuggerCmdStr
.isEmpty() ?
485 GdbDriver::defaultGdb() : m_debuggerCmdStr
);
486 prefDebugger
.setTerminal(m_outputTermCmdStr
);
488 PrefMisc
prefMisc(&dlg
);
489 prefMisc
.setPopIntoForeground(m_popForeground
);
490 prefMisc
.setBackTimeout(m_backTimeout
);
491 prefMisc
.setTabWidth(m_tabWidth
);
492 prefMisc
.setSourceFilter(m_sourceFilter
);
493 prefMisc
.setHeaderFilter(m_headerFilter
);
495 dlg
.addTab(&prefDebugger
, i18n("&Debugger"));
496 dlg
.addTab(&prefMisc
, i18n("&Miscellaneous"));
497 if (dlg
.exec() == QDialog::Accepted
)
499 setDebuggerCmdStr(prefDebugger
.debuggerCmd());
500 setTerminalCmd(prefDebugger
.terminal());
501 m_popForeground
= prefMisc
.popIntoForeground();
502 m_backTimeout
= prefMisc
.backTimeout();
503 m_tabWidth
= prefMisc
.tabWidth();
504 m_sourceFilter
= prefMisc
.sourceFilter();
505 if (m_sourceFilter
.isEmpty())
506 m_sourceFilter
= defaultSourceFilter
;
507 m_headerFilter
= prefMisc
.headerFilter();
508 if (m_headerFilter
.isEmpty())
509 m_headerFilter
= defaultHeaderFilter
;
513 const char fifoNameBase
[] = "/tmp/kdbgttywin%05d";
516 * We use the scope operator :: in this function, so that we don't
517 * accidentally use the wrong close() function (I've been bitten ;-),
518 * outch!) (We use it for all the libc functions, to be consistent...)
520 QString
DebuggerMainWndBase::createOutputWindow()
522 // create a name for a fifo
524 fifoName
.sprintf(fifoNameBase
, ::getpid());
526 // create a fifo that will pass in the tty name
527 ::unlink(fifoName
); /* remove remnants */
529 if (::mkfifo(fifoName
, S_IRUSR
|S_IWUSR
) < 0) {
531 TRACE("mkfifo " + fifoName
+ " failed");
535 if (::mknod(fifoName
, S_IFIFO
| S_IRUSR
|S_IWUSR
, 0) < 0) {
537 TRACE("mknod " + fifoName
+ " failed");
542 m_outputTermProc
= new KProcess
;
546 * Spawn an xterm that in turn runs a shell script that passes us
547 * back the terminal name and then only sits and waits.
549 static const char shellScriptFmt
[] =
551 "trap \"\" INT QUIT TSTP;" /* ignore various signals */
552 "exec<&-;exec>&-;" /* close stdin and stdout */
553 "while :;do sleep 3600;done";
554 // let config file override this script
555 const char* fmt
= shellScriptFmt
;
556 if (m_outputTermKeepScript
.length() != 0) {
557 fmt
= m_outputTermKeepScript
.data();
560 QString
shellScript(strlen(fmt
) + fifoName
.length());
564 shellScript
.sprintf(fmt
, fifoName
.data());
565 TRACE("output window script is " + shellScript
);
567 QString title
= kapp
->getCaption();
568 title
+= i18n(": Program output");
570 // parse the command line specified in the preferences
571 ValArray
<QString
> cmdParts
;
572 splitCmdStr(m_outputTermCmdStr
, cmdParts
);
575 * Build the argv array. Thereby substitute special sequences:
582 { "%C", shellScript
}
585 for (int i
= 0; i
< cmdParts
.size(); i
++) {
586 QString
& str
= cmdParts
[i
];
587 for (int j
= sizeof(substitute
)/sizeof(substitute
[0])-1; j
>= 0; j
--) {
588 int pos
= str
.find(substitute
[j
].seq
);
590 str
.replace(pos
, 2, substitute
[j
].replace
);
591 break; /* substitute only one sequence */
594 *m_outputTermProc
<< str
;
599 if (m_outputTermProc
->start())
601 // read the ttyname from the fifo
602 int f
= ::open(fifoName
, O_RDONLY
);
610 int n
= ::read(f
, ttyname
, sizeof(ttyname
)-sizeof(char)); /* leave space for '\0' */
622 QString tty
= QString(ttyname
).stripWhiteSpace();
628 // error, could not start xterm
629 TRACE("fork failed for fifo " + fifoName
);
631 shutdownTermWindow();
636 void DebuggerMainWndBase::shutdownTermWindow()
638 delete m_outputTermProc
;
639 m_outputTermProc
= 0;
642 void DebuggerMainWndBase::setTerminalCmd(const QString
& cmd
)
644 m_outputTermCmdStr
= cmd
;
645 // revert to default if empty
646 if (m_outputTermCmdStr
.isEmpty()) {
647 m_outputTermCmdStr
= defaultTermCmdStr
;
651 void DebuggerMainWndBase::slotDebuggerStarting()
653 if (m_debugger
== 0) /* paranoia check */
656 if (m_ttyLevel
== m_debugger
->ttyLevel())
661 // shut down terminal emulations we will not need
662 switch (m_ttyLevel
) {
663 case KDebugger::ttySimpleOutputOnly
:
664 ttyWindow()->deactivate();
666 case KDebugger::ttyFull
:
667 if (m_outputTermProc
!= 0) {
668 m_outputTermProc
->kill();
669 // will be deleted in slot
675 m_ttyLevel
= m_debugger
->ttyLevel();
678 switch (m_ttyLevel
) {
679 case KDebugger::ttySimpleOutputOnly
:
680 ttyName
= ttyWindow()->activate();
682 case KDebugger::ttyFull
:
683 if (m_outputTermProc
== 0) {
684 // create an output window
685 ttyName
= createOutputWindow();
686 TRACE(ttyName
.isEmpty() ?
687 "createOuputWindow failed" : "successfully created output window");
693 m_debugger
->setTerminal(ttyName
);
697 void DebuggerMainWndBase::setDebuggerCmdStr(const QString
& cmd
)
699 m_debuggerCmdStr
= cmd
;
700 // make empty if it is the default
701 if (m_debuggerCmdStr
== GdbDriver::defaultGdb()) {
702 m_debuggerCmdStr
= QString();
704 if (m_debugger
!= 0) {
705 m_debugger
->setDebuggerCmd(m_debuggerCmdStr
);
709 void DebuggerMainWndBase::addRecentExec(const QString
& executable
)
711 int pos
= m_recentExecList
.find(executable
);
715 m_recentExecList
.remove(pos
);
719 m_recentExecList
.insert(0, executable
);
720 } // else pos == 0, which means we dont need to change the list
723 while (m_recentExecList
.count() > MAX_RECENT_FILES
) {
724 m_recentExecList
.remove(MAX_RECENT_FILES
);
728 void DebuggerMainWndBase::removeRecentExec(const QString
& executable
)
730 int pos
= m_recentExecList
.find(executable
);
732 m_recentExecList
.remove(pos
);
736 bool DebuggerMainWndBase::debugProgramInteractive(const QString
& executable
,
739 // check the file name
740 QFileInfo
fi(executable
);
741 m_lastDirectory
= fi
.dirPath(true);
744 QString msgFmt
= i18n("`%s' is not a file or does not exist");
745 SIZED_QString(msg
, msgFmt
.length() + executable
.length() + 20);
747 msg
.sprintf(msgFmt
, executable
.data());
748 KMsgBox::message(parent
, kapp
->appName(),
753 msg
.sprintf(msgFmt
, executable
.latin1());
754 KMessageBox::sorry(parent
, msg
);
759 return debugProgram(executable
, parent
);
763 #include "mainwndbase.moc"