blob: b19debbea55d60dedf39e7c6f18207e144cecf01 [file] [log] [blame]
The Android Open Source Project52d4c302009-03-03 19:29:09 -08001//
2// Copyright 2005 The Android Open Source Project
3//
4// Display runtime log output.
5//
6
7// For compilers that support precompilation, include "wx/wx.h".
8#include "wx/wxprec.h"
9
10// Otherwise, include all standard headers
11#ifndef WX_PRECOMP
12# include "wx/wx.h"
13#endif
14#include "wx/image.h" // needed for Windows build
15#include "wx/dcbuffer.h"
16
17#include "LogWindow.h"
18#include "LogMessage.h"
19#include "LogPrefsDialog.h"
20#include "MyApp.h"
21#include "Preferences.h"
22#include "Resource.h"
23#include "UserEventMessage.h"
24
25#include <errno.h>
26
27static int android_snprintfBuffer(char** pBuf, int bufLen, const char* format, ...);
28static int android_vsnprintfBuffer(char** pBuf, int bufLen, const char* format, va_list args);
29
30
31using namespace android;
32
33#if 0 // experiment -- works on Win32, but not with GTK
34class MyTextCtrl : public wxTextCtrl {
35public:
36 MyTextCtrl(wxWindow* parent, wxWindowID id, const wxString& value,
37 const wxPoint& pos, const wxSize& size, int style = 0)
38 : wxTextCtrl(parent, id, value, pos, size, style)
39 {
40 printf("***************** MyTextCtrl!\n");
41 }
42
43 void OnScroll(wxScrollWinEvent& event);
44 void OnScrollBottom(wxScrollWinEvent& event);
45
46private:
47 DECLARE_EVENT_TABLE()
48};
49
50BEGIN_EVENT_TABLE(MyTextCtrl, wxTextCtrl)
51 EVT_SCROLLWIN(MyTextCtrl::OnScroll)
52 EVT_SCROLLWIN_BOTTOM(MyTextCtrl::OnScrollBottom)
53END_EVENT_TABLE()
54
55void MyTextCtrl::OnScroll(wxScrollWinEvent& event)
56{
57 printf("OnScroll!\n");
58}
59
60void MyTextCtrl::OnScrollBottom(wxScrollWinEvent& event)
61{
62 printf("OnScrollBottom!\n");
63}
64#endif
65
66
67BEGIN_EVENT_TABLE(LogWindow, wxDialog)
68 EVT_CLOSE(LogWindow::OnClose)
69 EVT_MOVE(LogWindow::OnMove)
70 EVT_COMBOBOX(IDC_LOG_LEVEL, LogWindow::OnLogLevel)
71 EVT_BUTTON(IDC_LOG_CLEAR, LogWindow::OnLogClear)
72 EVT_BUTTON(IDC_LOG_PAUSE, LogWindow::OnLogPause)
73 EVT_BUTTON(IDC_LOG_PREFS, LogWindow::OnLogPrefs)
74END_EVENT_TABLE()
75
76/*
77 * Information about log levels.
78 *
79 * Each entry here corresponds to an entry in the combo box. The first
80 * letter of each name should be unique.
81 */
82static const struct {
83 wxString name;
84 android_LogPriority priority;
85} gLogLevels[] = {
86 { wxT("Verbose"), ANDROID_LOG_VERBOSE },
87 { wxT("Debug"), ANDROID_LOG_DEBUG },
88 { wxT("Info"), ANDROID_LOG_INFO },
89 { wxT("Warn"), ANDROID_LOG_WARN },
90 { wxT("Error"), ANDROID_LOG_ERROR }
91};
92
93
94/*
95 * Create a new LogWindow. This should be a child of the main frame.
96 */
97LogWindow::LogWindow(wxWindow* parent)
98 : wxDialog(parent, wxID_ANY, wxT("Log Output"), wxDefaultPosition,
99 wxDefaultSize,
100 wxCAPTION | wxSYSTEM_MENU | wxCLOSE_BOX | wxRESIZE_BORDER),
101 mDisplayArray(NULL), mMaxDisplayMsgs(0), mPaused(false),
102 mMinPriority(ANDROID_LOG_VERBOSE),
103 mHeaderFormat(LogPrefsDialog::kHFFull),
104 mSingleLine(false), mExtraSpacing(0), mPointSize(10), mUseColor(true),
105 mFontMonospace(true), mWriteFile(false), mTruncateOld(true), mLogFp(NULL),
106 mNewlyShown(false), mLastPosition(wxDefaultPosition), mVisible(false)
107{
108 ConstructControls();
109
110 Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs();
111
112 int poolSize = 10240; // 10MB
113 pPrefs->GetInt("log-pool-size-kbytes", &poolSize);
114 assert(poolSize > 0);
115 mPool.Resize(poolSize * 1024);
116
117 mMaxDisplayMsgs = 1000;
118 pPrefs->GetInt("log-display-msg-count", &mMaxDisplayMsgs);
119 assert(mMaxDisplayMsgs > 0);
120 mDisplayArray = new LogMessage*[mMaxDisplayMsgs];
121 memset(mDisplayArray, 0, sizeof(LogMessage*) * mMaxDisplayMsgs);
122 mTopPtr = -1;
123 mNextPtr = 0;
124
125 int tmpInt = (int) mHeaderFormat;
126 pPrefs->GetInt("log-header-format", &tmpInt);
127 mHeaderFormat = (LogPrefsDialog::HeaderFormat) tmpInt;
128 pPrefs->GetBool("log-single-line", &mSingleLine);
129 pPrefs->GetInt("log-extra-spacing", &mExtraSpacing);
130 pPrefs->GetInt("log-point-size", &mPointSize);
131 pPrefs->GetBool("log-use-color", &mUseColor);
132 pPrefs->SetBool("log-font-monospace", &mFontMonospace);
133 SetTextStyle();
134
135 mFileName = wxT("/tmp/android-log.txt");
136 pPrefs->GetBool("log-write-file", &mWriteFile);
137 pPrefs->GetString("log-filename", /*ref*/mFileName);
138 pPrefs->GetBool("log-truncate-old", &mTruncateOld);
139
140 PrepareLogFile();
141}
142
143/*
144 * Destroy everything we own.
145 */
146LogWindow::~LogWindow(void)
147{
148 ClearDisplay();
149 delete[] mDisplayArray;
150
151 if (mLogFp != NULL)
152 fclose(mLogFp);
153}
154
155/*
156 * Set the text style, based on our preferences.
157 */
158void LogWindow::SetTextStyle(void)
159{
160 wxTextCtrl* pTextCtrl;
161 pTextCtrl = (wxTextCtrl*) FindWindow(IDC_LOG_TEXT);
162 wxTextAttr style;
163 style = pTextCtrl->GetDefaultStyle();
164
165 if (mFontMonospace) {
166 wxFont font(mPointSize, wxFONTFAMILY_MODERN, wxFONTSTYLE_NORMAL,
167 wxFONTWEIGHT_NORMAL);
168 style.SetFont(font);
169 } else {
170 wxFont font(mPointSize, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL,
171 wxFONTWEIGHT_NORMAL);
172 style.SetFont(font);
173 }
174
175 pTextCtrl->SetDefaultStyle(style);
176}
177
178/*
179 * Set up the goodies in the window.
180 *
181 * Also initializes mMinPriority.
182 */
183void LogWindow::ConstructControls(void)
184{
185 Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs();
186 wxPanel* base = new wxPanel(this, wxID_ANY);
187 wxBoxSizer* masterSizer = new wxBoxSizer(wxVERTICAL);
188 wxBoxSizer* indentSizer = new wxBoxSizer(wxHORIZONTAL);
189 wxBoxSizer* configPrioritySizer = new wxBoxSizer(wxHORIZONTAL);
190 wxGridSizer* configSizer = new wxGridSizer(4, 1);
191
192 /*
193 * Configure log level combo box.
194 */
195 wxComboBox* logLevel;
196 int defaultLogLevel = 1;
197 pPrefs->GetInt("log-display-level", &defaultLogLevel);
198 logLevel = new wxComboBox(base, IDC_LOG_LEVEL, wxT(""),
199 wxDefaultPosition, wxDefaultSize, 0, NULL,
200 wxCB_READONLY /*| wxSUNKEN_BORDER*/);
201 for (int i = 0; i < NELEM(gLogLevels); i++) {
202 logLevel->Append(gLogLevels[i].name);
203 logLevel->SetClientData(i, (void*) gLogLevels[i].priority);
204 }
205 logLevel->SetSelection(defaultLogLevel);
206 mMinPriority = gLogLevels[defaultLogLevel].priority;
207
208 /*
209 * Set up stuff at the bottom, starting with the options
210 * at the bottom left.
211 */
212 configPrioritySizer->Add(new wxStaticText(base, wxID_ANY, wxT("Log level:"),
213 wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT),
214 0, wxALIGN_CENTER_VERTICAL);
215 configPrioritySizer->AddSpacer(kInterSpacing);
216 configPrioritySizer->Add(logLevel);
217
218 wxButton* clear = new wxButton(base, IDC_LOG_CLEAR, wxT("&Clear"),
219 wxDefaultPosition, wxDefaultSize, 0);
220 wxButton* pause = new wxButton(base, IDC_LOG_PAUSE, wxT("&Pause"),
221 wxDefaultPosition, wxDefaultSize, 0);
222 wxButton* prefs = new wxButton(base, IDC_LOG_PREFS, wxT("C&onfigure"),
223 wxDefaultPosition, wxDefaultSize, 0);
224
225 configSizer->Add(configPrioritySizer, 0, wxALIGN_LEFT);
226 configSizer->Add(clear, 0, wxALIGN_CENTER);
227 configSizer->Add(pause, 0, wxALIGN_CENTER);
228 configSizer->Add(prefs, 0, wxALIGN_RIGHT);
229
230 /*
231 * Create text ctrl.
232 */
233 wxTextCtrl* pTextCtrl;
234 pTextCtrl = new wxTextCtrl(base, IDC_LOG_TEXT, wxT(""),
235 wxDefaultPosition, wxDefaultSize,
236 wxTE_MULTILINE | wxTE_READONLY | wxTE_RICH2 | wxTE_NOHIDESEL |
237 wxHSCROLL);
238
239 /*
240 * Add components to master sizer.
241 */
242 masterSizer->AddSpacer(kEdgeSpacing);
243 masterSizer->Add(pTextCtrl, 1, wxEXPAND);
244 masterSizer->AddSpacer(kInterSpacing);
245 masterSizer->Add(configSizer, 0, wxEXPAND);
246 masterSizer->AddSpacer(kEdgeSpacing);
247
248 /*
249 * Indent from sides.
250 */
251 indentSizer->AddSpacer(kEdgeSpacing);
252 indentSizer->Add(masterSizer, 1, wxEXPAND);
253 indentSizer->AddSpacer(kEdgeSpacing);
254
255 base->SetSizer(indentSizer);
256
257 indentSizer->Fit(this); // shrink-to-fit
258 indentSizer->SetSizeHints(this); // define minimum size
259}
260
261/*
262 * In some cases, this means the user has clicked on our "close" button.
263 * We don't really even want one, but both WinXP and KDE put one on our
264 * window whether we want it or not. So, we make it work as a "hide"
265 * button instead.
266 *
267 * This also gets called when the app is shutting down, and we do want
268 * to destroy ourselves then, saving various information about our state.
269 */
270void LogWindow::OnClose(wxCloseEvent& event)
271{
272 /* just hide the window, unless we're shutting down */
273 if (event.CanVeto()) {
274 event.Veto();
275 Show(false);
276 return;
277 }
278
279 /*
280 * Save some preferences.
281 */
282 SaveWindowPrefs();
283
284 /* if we can't veto the Close(), destroy ourselves */
285 Destroy();
286}
287
288/*
289 * Save all of our preferences to the config file.
290 */
291void LogWindow::SaveWindowPrefs(void)
292{
293 Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs();
294
295 /*
296 * Save shown/hidden state.
297 */
298 pPrefs->SetBool("window-log-show", IsShown());
299
300 /*
301 * Limits and formatting prefs.
302 */
303 pPrefs->SetInt("log-display-msg-count", mMaxDisplayMsgs);
304 pPrefs->SetInt("log-pool-size-kbytes", mPool.GetMaxSize() / 1024);
305
306 pPrefs->SetInt("log-header-format", mHeaderFormat);
307 pPrefs->SetBool("log-single-line", mSingleLine);
308 pPrefs->SetInt("log-extra-spacing", mExtraSpacing);
309 pPrefs->SetInt("log-point-size", mPointSize);
310 pPrefs->SetBool("log-use-color", mUseColor);
311 pPrefs->SetBool("log-font-monospace", mFontMonospace);
312
313 pPrefs->SetBool("log-write-file", mWriteFile);
314 pPrefs->SetString("log-filename", mFileName.ToAscii());
315 pPrefs->SetBool("log-truncate-old", mTruncateOld);
316
317 /*
318 * Save window size and position.
319 */
320 wxPoint posn;
321 wxSize size;
322
323 assert(pPrefs != NULL);
324
325 posn = GetPosition();
326 size = GetSize();
327
328 pPrefs->SetInt("window-log-x", posn.x);
329 pPrefs->SetInt("window-log-y", posn.y);
330 pPrefs->SetInt("window-log-width", size.GetWidth());
331 pPrefs->SetInt("window-log-height", size.GetHeight());
332
333 /*
334 * Save current setting of debug level combo box.
335 */
336 wxComboBox* pCombo;
337 int selection;
338 pCombo = (wxComboBox*) FindWindow(IDC_LOG_LEVEL);
339 selection = pCombo->GetSelection();
340 pPrefs->SetInt("log-display-level", selection);
341}
342
343/*
344 * Return the desired position and size.
345 */
346/*static*/ wxRect LogWindow::GetPrefWindowRect(void)
347{
348 Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs();
349 int x, y, width, height;
350
351 assert(pPrefs != NULL);
352
353 x = y = 10;
354 width = 500;
355 height = 200;
356
357 /* these don't modify the arg if the pref doesn't exist */
358 pPrefs->GetInt("window-log-x", &x);
359 pPrefs->GetInt("window-log-y", &y);
360 pPrefs->GetInt("window-log-width", &width);
361 pPrefs->GetInt("window-log-height", &height);
362
363 return wxRect(x, y, width, height);
364}
365
366/*
367 * Under Linux+GTK, the first time you show the window, it appears where
368 * it's supposed to. If you then hide it and show it again, it gets
369 * moved on top of the parent window. After that, you can reposition it
370 * and it remembers its position across hide/show.
371 *
372 * To counter this annoyance, we save the position when we hide, and
373 * reset the position after a show. The "newly shown" flag ensures that
374 * we only reposition the window as the result of a Show(true) call.
375 *
376 * Sometimes, something helpful will shift the window over if it's
377 * partially straddling a seam between two monitors. I don't see an easy
378 * way to block this, and I'm not sure I want to anyway.
379 */
380void LogWindow::OnMove(wxMoveEvent& event)
381{
382 wxPoint point;
383 point = event.GetPosition();
384 //printf("Sim: log window is at (%d,%d) (new=%d)\n", point.x, point.y,
385 // mNewlyShown);
386
387 if (mNewlyShown) {
388 if (mLastPosition == wxDefaultPosition) {
389 //printf("Sim: no last position established\n");
390 } else {
391 Move(mLastPosition);
392 }
393
394 mNewlyShown = false;
395 }
396}
397
398/*
399 * Set the "newly shown" flag.
400 */
401bool LogWindow::Show(bool show)
402{
403 if (show) {
404 mNewlyShown = true;
405 Redisplay();
406 } else {
407 mLastPosition = GetPosition();
408 }
409
410 mVisible = show;
411 return wxDialog::Show(show);
412}
413
414/*
415 * User has adjusted the log level. Update the display appropriately.
416 *
417 * This is a wxEVT_COMMAND_COMBOBOX_SELECTED event.
418 */
419void LogWindow::OnLogLevel(wxCommandEvent& event)
420{
421 int selection;
422 android_LogPriority priority;
423
424 selection = event.GetInt();
425 wxComboBox* pCombo = (wxComboBox*) FindWindow(IDC_LOG_LEVEL);
426 priority = (android_LogPriority) (long)pCombo->GetClientData(event.GetInt());
427
428 printf("Sim: log level selected: %d (%s)\n", (int) priority,
429 (const char*) gLogLevels[selection].name.ToAscii());
430 mMinPriority = priority;
431 Redisplay();
432}
433
434/*
435 * Clear out the log.
436 */
437void LogWindow::OnLogClear(wxCommandEvent& event)
438{
439 ClearDisplay();
440 mPool.Clear();
441}
442
443/*
444 * Handle the pause/resume button.
445 *
446 * If we're un-pausing, we need to get caught up.
447 */
448void LogWindow::OnLogPause(wxCommandEvent& event)
449{
450 mPaused = !mPaused;
451
452 wxButton* pButton = (wxButton*) FindWindow(IDC_LOG_PAUSE);
453 if (mPaused) {
454 pButton->SetLabel(wxT("&Resume"));
455
456 mPool.SetBookmark();
457 } else {
458 pButton->SetLabel(wxT("&Pause"));
459
460 LogMessage* pMsg = mPool.GetBookmark();
461 if (pMsg == NULL) {
462 /* bookmarked item fell out of pool */
463 printf("--- bookmark was lost, redisplaying\n");
464 Redisplay();
465 } else {
466 /*
467 * The bookmark points to the last item added to the display.
468 * We want to chase its "prev" pointer to walk toward the head
469 * of the list, adding items from oldest to newest.
470 */
471 pMsg = pMsg->GetPrev();
472 while (pMsg != NULL) {
473 if (FilterMatches(pMsg))
474 AddToDisplay(pMsg);
475 pMsg = pMsg->GetPrev();
476 }
477 }
478 }
479}
480
481/*
482 * Open log preferences dialog.
483 */
484void LogWindow::OnLogPrefs(wxCommandEvent& event)
485{
486 LogPrefsDialog dialog(this);
487
488 /*
489 * Set up the dialog.
490 */
491 dialog.mHeaderFormat = mHeaderFormat;
492 dialog.mSingleLine = mSingleLine;
493 dialog.mExtraSpacing = mExtraSpacing;
494 dialog.mPointSize = mPointSize;
495 dialog.mUseColor = mUseColor;
496 dialog.mFontMonospace = mFontMonospace;
497
498 dialog.mDisplayMax = mMaxDisplayMsgs;
499 dialog.mPoolSizeKB = mPool.GetMaxSize() / 1024;
500
501 dialog.mWriteFile = mWriteFile;
502 dialog.mFileName = mFileName;
503 dialog.mTruncateOld = mTruncateOld;
504
505 /*
506 * Show it. If they hit "OK", copy the updated values out, and
507 * re-display the log output.
508 */
509 if (dialog.ShowModal() == wxID_OK) {
510 /* discard old display arra */
511 ClearDisplay();
512 delete[] mDisplayArray;
513
514 mHeaderFormat = dialog.mHeaderFormat;
515 mSingleLine = dialog.mSingleLine;
516 mExtraSpacing = dialog.mExtraSpacing;
517 mPointSize = dialog.mPointSize;
518 mUseColor = dialog.mUseColor;
519 mFontMonospace = dialog.mFontMonospace;
520
521 assert(dialog.mDisplayMax > 0);
522 assert(dialog.mPoolSizeKB > 0);
523 mMaxDisplayMsgs = dialog.mDisplayMax;
524 mPool.Resize(dialog.mPoolSizeKB * 1024);
525
526 mWriteFile = dialog.mWriteFile;
527 if (mLogFp != NULL && mFileName != dialog.mFileName) {
528 printf("--- log file name changed, closing\n");
529 fclose(mLogFp);
530 mLogFp = NULL;
531 }
532 mFileName = dialog.mFileName;
533 mTruncateOld = dialog.mTruncateOld;
534
535 mDisplayArray = new LogMessage*[mMaxDisplayMsgs];
536 memset(mDisplayArray, 0, sizeof(LogMessage*) * mMaxDisplayMsgs);
537 Redisplay();
538
539 PrepareLogFile();
540 }
541}
542
543/*
544 * Handle a log message "user event". This should only be called in
545 * the main UI thread.
546 *
547 * We take ownership of "*pLogMessage".
548 */
549void LogWindow::AddLogMessage(LogMessage* pLogMessage)
550{
551 mPool.Add(pLogMessage);
552
553 if (!mPaused && mVisible && FilterMatches(pLogMessage)) {
554 /*
555 * Thought: keep a reference to the previous message. If it
556 * matches in most fields (all except timestamp?), hold it and
557 * increment a counter. If we get a message that doesn't match,
558 * or a timer elapses, synthesize a "previous message repeated N
559 * times" string.
560 */
561 AddToDisplay(pLogMessage);
562 }
563
564 // release the initial ref caused by allocation
565 pLogMessage->Release();
566
567 if (mLogFp != NULL)
568 LogToFile(pLogMessage);
569}
570
571/*
572 * Clear out the display, releasing any log messages held in the display
573 * array.
574 */
575void LogWindow::ClearDisplay(void)
576{
577 wxTextCtrl* pTextCtrl;
578 pTextCtrl = (wxTextCtrl*) FindWindow(IDC_LOG_TEXT);
579 pTextCtrl->Clear();
580
581 /*
582 * Just run through the entire array.
583 */
584 for (int i = 0; i < mMaxDisplayMsgs; i++) {
585 if (mDisplayArray[i] != NULL) {
586 mDisplayArray[i]->Release();
587 mDisplayArray[i] = NULL;
588 }
589 }
590 mTopPtr = -1;
591 mNextPtr = 0;
592}
593
594/*
595 * Clear the current display and regenerate it from the log pool. We need
596 * to do this whenever we change filters or log message formatting.
597 */
598void LogWindow::Redisplay(void)
599{
600 /*
601 * Freeze output rendering so it doesn't flash during update. Doesn't
602 * seem to help for GTK, and it leaves garbage on the screen in WinXP,
603 * so I'm leaving it commented out.
604 */
605 //wxTextCtrl* pText = (wxTextCtrl*) FindWindow(IDC_LOG_TEXT);
606 //pText->Freeze();
607
608 //printf("--- redisplay\n");
609 ClearDisplay();
610
611 /*
612 * Set up the default wxWidgets text style stuff.
613 */
614 SetTextStyle();
615
616 /*
617 * Here's the plan:
618 * - Start at the head of the pool (where the most recently added
619 * items are).
620 * - Check to see if the current item passes our filter. If it does,
621 * increment the "found count".
622 * - Continue in this manner until we run out of pool or have
623 * sufficient items to fill the screen.
624 * - Starting from the current position, walk back toward the head,
625 * adding the items that meet the current filter criteria.
626 *
627 * Don't forget that the log pool could be empty.
628 */
629 LogMessage* pMsg = mPool.GetHead();
630
631 if (pMsg != NULL) {
632 int foundCount = 0;
633
634 // note this stops before it runs off the end
635 while (pMsg->GetNext() != NULL && foundCount < mMaxDisplayMsgs) {
636 if (FilterMatches(pMsg))
637 foundCount++;
638 pMsg = pMsg->GetNext();
639 }
640
641 while (pMsg != NULL) {
642 if (FilterMatches(pMsg))
643 AddToDisplay(pMsg);
644 pMsg = pMsg->GetPrev();
645 }
646 }
647
648 //pText->Thaw();
649}
650
651
652/*
653 * Returns "true" if the currently specified filters would allow this
654 * message to be shown.
655 */
656bool LogWindow::FilterMatches(const LogMessage* pLogMessage)
657{
658 if (pLogMessage->GetPriority() >= mMinPriority)
659 return true;
660 else
661 return false;
662}
663
664/*
665 * Realloc the array of pointers, and remove anything from the display
666 * that should no longer be there.
667 */
668void LogWindow::SetMaxDisplayMsgs(int max)
669{
670 Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs();
671
672 pPrefs->SetInt("log-display-msg-count", max);
673}
674
675/*
676 * Add the message to the display array and to the screen.
677 */
678void LogWindow::AddToDisplay(LogMessage* pLogMessage)
679{
680 wxTextCtrl* pTextCtrl;
681 pTextCtrl = (wxTextCtrl*) FindWindow(IDC_LOG_TEXT);
682
683 if (mNextPtr == mTopPtr) {
684 /*
685 * The display array is full.
686 *
687 * We need to eliminate the topmost entry. This requires removing
688 * it from the array and removing the text from the wxTextCtrl.
689 */
690 pTextCtrl->Remove(0, mDisplayArray[mTopPtr]->GetTextCtrlLen());
691 mDisplayArray[mTopPtr]->Release();
692 mTopPtr = (mTopPtr + 1) % mMaxDisplayMsgs;
693 }
694
695 /*
696 * Add formatted text to the text ctrl. Track how much actual space
697 * is required. The space may be different on Win32 (CRLF-based) vs.
698 * GTK (LF-based), so we need to measure it, not compute it from the
699 * text string.
700 */
701 long lastBefore, lastAfter;
702 //long insertBefore;
703 //insertBefore = pTextCtrl->GetInsertionPoint();
704 lastBefore = pTextCtrl->GetLastPosition();
705 FormatMessage(pLogMessage, pTextCtrl);
706 lastAfter = pTextCtrl->GetLastPosition();
707 pLogMessage->SetTextCtrlLen(lastAfter - lastBefore);
708
709 /*
710 * If we restore the old insertion point, we will be glued to where
711 * we were. This is okay until we start deleting text from the top,
712 * at which point we need to adjust it to retain our position.
713 *
714 * If we set the insertion point to the bottom, we effectively
715 * implement "scroll to bottom on output".
716 *
717 * If we don't set it at all, we get slightly strange behavior out
718 * of GTK, which seems to be par for the course here.
719 */
720 //pTextCtrl->SetInsertionPoint(insertBefore); // restore insertion pt
721 pTextCtrl->SetInsertionPoint(lastAfter);
722
723 /* add it to array, claim ownership */
724 mDisplayArray[mNextPtr] = pLogMessage;
725 pLogMessage->Acquire();
726
727 /* adjust pointers */
728 if (mTopPtr < 0) // first time only
729 mTopPtr = 0;
730 mNextPtr = (mNextPtr + 1) % mMaxDisplayMsgs;
731}
732
733
734/*
735 * Return a human-readable string for the priority level. Always returns
736 * a valid string.
737 */
738static const wxCharBuffer GetPriorityString(android_LogPriority priority)
739{
740 int idx;
741
742 idx = (int) priority - (int) ANDROID_LOG_VERBOSE;
743 if (idx < 0 || idx >= NELEM(gLogLevels))
744 return "?unknown?";
745 return gLogLevels[idx].name.ToAscii();
746}
747
748/*
749 * Format a message and write it to the text control.
750 */
751void LogWindow::FormatMessage(const LogMessage* pLogMessage,
752 wxTextCtrl* pTextCtrl)
753{
754#if defined(HAVE_LOCALTIME_R)
755 struct tm tmBuf;
756#endif
757 struct tm* ptm;
758 char timeBuf[32];
759 char msgBuf[256];
760 int msgLen = 0;
761 char* outBuf;
762 char priChar;
763 LogPrefsDialog::HeaderFormat headerFmt;
764
765 headerFmt = mHeaderFormat;
766 if (pLogMessage->GetInternal())
767 headerFmt = LogPrefsDialog::kHFInternal;
768
769 priChar = ((const char*)GetPriorityString(pLogMessage->GetPriority()))[0];
770
771 /*
772 * Get the current date/time in pretty form
773 *
774 * It's often useful when examining a log with "less" to jump to
775 * a specific point in the file by searching for the date/time stamp.
776 * For this reason it's very annoying to have regexp meta characters
777 * in the time stamp. Don't use forward slashes, parenthesis,
778 * brackets, asterisks, or other special chars here.
779 */
780 time_t when = pLogMessage->GetWhen();
781 const char* fmt = NULL;
782#if defined(HAVE_LOCALTIME_R)
783 ptm = localtime_r(&when, &tmBuf);
784#else
785 ptm = localtime(&when);
786#endif
787 switch (headerFmt) {
788 case LogPrefsDialog::kHFFull:
789 case LogPrefsDialog::kHFInternal:
790 fmt = "%m-%d %H:%M:%S";
791 break;
792 case LogPrefsDialog::kHFBrief:
793 case LogPrefsDialog::kHFMinimal:
794 fmt = "%H:%M:%S";
795 break;
796 default:
797 break;
798 }
799 if (fmt != NULL)
800 strftime(timeBuf, sizeof(timeBuf), fmt, ptm);
801 else
802 strcpy(timeBuf, "-");
803
804 const int kMaxExtraNewlines = 2;
805 char hdrNewline[2];
806 char finalNewlines[kMaxExtraNewlines+1 +1];
807
808 if (mSingleLine)
809 hdrNewline[0] = ' ';
810 else
811 hdrNewline[0] = '\n';
812 hdrNewline[1] = '\0';
813
814 assert(mExtraSpacing <= kMaxExtraNewlines);
815 int i;
816 for (i = 0; i < mExtraSpacing+1; i++)
817 finalNewlines[i] = '\n';
818 finalNewlines[i] = '\0';
819
820 wxTextAttr msgColor;
821 switch (pLogMessage->GetPriority()) {
822 case ANDROID_LOG_WARN:
823 msgColor.SetTextColour(*wxBLUE);
824 break;
825 case ANDROID_LOG_ERROR:
826 msgColor.SetTextColour(*wxRED);
827 break;
828 case ANDROID_LOG_VERBOSE:
829 case ANDROID_LOG_DEBUG:
830 case ANDROID_LOG_INFO:
831 default:
832 msgColor.SetTextColour(*wxBLACK);
833 break;
834 }
835 if (pLogMessage->GetInternal())
836 msgColor.SetTextColour(*wxGREEN);
837
838 /*
839 * Construct a buffer containing the log header.
840 */
841 bool splitHeader = true;
842 outBuf = msgBuf;
843 switch (headerFmt) {
844 case LogPrefsDialog::kHFFull:
845 splitHeader = true;
846 msgLen = android_snprintfBuffer(&outBuf, sizeof(msgBuf),
847 "[ %s %5d %c/%-6.6s]%s",
848 timeBuf, pLogMessage->GetPid(), priChar,
849 pLogMessage->GetTag(), hdrNewline);
850 break;
851 case LogPrefsDialog::kHFBrief:
852 splitHeader = true;
853 msgLen = android_snprintfBuffer(&outBuf, sizeof(msgBuf),
854 "[%s %5d]%s",
855 timeBuf, pLogMessage->GetPid(), hdrNewline);
856 break;
857 case LogPrefsDialog::kHFMinimal:
858 splitHeader = false;
859 msgLen = android_snprintfBuffer(&outBuf, sizeof(msgBuf),
860 "%s %5d- %s",
861 timeBuf, pLogMessage->GetPid(), pLogMessage->GetMsg());
862 break;
863 case LogPrefsDialog::kHFInternal:
864 splitHeader = false;
865 msgLen = android_snprintfBuffer(&outBuf, sizeof(msgBuf),
866 "[%s] %s", timeBuf, pLogMessage->GetMsg());
867 break;
868 default:
869 fprintf(stderr, "Sim: unexpected header format %d\n", headerFmt);
870 assert(false);
871 break;
872 }
873
874 if (msgLen < 0) {
875 fprintf(stderr, "WHOOPS\n");
876 assert(outBuf == msgBuf);
877 return;
878 }
879
880 if (splitHeader) {
881 if (mUseColor)
882 pTextCtrl->SetDefaultStyle(wxTextAttr(*wxLIGHT_GREY));
883 pTextCtrl->AppendText(wxString::FromAscii(outBuf));
884 if (mUseColor)
885 pTextCtrl->SetDefaultStyle(msgColor);
886 pTextCtrl->AppendText(wxString::FromAscii(pLogMessage->GetMsg()));
887 if (mUseColor)
888 pTextCtrl->SetDefaultStyle(*wxBLACK);
889 pTextCtrl->AppendText(wxString::FromAscii(finalNewlines));
890 } else {
891 if (mUseColor)
892 pTextCtrl->SetDefaultStyle(msgColor);
893 pTextCtrl->AppendText(wxString::FromAscii(outBuf));
894 if (mUseColor)
895 pTextCtrl->SetDefaultStyle(*wxBLACK);
896 pTextCtrl->AppendText(wxString::FromAscii(finalNewlines));
897 }
898
899 /* if we allocated storage for this message, free it */
900 if (outBuf != msgBuf)
901 free(outBuf);
902}
903
904/*
905 * Write the message to the log file.
906 *
907 * We can't just do this in FormatMessage(), because that re-writes all
908 * messages on the display whenever the output format or filter changes.
909 *
910 * Use a one-log-per-line format here to make "grep" useful.
911 */
912void LogWindow::LogToFile(const LogMessage* pLogMessage)
913{
914#if defined(HAVE_LOCALTIME_R)
915 struct tm tmBuf;
916#endif
917 struct tm* ptm;
918 char timeBuf[32];
919 char msgBuf[256];
920 int msgLen;
921 char* outBuf;
922 char priChar;
923
924 assert(mLogFp != NULL);
925
926 time_t when = pLogMessage->GetWhen();
927#if defined(HAVE_LOCALTIME_R)
928 ptm = localtime_r(&when, &tmBuf);
929#else
930 ptm = localtime(&when);
931#endif
932
933 strftime(timeBuf, sizeof(timeBuf), "%m-%d %H:%M:%S", ptm);
934 priChar = ((const char*)GetPriorityString(pLogMessage->GetPriority()))[0];
935
936 outBuf = msgBuf;
937 if (pLogMessage->GetInternal()) {
938 msgLen = android_snprintfBuffer(&outBuf, sizeof(msgBuf),
939 "[%s %5d *] %s\n",
940 timeBuf, pLogMessage->GetPid(), pLogMessage->GetMsg());
941 } else {
942 msgLen = android_snprintfBuffer(&outBuf, sizeof(msgBuf),
943 "[%s %5d %c] %s)\n",
944 timeBuf, pLogMessage->GetPid(), priChar,
945 pLogMessage->GetMsg());
946 }
947 if (fwrite(outBuf, msgLen, 1, mLogFp) != 1)
948 fprintf(stderr, "Sim: WARNING: partial log write\n");
949 fflush(mLogFp);
950
951 /* if we allocated storage for this message, free it */
952 if (outBuf != msgBuf)
953 free(outBuf);
954}
955
956/*
957 * Get the modification date of a file.
958 */
959static bool GetFileModDate(const char* fileName, time_t* pModWhen)
960{
961 struct stat sb;
962
963 if (stat(fileName, &sb) < 0)
964 return false;
965
966 *pModWhen = sb.st_mtime;
967 return true;
968}
969
970/*
971 * Open or close the log file as appropriate.
972 */
973void LogWindow::PrepareLogFile(void)
974{
975 const int kLogFileMaxAge = 8 * 60 * 60; // 8 hours
976
977 if (!mWriteFile && mLogFp != NULL) {
978 printf("Sim: closing log file\n");
979 fclose(mLogFp);
980 mLogFp = NULL;
981 } else if (mWriteFile && mLogFp == NULL) {
982 printf("Sim: opening log file '%s'\n", (const char*)mFileName.ToAscii());
983 time_t now, modWhen = 0;
984 const char* openFlags;
985
986 now = time(NULL);
987 if (!mTruncateOld ||
988 (GetFileModDate(mFileName.ToAscii(), &modWhen) &&
989 modWhen + kLogFileMaxAge > now))
990 {
991 if (modWhen != 0) {
992 printf("--- log file is %.3f hours old, appending\n",
993 (now - modWhen) / 3600.0);
994 }
995 openFlags = "a"; // open for append (text mode)
996 } else {
997 if (modWhen != 0) {
998 printf("--- log file is %.3f hours old, truncating\n",
999 (now - modWhen) / 3600.0);
1000 }
1001 openFlags = "w"; // open for writing, truncate (text mode)
1002 }
1003
1004 mLogFp = fopen(mFileName.ToAscii(), openFlags);
1005 if (mLogFp == NULL) {
1006 fprintf(stderr, "Sim: failed opening log file '%s': %s\n",
1007 (const char*) mFileName.ToAscii(), strerror(errno));
1008 } else {
1009 fprintf(mLogFp, "\n\n");
1010 fflush(mLogFp);
1011 }
1012 }
1013}
1014
1015/*
1016 * Add a new log message.
1017 *
1018 * This function can be called from any thread. It makes a copy of the
1019 * stuff in "*pBundle" and sends it to the main UI thread.
1020 */
1021/*static*/ void LogWindow::PostLogMsg(const android_LogBundle* pBundle)
1022{
1023 LogMessage* pNewMessage = LogMessage::Create(pBundle);
1024
1025 SendToWindow(pNewMessage);
1026}
1027
1028/*
1029 * Post a simple string to the log.
1030 */
1031/*static*/ void LogWindow::PostLogMsg(const char* msg)
1032{
1033 LogMessage* pNewMessage = LogMessage::Create(msg);
1034
1035 SendToWindow(pNewMessage);
1036}
1037
1038/*
1039 * Post a simple wxString to the log.
1040 */
1041/*static*/ void LogWindow::PostLogMsg(const wxString& msg)
1042{
1043 LogMessage* pNewMessage = LogMessage::Create(msg.ToAscii());
1044
1045 SendToWindow(pNewMessage);
1046}
1047
1048/*
1049 * Send a log message to the log window.
1050 */
1051/*static*/ void LogWindow::SendToWindow(LogMessage* pMessage)
1052{
1053 if (pMessage != NULL) {
1054 wxWindow* pMainFrame = ((MyApp*)wxTheApp)->GetMainFrame();
1055 UserEventMessage* pUem = new UserEventMessage;
1056 pUem->CreateLogMessage(pMessage);
1057
1058 UserEvent uev(0, (void*) pUem);
1059
1060 pMainFrame->AddPendingEvent(uev);
1061 } else {
1062 fprintf(stderr, "Sim: failed to add new log message\n");
1063 }
1064}
1065
1066
1067/*
1068 * This is a sanity check. We need to stop somewhere to avoid trashing
1069 * the system on bad input.
1070 */
1071#define kMaxLen 65536
1072
1073#define VSNPRINTF vsnprintf // used to worry about _vsnprintf
1074
1075
1076/*
1077 * Print a formatted message into a buffer. Pass in a buffer to try to use.
1078 *
1079 * If the buffer isn't big enough to hold the message, allocate storage
1080 * with malloc() and return that instead. The caller is responsible for
1081 * freeing the storage.
1082 *
1083 * Returns the length of the string, or -1 if the printf call failed.
1084 */
1085static int android_vsnprintfBuffer(char** pBuf, int bufLen, const char* format, va_list args)
1086{
1087 int charsOut;
1088 char* localBuf = NULL;
1089
1090 assert(pBuf != NULL && *pBuf != NULL);
1091 assert(bufLen > 0);
1092 assert(format != NULL);
1093
1094 while (1) {
1095 /*
1096 * In some versions of libc, vsnprintf only returns 0 or -1, where
1097 * -1 indicates the the buffer wasn't big enough. In glibc 2.1
1098 * and later, it returns the actual size needed.
1099 *
1100 * MinGW is just returning -1, so we have to retry there.
1101 */
1102 char* newBuf;
1103
1104 charsOut = VSNPRINTF(*pBuf, bufLen, format, args);
1105
1106 if (charsOut >= 0 && charsOut < bufLen)
1107 break;
1108
1109 //fprintf(stderr, "EXCEED: %d vs %d\n", charsOut, bufLen);
1110 if (charsOut < 0) {
1111 /* exact size not known, double previous size */
1112 bufLen *= 2;
1113 if (bufLen > kMaxLen)
1114 goto fail;
1115 } else {
1116 /* exact size known, just use that */
1117
1118 bufLen = charsOut + 1;
1119 }
1120 //fprintf(stderr, "RETRY at %d\n", bufLen);
1121
1122 newBuf = (char*) realloc(localBuf, bufLen);
1123 if (newBuf == NULL)
1124 goto fail;
1125 *pBuf = localBuf = newBuf;
1126 }
1127
1128 // On platforms where snprintf() doesn't return the number of
1129 // characters output, we would need to call strlen() here.
1130
1131 return charsOut;
1132
1133fail:
1134 if (localBuf != NULL) {
1135 free(localBuf);
1136 *pBuf = NULL;
1137 }
1138 return -1;
1139}
1140
1141/*
1142 * Variable-arg form of the above.
1143 */
1144static int android_snprintfBuffer(char** pBuf, int bufLen, const char* format, ...)
1145{
1146 va_list args;
1147 int result;
1148
1149 va_start(args, format);
1150 result = android_vsnprintfBuffer(pBuf, bufLen, format, args);
1151 va_end(args);
1152
1153 return result;
1154}
1155
1156