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"
27 #include "procattach.h"
30 #include "commandids.h"
36 #ifdef HAVE_SYS_STAT_H
37 #include <sys/stat.h> /* mknod(2) */
40 #include <fcntl.h> /* open(2) */
43 #include <unistd.h> /* getpid, unlink etc. */
46 #define MAX_RECENT_FILES 4
51 WatchWindow::WatchWindow(QWidget
* parent
, const char* name
, WFlags f
) :
52 QWidget(parent
, name
, f
),
53 m_watchEdit(this, "watch_edit"),
54 m_watchAdd(i18n(" Add "), this, "watch_add"),
55 m_watchDelete(i18n(" Del "), this, "watch_delete"),
56 m_watchVariables(this, "watch_variables"),
61 m_watchAdd
.setMinimumSize(m_watchAdd
.sizeHint());
62 m_watchDelete
.setMinimumSize(m_watchDelete
.sizeHint());
63 m_watchV
.addLayout(&m_watchH
, 0);
64 m_watchV
.addWidget(&m_watchVariables
, 10);
65 m_watchH
.addWidget(&m_watchEdit
, 10);
66 m_watchH
.addWidget(&m_watchAdd
, 0);
67 m_watchH
.addWidget(&m_watchDelete
, 0);
69 connect(&m_watchEdit
, SIGNAL(returnPressed()), SIGNAL(addWatch()));
70 connect(&m_watchAdd
, SIGNAL(clicked()), SIGNAL(addWatch()));
71 connect(&m_watchDelete
, SIGNAL(clicked()), SIGNAL(deleteWatch()));
72 connect(&m_watchVariables
, SIGNAL(highlighted(int)), SLOT(slotWatchHighlighted(int)));
74 m_watchVariables
.setMoveCurrentToSibling(true);
75 m_watchVariables
.installEventFilter(this);
78 WatchWindow::~WatchWindow()
82 bool WatchWindow::eventFilter(QObject
*, QEvent
* ev
)
92 QKeyEvent
* kev
= static_cast<QKeyEvent
*>(ev
);
93 if (kev
->key() == Key_Delete
) {
102 // place the text of the hightlighted watch expr in the edit field
103 void WatchWindow::slotWatchHighlighted(int idx
)
105 QString text
= m_watchVariables
.exprStringAt(idx
);
106 m_watchEdit
.setText(text
);
110 static void splitCmdStr(const QString
& cmd
, ValArray
<QString
>& parts
)
112 QString str
= cmd
.simplifyWhiteSpace();
115 while ((end
= str
.find(' ', start
)) >= 0) {
116 parts
.append(str
.mid(start
, end
-start
));
119 parts
.append(str
.mid(start
, str
.length()-start
));
123 const char defaultTermCmdStr
[] = "xterm -name kdbgio -title %T -e sh -c %C";
124 const char defaultSourceFilter
[] = "*.c *.cc *.cpp *.c++ *.C *.CC";
125 const char defaultHeaderFilter
[] = "*.h *.hh *.hpp *.h++";
128 DebuggerMainWndBase::DebuggerMainWndBase() :
129 m_animationCounter(0),
130 m_outputTermCmdStr(defaultTermCmdStr
),
132 m_ttyLevel(-1), /* no tty yet */
133 #ifdef GDB_TRANSCRIPT
134 m_transcriptFile(GDB_TRANSCRIPT
),
136 m_popForeground(false),
139 m_sourceFilter(defaultSourceFilter
),
140 m_headerFilter(defaultHeaderFilter
),
143 m_statusActive
= i18n("active");
144 m_recentExecList
.setAutoDelete(true);
147 DebuggerMainWndBase::~DebuggerMainWndBase()
151 // if the output window is open, close it
152 if (m_outputTermProc
!= 0) {
153 m_outputTermProc
->disconnect(); /* ignore signals */
154 m_outputTermProc
->kill();
155 shutdownTermWindow();
159 void DebuggerMainWndBase::setupDebugger(ExprWnd
* localVars
,
163 QWidget
* parent
= dbgMainWnd();
165 GdbDriver
* driver
= new GdbDriver
;
166 driver
->setLogFileName(m_transcriptFile
);
168 m_debugger
= new KDebugger(parent
, localVars
, watchVars
, backtrace
, driver
);
170 QObject::connect(m_debugger
, SIGNAL(updateStatusMessage()),
171 parent
, SLOT(slotNewStatusMsg()));
172 QObject::connect(m_debugger
, SIGNAL(updateUI()),
173 parent
, SLOT(updateUI()));
175 QObject::connect(m_debugger
, SIGNAL(lineItemsChanged()),
176 parent
, SLOT(updateLineItems()));
178 QObject::connect(m_debugger
, SIGNAL(animationTimeout()),
179 parent
, SLOT(slotAnimationTimeout()));
180 QObject::connect(m_debugger
, SIGNAL(debuggerStarting()),
181 parent
, SLOT(slotDebuggerStarting()));
183 m_debugger
->setDebuggerCmd(m_debuggerCmdStr
);
187 void DebuggerMainWndBase::setCoreFile(const QString
& corefile
)
189 assert(m_debugger
!= 0);
190 m_debugger
->useCoreFile(corefile
, true);
193 void DebuggerMainWndBase::setRemoteDevice(const QString
& remoteDevice
)
195 if (m_debugger
!= 0) {
196 m_debugger
->setRemoteDevice(remoteDevice
);
200 void DebuggerMainWndBase::setTranscript(const char* name
)
202 m_transcriptFile
= name
;
204 m_debugger
->driver()->setLogFileName(m_transcriptFile
);
207 const char OutputWindowGroup
[] = "OutputWindow";
208 const char TermCmdStr
[] = "TermCmdStr";
209 const char KeepScript
[] = "KeepScript";
210 const char DebuggerGroup
[] = "Debugger";
211 const char DebuggerCmdStr
[] = "DebuggerCmdStr";
212 const char PreferencesGroup
[] = "Preferences";
213 const char PopForeground
[] = "PopForeground";
214 const char BackTimeout
[] = "BackTimeout";
215 const char TabWidth
[] = "TabWidth";
216 const char FilesGroup
[] = "Files";
217 const char RecentExecutables
[] = "RecentExecutables";
218 const char SourceFileFilter
[] = "SourceFileFilter";
219 const char HeaderFileFilter
[] = "HeaderFileFilter";
221 void DebuggerMainWndBase::saveSettings(KConfig
* config
)
223 if (m_debugger
!= 0) {
224 m_debugger
->saveSettings(config
);
227 KConfigGroupSaver
g(config
, OutputWindowGroup
);
228 config
->writeEntry(TermCmdStr
, m_outputTermCmdStr
);
230 config
->setGroup(DebuggerGroup
);
231 config
->writeEntry(DebuggerCmdStr
, m_debuggerCmdStr
);
233 config
->setGroup(PreferencesGroup
);
234 config
->writeEntry(PopForeground
, m_popForeground
);
235 config
->writeEntry(BackTimeout
, m_backTimeout
);
236 config
->writeEntry(TabWidth
, m_tabWidth
);
237 config
->writeEntry(SourceFileFilter
, m_sourceFilter
);
238 config
->writeEntry(HeaderFileFilter
, m_headerFilter
);
240 config
->setGroup(FilesGroup
);
241 config
->writeEntry(RecentExecutables
, m_recentExecList
, ',');
244 void DebuggerMainWndBase::restoreSettings(KConfig
* config
)
246 if (m_debugger
!= 0) {
247 m_debugger
->restoreSettings(config
);
250 KConfigGroupSaver
g(config
, OutputWindowGroup
);
252 * For debugging and emergency purposes, let the config file override
253 * the shell script that is used to keep the output window open. This
254 * string must have EXACTLY 1 %s sequence in it.
256 setTerminalCmd(config
->readEntry(TermCmdStr
, defaultTermCmdStr
));
257 m_outputTermKeepScript
= config
->readEntry(KeepScript
);
259 config
->setGroup(DebuggerGroup
);
260 setDebuggerCmdStr(config
->readEntry(DebuggerCmdStr
));
262 config
->setGroup(PreferencesGroup
);
263 m_popForeground
= config
->readBoolEntry(PopForeground
, false);
264 m_backTimeout
= config
->readNumEntry(BackTimeout
, 1000);
265 m_tabWidth
= config
->readNumEntry(TabWidth
, 0);
266 m_sourceFilter
= config
->readEntry(SourceFileFilter
, m_sourceFilter
);
267 m_headerFilter
= config
->readEntry(HeaderFileFilter
, m_headerFilter
);
269 config
->setGroup(FilesGroup
);
270 config
->readListEntry(RecentExecutables
, m_recentExecList
,',');
273 bool DebuggerMainWndBase::debugProgram(const QString
& executable
)
275 assert(m_debugger
!= 0);
276 return m_debugger
->debugProgram(executable
);
279 // helper that gets a file name (it only differs in the caption of the dialog)
280 QString
DebuggerMainWndBase::myGetFileName(QString caption
,
281 QString dir
, QString filter
,
285 KFileDialog
dlg(dir
, filter
, parent
, "filedialog", true);
287 dlg
.setCaption(caption
);
289 if (dlg
.exec() == QDialog::Accepted
)
290 filename
= dlg
.selectedFile();
295 bool DebuggerMainWndBase::handleCommand(int item
)
297 /* first commands that don't require the debugger */
299 case ID_FILE_GLOBAL_OPTIONS
:
304 // now commands that do
309 case ID_FILE_EXECUTABLE
:
310 if (m_debugger
->isIdle())
312 // open a new executable
313 QString executable
= myGetFileName(i18n("Select the executable to debug"),
314 m_lastDirectory
, 0, dbgMainWnd());
315 if (executable
.isEmpty())
318 if (debugProgramInteractive(executable
)) {
319 addRecentExec(executable
);
323 case ID_FILE_PROG_SETTINGS
:
324 m_debugger
->programSettings(dbgMainWnd());
326 case ID_FILE_COREFILE
:
327 if (m_debugger
->canUseCoreFile())
329 QString corefile
= myGetFileName(i18n("Select core dump"),
330 m_lastDirectory
, 0, dbgMainWnd());
331 if (!corefile
.isEmpty()) {
332 m_debugger
->useCoreFile(corefile
, false);
337 m_debugger
->programRun();
339 case ID_PROGRAM_ATTACH
:
341 ProcAttach
dlg(dbgMainWnd());
342 dlg
.setText(m_debugger
->attachedPid());
344 m_debugger
->attachProgram(dlg
.text());
348 case ID_PROGRAM_RUN_AGAIN
:
349 m_debugger
->programRunAgain();
351 case ID_PROGRAM_STEP
:
352 m_debugger
->programStep(false);
354 case ID_PROGRAM_STEPI
:
355 m_debugger
->programStep(true);
357 case ID_PROGRAM_NEXT
:
358 m_debugger
->programNext(false);
360 case ID_PROGRAM_NEXTI
:
361 m_debugger
->programNext(true);
363 case ID_PROGRAM_FINISH
:
364 m_debugger
->programFinish();
366 case ID_PROGRAM_KILL
:
367 m_debugger
->programKill();
369 case ID_PROGRAM_BREAK
:
370 m_debugger
->programBreak();
372 case ID_PROGRAM_ARGS
:
373 m_debugger
->programArgs(dbgMainWnd());
379 void DebuggerMainWndBase::updateUIItem(UpdateUI
* item
)
382 case ID_FILE_EXECUTABLE
:
383 item
->enable(m_debugger
->isIdle());
385 case ID_FILE_PROG_SETTINGS
:
386 item
->enable(m_debugger
->haveExecutable());
388 case ID_FILE_COREFILE
:
389 item
->enable(m_debugger
->canUseCoreFile());
391 case ID_PROGRAM_STEP
:
392 case ID_PROGRAM_STEPI
:
393 case ID_PROGRAM_NEXT
:
394 case ID_PROGRAM_NEXTI
:
395 case ID_PROGRAM_FINISH
:
396 case ID_PROGRAM_UNTIL
:
397 case ID_PROGRAM_RUN_AGAIN
:
398 item
->enable(m_debugger
->canSingleStep());
400 case ID_PROGRAM_ATTACH
:
402 item
->enable(m_debugger
->isReady());
404 case ID_PROGRAM_KILL
:
405 item
->enable(m_debugger
->haveExecutable() && m_debugger
->isProgramActive());
407 case ID_PROGRAM_BREAK
:
408 item
->enable(m_debugger
->isProgramRunning());
410 case ID_PROGRAM_ARGS
:
411 item
->enable(m_debugger
->haveExecutable());
416 dbgStatusBar()->changeItem(m_debugger
->isProgramActive() ?
417 static_cast<const char*>(m_statusActive
) : "",
421 void DebuggerMainWndBase::updateLineItems()
425 void DebuggerMainWndBase::initAnimation()
428 QString path
= kapp
->kde_datadir() + "/kfm/pics/";
430 pixmap
.load(path
+ "/kde1.xpm");
433 QPixmap pixmap
= BarIcon("kde1");
437 KToolBar
* toolbar
= dbgToolBar();
438 toolbar
->insertButton(pixmap
, ID_STATUS_BUSY
);
439 toolbar
->alignItemRight(ID_STATUS_BUSY
, true);
441 // Load animated logo
442 m_animation
.setAutoDelete(true);
444 for (int i
= 1; i
<= numPix
; i
++) {
446 n
.sprintf("/kde%d.xpm", i
);
447 QPixmap
* p
= new QPixmap();
450 n
.sprintf("kde%d", i
);
451 QPixmap
* p
= new QPixmap(BarIcon(n
));
454 m_animation
.append(p
);
459 // safeguard: if we did not find a single icon, add a dummy
460 if (m_animation
.count() == 0) {
461 QPixmap
* pix
= new QPixmap(2,2);
464 p
.fillRect(0,0,2,2,QBrush(white
));
466 p
.fillRect(0,0,2,2,QBrush(Qt::white
));
468 m_animation
.append(pix
);
472 void DebuggerMainWndBase::slotAnimationTimeout()
474 assert(m_animation
.count() > 0); /* must have been initialized */
475 m_animationCounter
++;
476 if (m_animationCounter
== m_animation
.count())
477 m_animationCounter
= 0;
478 dbgToolBar()->setButtonPixmap(ID_STATUS_BUSY
,
479 *m_animation
.at(m_animationCounter
));
482 void DebuggerMainWndBase::slotNewStatusMsg()
484 QString msg
= m_debugger
->statusMessage();
485 dbgStatusBar()->changeItem(msg
, ID_STATUS_MSG
);
488 void DebuggerMainWndBase::doGlobalOptions()
490 QTabDialog
dlg(dbgMainWnd(), "global_options", true);
491 QString title
= kapp
->getCaption();
492 title
+= i18n(": Global options");
493 dlg
.setCaption(title
);
494 dlg
.setCancelButton(i18n("Cancel"));
495 dlg
.setOKButton(i18n("OK"));
497 PrefDebugger
prefDebugger(&dlg
);
498 prefDebugger
.setDebuggerCmd(m_debuggerCmdStr
.isEmpty() ?
499 GdbDriver::defaultGdb() : m_debuggerCmdStr
);
500 prefDebugger
.setTerminal(m_outputTermCmdStr
);
502 PrefMisc
prefMisc(&dlg
);
503 prefMisc
.setPopIntoForeground(m_popForeground
);
504 prefMisc
.setBackTimeout(m_backTimeout
);
505 prefMisc
.setTabWidth(m_tabWidth
);
506 prefMisc
.setSourceFilter(m_sourceFilter
);
507 prefMisc
.setHeaderFilter(m_headerFilter
);
509 dlg
.addTab(&prefDebugger
, i18n("&Debugger"));
510 dlg
.addTab(&prefMisc
, i18n("&Miscellaneous"));
511 if (dlg
.exec() == QDialog::Accepted
)
513 setDebuggerCmdStr(prefDebugger
.debuggerCmd());
514 setTerminalCmd(prefDebugger
.terminal());
515 m_popForeground
= prefMisc
.popIntoForeground();
516 m_backTimeout
= prefMisc
.backTimeout();
517 m_tabWidth
= prefMisc
.tabWidth();
518 m_sourceFilter
= prefMisc
.sourceFilter();
519 if (m_sourceFilter
.isEmpty())
520 m_sourceFilter
= defaultSourceFilter
;
521 m_headerFilter
= prefMisc
.headerFilter();
522 if (m_headerFilter
.isEmpty())
523 m_headerFilter
= defaultHeaderFilter
;
527 const char fifoNameBase
[] = "/tmp/kdbgttywin%05d";
530 * We use the scope operator :: in this function, so that we don't
531 * accidentally use the wrong close() function (I've been bitten ;-),
532 * outch!) (We use it for all the libc functions, to be consistent...)
534 QString
DebuggerMainWndBase::createOutputWindow()
536 // create a name for a fifo
538 fifoName
.sprintf(fifoNameBase
, ::getpid());
540 // create a fifo that will pass in the tty name
541 ::unlink(fifoName
); /* remove remnants */
543 if (::mkfifo(fifoName
, S_IRUSR
|S_IWUSR
) < 0) {
545 TRACE("mkfifo " + fifoName
+ " failed");
549 if (::mknod(fifoName
, S_IFIFO
| S_IRUSR
|S_IWUSR
, 0) < 0) {
551 TRACE("mknod " + fifoName
+ " failed");
556 m_outputTermProc
= new KProcess
;
560 * Spawn an xterm that in turn runs a shell script that passes us
561 * back the terminal name and then only sits and waits.
563 static const char shellScriptFmt
[] =
565 "trap \"\" INT QUIT TSTP;" /* ignore various signals */
566 "exec<&-;exec>&-;" /* close stdin and stdout */
567 "while :;do sleep 3600;done";
568 // let config file override this script
569 const char* fmt
= shellScriptFmt
;
570 if (m_outputTermKeepScript
.length() != 0) {
571 fmt
= m_outputTermKeepScript
.data();
574 QString
shellScript(strlen(fmt
) + fifoName
.length());
578 shellScript
.sprintf(fmt
, fifoName
.data());
579 TRACE("output window script is " + shellScript
);
581 QString title
= kapp
->getCaption();
582 title
+= i18n(": Program output");
584 // parse the command line specified in the preferences
585 ValArray
<QString
> cmdParts
;
586 splitCmdStr(m_outputTermCmdStr
, cmdParts
);
589 * Build the argv array. Thereby substitute special sequences:
596 { "%C", shellScript
}
599 for (int i
= 0; i
< cmdParts
.size(); i
++) {
600 QString
& str
= cmdParts
[i
];
601 for (int j
= sizeof(substitute
)/sizeof(substitute
[0])-1; j
>= 0; j
--) {
602 int pos
= str
.find(substitute
[j
].seq
);
604 str
.replace(pos
, 2, substitute
[j
].replace
);
605 break; /* substitute only one sequence */
608 *m_outputTermProc
<< str
;
613 if (m_outputTermProc
->start())
615 // read the ttyname from the fifo
616 int f
= ::open(fifoName
, O_RDONLY
);
624 int n
= ::read(f
, ttyname
, sizeof(ttyname
)-sizeof(char)); /* leave space for '\0' */
636 QString tty
= QString(ttyname
).stripWhiteSpace();
642 // error, could not start xterm
643 TRACE("fork failed for fifo " + fifoName
);
645 shutdownTermWindow();
650 void DebuggerMainWndBase::shutdownTermWindow()
652 delete m_outputTermProc
;
653 m_outputTermProc
= 0;
656 void DebuggerMainWndBase::setTerminalCmd(const QString
& cmd
)
658 m_outputTermCmdStr
= cmd
;
659 // revert to default if empty
660 if (m_outputTermCmdStr
.isEmpty()) {
661 m_outputTermCmdStr
= defaultTermCmdStr
;
665 void DebuggerMainWndBase::slotDebuggerStarting()
667 if (m_debugger
== 0) /* paranoia check */
670 if (m_ttyLevel
== m_debugger
->ttyLevel())
675 // shut down terminal emulations we will not need
676 switch (m_ttyLevel
) {
677 case KDebugger::ttySimpleOutputOnly
:
678 ttyWindow()->deactivate();
680 case KDebugger::ttyFull
:
681 if (m_outputTermProc
!= 0) {
682 m_outputTermProc
->kill();
683 // will be deleted in slot
689 m_ttyLevel
= m_debugger
->ttyLevel();
692 switch (m_ttyLevel
) {
693 case KDebugger::ttySimpleOutputOnly
:
694 ttyName
= ttyWindow()->activate();
696 case KDebugger::ttyFull
:
697 if (m_outputTermProc
== 0) {
698 // create an output window
699 ttyName
= createOutputWindow();
700 TRACE(ttyName
.isEmpty() ?
701 "createOuputWindow failed" : "successfully created output window");
707 m_debugger
->setTerminal(ttyName
);
711 void DebuggerMainWndBase::setDebuggerCmdStr(const QString
& cmd
)
713 m_debuggerCmdStr
= cmd
;
714 // make empty if it is the default
715 if (m_debuggerCmdStr
== GdbDriver::defaultGdb()) {
716 m_debuggerCmdStr
= QString();
718 if (m_debugger
!= 0) {
719 m_debugger
->setDebuggerCmd(m_debuggerCmdStr
);
723 void DebuggerMainWndBase::addRecentExec(const QString
& executable
)
725 int pos
= m_recentExecList
.find(executable
);
729 m_recentExecList
.remove(pos
);
733 m_recentExecList
.insert(0, executable
);
734 } // else pos == 0, which means we dont need to change the list
737 while (m_recentExecList
.count() > MAX_RECENT_FILES
) {
738 m_recentExecList
.remove(MAX_RECENT_FILES
);
742 void DebuggerMainWndBase::removeRecentExec(const QString
& executable
)
744 int pos
= m_recentExecList
.find(executable
);
746 m_recentExecList
.remove(pos
);
750 bool DebuggerMainWndBase::debugProgramInteractive(const QString
& executable
)
752 // check the file name
753 QFileInfo
fi(executable
);
754 m_lastDirectory
= fi
.dirPath(true);
757 QString msgFmt
= i18n("`%s' is not a file or does not exist");
758 SIZED_QString(msg
, msgFmt
.length() + executable
.length() + 20);
760 msg
.sprintf(msgFmt
, executable
.data());
761 KMsgBox::message(dbgMainWnd(), kapp
->appName(),
766 msg
.sprintf(msgFmt
, executable
.latin1());
767 KMessageBox::sorry(dbgMainWnd(), msg
);
772 if (!m_debugger
->debugProgram(executable
)) {
773 QString msg
= i18n("Could not start the debugger process.\n"
774 "Please shut down KDbg and resolve the problem.");
776 KMsgBox::message(dbgMainWnd(), kapp
->appName(),
781 KMessageBox::sorry(dbgMainWnd(), msg
);
788 #include "mainwndbase.moc"