| // |
| // Copyright 2005 The Android Open Source Project |
| // |
| // Display runtime log output. |
| // |
| |
| // For compilers that support precompilation, include "wx/wx.h". |
| #include "wx/wxprec.h" |
| |
| // Otherwise, include all standard headers |
| #ifndef WX_PRECOMP |
| # include "wx/wx.h" |
| #endif |
| #include "wx/image.h" // needed for Windows build |
| #include "wx/dcbuffer.h" |
| |
| #include "LogWindow.h" |
| #include "LogMessage.h" |
| #include "LogPrefsDialog.h" |
| #include "MyApp.h" |
| #include "Preferences.h" |
| #include "Resource.h" |
| #include "UserEventMessage.h" |
| |
| #include <errno.h> |
| |
| static int android_snprintfBuffer(char** pBuf, int bufLen, const char* format, ...); |
| static int android_vsnprintfBuffer(char** pBuf, int bufLen, const char* format, va_list args); |
| |
| |
| using namespace android; |
| |
| #if 0 // experiment -- works on Win32, but not with GTK |
| class MyTextCtrl : public wxTextCtrl { |
| public: |
| MyTextCtrl(wxWindow* parent, wxWindowID id, const wxString& value, |
| const wxPoint& pos, const wxSize& size, int style = 0) |
| : wxTextCtrl(parent, id, value, pos, size, style) |
| { |
| printf("***************** MyTextCtrl!\n"); |
| } |
| |
| void OnScroll(wxScrollWinEvent& event); |
| void OnScrollBottom(wxScrollWinEvent& event); |
| |
| private: |
| DECLARE_EVENT_TABLE() |
| }; |
| |
| BEGIN_EVENT_TABLE(MyTextCtrl, wxTextCtrl) |
| EVT_SCROLLWIN(MyTextCtrl::OnScroll) |
| EVT_SCROLLWIN_BOTTOM(MyTextCtrl::OnScrollBottom) |
| END_EVENT_TABLE() |
| |
| void MyTextCtrl::OnScroll(wxScrollWinEvent& event) |
| { |
| printf("OnScroll!\n"); |
| } |
| |
| void MyTextCtrl::OnScrollBottom(wxScrollWinEvent& event) |
| { |
| printf("OnScrollBottom!\n"); |
| } |
| #endif |
| |
| |
| BEGIN_EVENT_TABLE(LogWindow, wxDialog) |
| EVT_CLOSE(LogWindow::OnClose) |
| EVT_MOVE(LogWindow::OnMove) |
| EVT_COMBOBOX(IDC_LOG_LEVEL, LogWindow::OnLogLevel) |
| EVT_BUTTON(IDC_LOG_CLEAR, LogWindow::OnLogClear) |
| EVT_BUTTON(IDC_LOG_PAUSE, LogWindow::OnLogPause) |
| EVT_BUTTON(IDC_LOG_PREFS, LogWindow::OnLogPrefs) |
| END_EVENT_TABLE() |
| |
| /* |
| * Information about log levels. |
| * |
| * Each entry here corresponds to an entry in the combo box. The first |
| * letter of each name should be unique. |
| */ |
| static const struct { |
| wxString name; |
| android_LogPriority priority; |
| } gLogLevels[] = { |
| { wxT("Verbose"), ANDROID_LOG_VERBOSE }, |
| { wxT("Debug"), ANDROID_LOG_DEBUG }, |
| { wxT("Info"), ANDROID_LOG_INFO }, |
| { wxT("Warn"), ANDROID_LOG_WARN }, |
| { wxT("Error"), ANDROID_LOG_ERROR } |
| }; |
| |
| |
| /* |
| * Create a new LogWindow. This should be a child of the main frame. |
| */ |
| LogWindow::LogWindow(wxWindow* parent) |
| : wxDialog(parent, wxID_ANY, wxT("Log Output"), wxDefaultPosition, |
| wxDefaultSize, |
| wxCAPTION | wxSYSTEM_MENU | wxCLOSE_BOX | wxRESIZE_BORDER), |
| mDisplayArray(NULL), mMaxDisplayMsgs(0), mPaused(false), |
| mMinPriority(ANDROID_LOG_VERBOSE), |
| mHeaderFormat(LogPrefsDialog::kHFFull), |
| mSingleLine(false), mExtraSpacing(0), mPointSize(10), mUseColor(true), |
| mFontMonospace(true), mWriteFile(false), mTruncateOld(true), mLogFp(NULL), |
| mNewlyShown(false), mLastPosition(wxDefaultPosition), mVisible(false) |
| { |
| ConstructControls(); |
| |
| Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs(); |
| |
| int poolSize = 10240; // 10MB |
| pPrefs->GetInt("log-pool-size-kbytes", &poolSize); |
| assert(poolSize > 0); |
| mPool.Resize(poolSize * 1024); |
| |
| mMaxDisplayMsgs = 1000; |
| pPrefs->GetInt("log-display-msg-count", &mMaxDisplayMsgs); |
| assert(mMaxDisplayMsgs > 0); |
| mDisplayArray = new LogMessage*[mMaxDisplayMsgs]; |
| memset(mDisplayArray, 0, sizeof(LogMessage*) * mMaxDisplayMsgs); |
| mTopPtr = -1; |
| mNextPtr = 0; |
| |
| int tmpInt = (int) mHeaderFormat; |
| pPrefs->GetInt("log-header-format", &tmpInt); |
| mHeaderFormat = (LogPrefsDialog::HeaderFormat) tmpInt; |
| pPrefs->GetBool("log-single-line", &mSingleLine); |
| pPrefs->GetInt("log-extra-spacing", &mExtraSpacing); |
| pPrefs->GetInt("log-point-size", &mPointSize); |
| pPrefs->GetBool("log-use-color", &mUseColor); |
| pPrefs->SetBool("log-font-monospace", &mFontMonospace); |
| SetTextStyle(); |
| |
| mFileName = wxT("/tmp/android-log.txt"); |
| pPrefs->GetBool("log-write-file", &mWriteFile); |
| pPrefs->GetString("log-filename", /*ref*/mFileName); |
| pPrefs->GetBool("log-truncate-old", &mTruncateOld); |
| |
| PrepareLogFile(); |
| } |
| |
| /* |
| * Destroy everything we own. |
| */ |
| LogWindow::~LogWindow(void) |
| { |
| ClearDisplay(); |
| delete[] mDisplayArray; |
| |
| if (mLogFp != NULL) |
| fclose(mLogFp); |
| } |
| |
| /* |
| * Set the text style, based on our preferences. |
| */ |
| void LogWindow::SetTextStyle(void) |
| { |
| wxTextCtrl* pTextCtrl; |
| pTextCtrl = (wxTextCtrl*) FindWindow(IDC_LOG_TEXT); |
| wxTextAttr style; |
| style = pTextCtrl->GetDefaultStyle(); |
| |
| if (mFontMonospace) { |
| wxFont font(mPointSize, wxFONTFAMILY_MODERN, wxFONTSTYLE_NORMAL, |
| wxFONTWEIGHT_NORMAL); |
| style.SetFont(font); |
| } else { |
| wxFont font(mPointSize, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, |
| wxFONTWEIGHT_NORMAL); |
| style.SetFont(font); |
| } |
| |
| pTextCtrl->SetDefaultStyle(style); |
| } |
| |
| /* |
| * Set up the goodies in the window. |
| * |
| * Also initializes mMinPriority. |
| */ |
| void LogWindow::ConstructControls(void) |
| { |
| Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs(); |
| wxPanel* base = new wxPanel(this, wxID_ANY); |
| wxBoxSizer* masterSizer = new wxBoxSizer(wxVERTICAL); |
| wxBoxSizer* indentSizer = new wxBoxSizer(wxHORIZONTAL); |
| wxBoxSizer* configPrioritySizer = new wxBoxSizer(wxHORIZONTAL); |
| wxGridSizer* configSizer = new wxGridSizer(4, 1); |
| |
| /* |
| * Configure log level combo box. |
| */ |
| wxComboBox* logLevel; |
| int defaultLogLevel = 1; |
| pPrefs->GetInt("log-display-level", &defaultLogLevel); |
| logLevel = new wxComboBox(base, IDC_LOG_LEVEL, wxT(""), |
| wxDefaultPosition, wxDefaultSize, 0, NULL, |
| wxCB_READONLY /*| wxSUNKEN_BORDER*/); |
| for (int i = 0; i < NELEM(gLogLevels); i++) { |
| logLevel->Append(gLogLevels[i].name); |
| logLevel->SetClientData(i, (void*) gLogLevels[i].priority); |
| } |
| logLevel->SetSelection(defaultLogLevel); |
| mMinPriority = gLogLevels[defaultLogLevel].priority; |
| |
| /* |
| * Set up stuff at the bottom, starting with the options |
| * at the bottom left. |
| */ |
| configPrioritySizer->Add(new wxStaticText(base, wxID_ANY, wxT("Log level:"), |
| wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT), |
| 0, wxALIGN_CENTER_VERTICAL); |
| configPrioritySizer->AddSpacer(kInterSpacing); |
| configPrioritySizer->Add(logLevel); |
| |
| wxButton* clear = new wxButton(base, IDC_LOG_CLEAR, wxT("&Clear"), |
| wxDefaultPosition, wxDefaultSize, 0); |
| wxButton* pause = new wxButton(base, IDC_LOG_PAUSE, wxT("&Pause"), |
| wxDefaultPosition, wxDefaultSize, 0); |
| wxButton* prefs = new wxButton(base, IDC_LOG_PREFS, wxT("C&onfigure"), |
| wxDefaultPosition, wxDefaultSize, 0); |
| |
| configSizer->Add(configPrioritySizer, 0, wxALIGN_LEFT); |
| configSizer->Add(clear, 0, wxALIGN_CENTER); |
| configSizer->Add(pause, 0, wxALIGN_CENTER); |
| configSizer->Add(prefs, 0, wxALIGN_RIGHT); |
| |
| /* |
| * Create text ctrl. |
| */ |
| wxTextCtrl* pTextCtrl; |
| pTextCtrl = new wxTextCtrl(base, IDC_LOG_TEXT, wxT(""), |
| wxDefaultPosition, wxDefaultSize, |
| wxTE_MULTILINE | wxTE_READONLY | wxTE_RICH2 | wxTE_NOHIDESEL | |
| wxHSCROLL); |
| |
| /* |
| * Add components to master sizer. |
| */ |
| masterSizer->AddSpacer(kEdgeSpacing); |
| masterSizer->Add(pTextCtrl, 1, wxEXPAND); |
| masterSizer->AddSpacer(kInterSpacing); |
| masterSizer->Add(configSizer, 0, wxEXPAND); |
| masterSizer->AddSpacer(kEdgeSpacing); |
| |
| /* |
| * Indent from sides. |
| */ |
| indentSizer->AddSpacer(kEdgeSpacing); |
| indentSizer->Add(masterSizer, 1, wxEXPAND); |
| indentSizer->AddSpacer(kEdgeSpacing); |
| |
| base->SetSizer(indentSizer); |
| |
| indentSizer->Fit(this); // shrink-to-fit |
| indentSizer->SetSizeHints(this); // define minimum size |
| } |
| |
| /* |
| * In some cases, this means the user has clicked on our "close" button. |
| * We don't really even want one, but both WinXP and KDE put one on our |
| * window whether we want it or not. So, we make it work as a "hide" |
| * button instead. |
| * |
| * This also gets called when the app is shutting down, and we do want |
| * to destroy ourselves then, saving various information about our state. |
| */ |
| void LogWindow::OnClose(wxCloseEvent& event) |
| { |
| /* just hide the window, unless we're shutting down */ |
| if (event.CanVeto()) { |
| event.Veto(); |
| Show(false); |
| return; |
| } |
| |
| /* |
| * Save some preferences. |
| */ |
| SaveWindowPrefs(); |
| |
| /* if we can't veto the Close(), destroy ourselves */ |
| Destroy(); |
| } |
| |
| /* |
| * Save all of our preferences to the config file. |
| */ |
| void LogWindow::SaveWindowPrefs(void) |
| { |
| Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs(); |
| |
| /* |
| * Save shown/hidden state. |
| */ |
| pPrefs->SetBool("window-log-show", IsShown()); |
| |
| /* |
| * Limits and formatting prefs. |
| */ |
| pPrefs->SetInt("log-display-msg-count", mMaxDisplayMsgs); |
| pPrefs->SetInt("log-pool-size-kbytes", mPool.GetMaxSize() / 1024); |
| |
| pPrefs->SetInt("log-header-format", mHeaderFormat); |
| pPrefs->SetBool("log-single-line", mSingleLine); |
| pPrefs->SetInt("log-extra-spacing", mExtraSpacing); |
| pPrefs->SetInt("log-point-size", mPointSize); |
| pPrefs->SetBool("log-use-color", mUseColor); |
| pPrefs->SetBool("log-font-monospace", mFontMonospace); |
| |
| pPrefs->SetBool("log-write-file", mWriteFile); |
| pPrefs->SetString("log-filename", mFileName.ToAscii()); |
| pPrefs->SetBool("log-truncate-old", mTruncateOld); |
| |
| /* |
| * Save window size and position. |
| */ |
| wxPoint posn; |
| wxSize size; |
| |
| assert(pPrefs != NULL); |
| |
| posn = GetPosition(); |
| size = GetSize(); |
| |
| pPrefs->SetInt("window-log-x", posn.x); |
| pPrefs->SetInt("window-log-y", posn.y); |
| pPrefs->SetInt("window-log-width", size.GetWidth()); |
| pPrefs->SetInt("window-log-height", size.GetHeight()); |
| |
| /* |
| * Save current setting of debug level combo box. |
| */ |
| wxComboBox* pCombo; |
| int selection; |
| pCombo = (wxComboBox*) FindWindow(IDC_LOG_LEVEL); |
| selection = pCombo->GetSelection(); |
| pPrefs->SetInt("log-display-level", selection); |
| } |
| |
| /* |
| * Return the desired position and size. |
| */ |
| /*static*/ wxRect LogWindow::GetPrefWindowRect(void) |
| { |
| Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs(); |
| int x, y, width, height; |
| |
| assert(pPrefs != NULL); |
| |
| x = y = 10; |
| width = 500; |
| height = 200; |
| |
| /* these don't modify the arg if the pref doesn't exist */ |
| pPrefs->GetInt("window-log-x", &x); |
| pPrefs->GetInt("window-log-y", &y); |
| pPrefs->GetInt("window-log-width", &width); |
| pPrefs->GetInt("window-log-height", &height); |
| |
| return wxRect(x, y, width, height); |
| } |
| |
| /* |
| * Under Linux+GTK, the first time you show the window, it appears where |
| * it's supposed to. If you then hide it and show it again, it gets |
| * moved on top of the parent window. After that, you can reposition it |
| * and it remembers its position across hide/show. |
| * |
| * To counter this annoyance, we save the position when we hide, and |
| * reset the position after a show. The "newly shown" flag ensures that |
| * we only reposition the window as the result of a Show(true) call. |
| * |
| * Sometimes, something helpful will shift the window over if it's |
| * partially straddling a seam between two monitors. I don't see an easy |
| * way to block this, and I'm not sure I want to anyway. |
| */ |
| void LogWindow::OnMove(wxMoveEvent& event) |
| { |
| wxPoint point; |
| point = event.GetPosition(); |
| //printf("Sim: log window is at (%d,%d) (new=%d)\n", point.x, point.y, |
| // mNewlyShown); |
| |
| if (mNewlyShown) { |
| if (mLastPosition == wxDefaultPosition) { |
| //printf("Sim: no last position established\n"); |
| } else { |
| Move(mLastPosition); |
| } |
| |
| mNewlyShown = false; |
| } |
| } |
| |
| /* |
| * Set the "newly shown" flag. |
| */ |
| bool LogWindow::Show(bool show) |
| { |
| if (show) { |
| mNewlyShown = true; |
| Redisplay(); |
| } else { |
| mLastPosition = GetPosition(); |
| } |
| |
| mVisible = show; |
| return wxDialog::Show(show); |
| } |
| |
| /* |
| * User has adjusted the log level. Update the display appropriately. |
| * |
| * This is a wxEVT_COMMAND_COMBOBOX_SELECTED event. |
| */ |
| void LogWindow::OnLogLevel(wxCommandEvent& event) |
| { |
| int selection; |
| android_LogPriority priority; |
| |
| selection = event.GetInt(); |
| wxComboBox* pCombo = (wxComboBox*) FindWindow(IDC_LOG_LEVEL); |
| priority = (android_LogPriority) (long)pCombo->GetClientData(event.GetInt()); |
| |
| printf("Sim: log level selected: %d (%s)\n", (int) priority, |
| (const char*) gLogLevels[selection].name.ToAscii()); |
| mMinPriority = priority; |
| Redisplay(); |
| } |
| |
| /* |
| * Clear out the log. |
| */ |
| void LogWindow::OnLogClear(wxCommandEvent& event) |
| { |
| ClearDisplay(); |
| mPool.Clear(); |
| } |
| |
| /* |
| * Handle the pause/resume button. |
| * |
| * If we're un-pausing, we need to get caught up. |
| */ |
| void LogWindow::OnLogPause(wxCommandEvent& event) |
| { |
| mPaused = !mPaused; |
| |
| wxButton* pButton = (wxButton*) FindWindow(IDC_LOG_PAUSE); |
| if (mPaused) { |
| pButton->SetLabel(wxT("&Resume")); |
| |
| mPool.SetBookmark(); |
| } else { |
| pButton->SetLabel(wxT("&Pause")); |
| |
| LogMessage* pMsg = mPool.GetBookmark(); |
| if (pMsg == NULL) { |
| /* bookmarked item fell out of pool */ |
| printf("--- bookmark was lost, redisplaying\n"); |
| Redisplay(); |
| } else { |
| /* |
| * The bookmark points to the last item added to the display. |
| * We want to chase its "prev" pointer to walk toward the head |
| * of the list, adding items from oldest to newest. |
| */ |
| pMsg = pMsg->GetPrev(); |
| while (pMsg != NULL) { |
| if (FilterMatches(pMsg)) |
| AddToDisplay(pMsg); |
| pMsg = pMsg->GetPrev(); |
| } |
| } |
| } |
| } |
| |
| /* |
| * Open log preferences dialog. |
| */ |
| void LogWindow::OnLogPrefs(wxCommandEvent& event) |
| { |
| LogPrefsDialog dialog(this); |
| |
| /* |
| * Set up the dialog. |
| */ |
| dialog.mHeaderFormat = mHeaderFormat; |
| dialog.mSingleLine = mSingleLine; |
| dialog.mExtraSpacing = mExtraSpacing; |
| dialog.mPointSize = mPointSize; |
| dialog.mUseColor = mUseColor; |
| dialog.mFontMonospace = mFontMonospace; |
| |
| dialog.mDisplayMax = mMaxDisplayMsgs; |
| dialog.mPoolSizeKB = mPool.GetMaxSize() / 1024; |
| |
| dialog.mWriteFile = mWriteFile; |
| dialog.mFileName = mFileName; |
| dialog.mTruncateOld = mTruncateOld; |
| |
| /* |
| * Show it. If they hit "OK", copy the updated values out, and |
| * re-display the log output. |
| */ |
| if (dialog.ShowModal() == wxID_OK) { |
| /* discard old display arra */ |
| ClearDisplay(); |
| delete[] mDisplayArray; |
| |
| mHeaderFormat = dialog.mHeaderFormat; |
| mSingleLine = dialog.mSingleLine; |
| mExtraSpacing = dialog.mExtraSpacing; |
| mPointSize = dialog.mPointSize; |
| mUseColor = dialog.mUseColor; |
| mFontMonospace = dialog.mFontMonospace; |
| |
| assert(dialog.mDisplayMax > 0); |
| assert(dialog.mPoolSizeKB > 0); |
| mMaxDisplayMsgs = dialog.mDisplayMax; |
| mPool.Resize(dialog.mPoolSizeKB * 1024); |
| |
| mWriteFile = dialog.mWriteFile; |
| if (mLogFp != NULL && mFileName != dialog.mFileName) { |
| printf("--- log file name changed, closing\n"); |
| fclose(mLogFp); |
| mLogFp = NULL; |
| } |
| mFileName = dialog.mFileName; |
| mTruncateOld = dialog.mTruncateOld; |
| |
| mDisplayArray = new LogMessage*[mMaxDisplayMsgs]; |
| memset(mDisplayArray, 0, sizeof(LogMessage*) * mMaxDisplayMsgs); |
| Redisplay(); |
| |
| PrepareLogFile(); |
| } |
| } |
| |
| /* |
| * Handle a log message "user event". This should only be called in |
| * the main UI thread. |
| * |
| * We take ownership of "*pLogMessage". |
| */ |
| void LogWindow::AddLogMessage(LogMessage* pLogMessage) |
| { |
| mPool.Add(pLogMessage); |
| |
| if (!mPaused && mVisible && FilterMatches(pLogMessage)) { |
| /* |
| * Thought: keep a reference to the previous message. If it |
| * matches in most fields (all except timestamp?), hold it and |
| * increment a counter. If we get a message that doesn't match, |
| * or a timer elapses, synthesize a "previous message repeated N |
| * times" string. |
| */ |
| AddToDisplay(pLogMessage); |
| } |
| |
| // release the initial ref caused by allocation |
| pLogMessage->Release(); |
| |
| if (mLogFp != NULL) |
| LogToFile(pLogMessage); |
| } |
| |
| /* |
| * Clear out the display, releasing any log messages held in the display |
| * array. |
| */ |
| void LogWindow::ClearDisplay(void) |
| { |
| wxTextCtrl* pTextCtrl; |
| pTextCtrl = (wxTextCtrl*) FindWindow(IDC_LOG_TEXT); |
| pTextCtrl->Clear(); |
| |
| /* |
| * Just run through the entire array. |
| */ |
| for (int i = 0; i < mMaxDisplayMsgs; i++) { |
| if (mDisplayArray[i] != NULL) { |
| mDisplayArray[i]->Release(); |
| mDisplayArray[i] = NULL; |
| } |
| } |
| mTopPtr = -1; |
| mNextPtr = 0; |
| } |
| |
| /* |
| * Clear the current display and regenerate it from the log pool. We need |
| * to do this whenever we change filters or log message formatting. |
| */ |
| void LogWindow::Redisplay(void) |
| { |
| /* |
| * Freeze output rendering so it doesn't flash during update. Doesn't |
| * seem to help for GTK, and it leaves garbage on the screen in WinXP, |
| * so I'm leaving it commented out. |
| */ |
| //wxTextCtrl* pText = (wxTextCtrl*) FindWindow(IDC_LOG_TEXT); |
| //pText->Freeze(); |
| |
| //printf("--- redisplay\n"); |
| ClearDisplay(); |
| |
| /* |
| * Set up the default wxWidgets text style stuff. |
| */ |
| SetTextStyle(); |
| |
| /* |
| * Here's the plan: |
| * - Start at the head of the pool (where the most recently added |
| * items are). |
| * - Check to see if the current item passes our filter. If it does, |
| * increment the "found count". |
| * - Continue in this manner until we run out of pool or have |
| * sufficient items to fill the screen. |
| * - Starting from the current position, walk back toward the head, |
| * adding the items that meet the current filter criteria. |
| * |
| * Don't forget that the log pool could be empty. |
| */ |
| LogMessage* pMsg = mPool.GetHead(); |
| |
| if (pMsg != NULL) { |
| int foundCount = 0; |
| |
| // note this stops before it runs off the end |
| while (pMsg->GetNext() != NULL && foundCount < mMaxDisplayMsgs) { |
| if (FilterMatches(pMsg)) |
| foundCount++; |
| pMsg = pMsg->GetNext(); |
| } |
| |
| while (pMsg != NULL) { |
| if (FilterMatches(pMsg)) |
| AddToDisplay(pMsg); |
| pMsg = pMsg->GetPrev(); |
| } |
| } |
| |
| //pText->Thaw(); |
| } |
| |
| |
| /* |
| * Returns "true" if the currently specified filters would allow this |
| * message to be shown. |
| */ |
| bool LogWindow::FilterMatches(const LogMessage* pLogMessage) |
| { |
| if (pLogMessage->GetPriority() >= mMinPriority) |
| return true; |
| else |
| return false; |
| } |
| |
| /* |
| * Realloc the array of pointers, and remove anything from the display |
| * that should no longer be there. |
| */ |
| void LogWindow::SetMaxDisplayMsgs(int max) |
| { |
| Preferences* pPrefs = ((MyApp*)wxTheApp)->GetPrefs(); |
| |
| pPrefs->SetInt("log-display-msg-count", max); |
| } |
| |
| /* |
| * Add the message to the display array and to the screen. |
| */ |
| void LogWindow::AddToDisplay(LogMessage* pLogMessage) |
| { |
| wxTextCtrl* pTextCtrl; |
| pTextCtrl = (wxTextCtrl*) FindWindow(IDC_LOG_TEXT); |
| |
| if (mNextPtr == mTopPtr) { |
| /* |
| * The display array is full. |
| * |
| * We need to eliminate the topmost entry. This requires removing |
| * it from the array and removing the text from the wxTextCtrl. |
| */ |
| pTextCtrl->Remove(0, mDisplayArray[mTopPtr]->GetTextCtrlLen()); |
| mDisplayArray[mTopPtr]->Release(); |
| mTopPtr = (mTopPtr + 1) % mMaxDisplayMsgs; |
| } |
| |
| /* |
| * Add formatted text to the text ctrl. Track how much actual space |
| * is required. The space may be different on Win32 (CRLF-based) vs. |
| * GTK (LF-based), so we need to measure it, not compute it from the |
| * text string. |
| */ |
| long lastBefore, lastAfter; |
| //long insertBefore; |
| //insertBefore = pTextCtrl->GetInsertionPoint(); |
| lastBefore = pTextCtrl->GetLastPosition(); |
| FormatMessage(pLogMessage, pTextCtrl); |
| lastAfter = pTextCtrl->GetLastPosition(); |
| pLogMessage->SetTextCtrlLen(lastAfter - lastBefore); |
| |
| /* |
| * If we restore the old insertion point, we will be glued to where |
| * we were. This is okay until we start deleting text from the top, |
| * at which point we need to adjust it to retain our position. |
| * |
| * If we set the insertion point to the bottom, we effectively |
| * implement "scroll to bottom on output". |
| * |
| * If we don't set it at all, we get slightly strange behavior out |
| * of GTK, which seems to be par for the course here. |
| */ |
| //pTextCtrl->SetInsertionPoint(insertBefore); // restore insertion pt |
| pTextCtrl->SetInsertionPoint(lastAfter); |
| |
| /* add it to array, claim ownership */ |
| mDisplayArray[mNextPtr] = pLogMessage; |
| pLogMessage->Acquire(); |
| |
| /* adjust pointers */ |
| if (mTopPtr < 0) // first time only |
| mTopPtr = 0; |
| mNextPtr = (mNextPtr + 1) % mMaxDisplayMsgs; |
| } |
| |
| |
| /* |
| * Return a human-readable string for the priority level. Always returns |
| * a valid string. |
| */ |
| static const wxCharBuffer GetPriorityString(android_LogPriority priority) |
| { |
| int idx; |
| |
| idx = (int) priority - (int) ANDROID_LOG_VERBOSE; |
| if (idx < 0 || idx >= NELEM(gLogLevels)) |
| return "?unknown?"; |
| return gLogLevels[idx].name.ToAscii(); |
| } |
| |
| /* |
| * Format a message and write it to the text control. |
| */ |
| void LogWindow::FormatMessage(const LogMessage* pLogMessage, |
| wxTextCtrl* pTextCtrl) |
| { |
| #if defined(HAVE_LOCALTIME_R) |
| struct tm tmBuf; |
| #endif |
| struct tm* ptm; |
| char timeBuf[32]; |
| char msgBuf[256]; |
| int msgLen = 0; |
| char* outBuf; |
| char priChar; |
| LogPrefsDialog::HeaderFormat headerFmt; |
| |
| headerFmt = mHeaderFormat; |
| if (pLogMessage->GetInternal()) |
| headerFmt = LogPrefsDialog::kHFInternal; |
| |
| priChar = ((const char*)GetPriorityString(pLogMessage->GetPriority()))[0]; |
| |
| /* |
| * Get the current date/time in pretty form |
| * |
| * It's often useful when examining a log with "less" to jump to |
| * a specific point in the file by searching for the date/time stamp. |
| * For this reason it's very annoying to have regexp meta characters |
| * in the time stamp. Don't use forward slashes, parenthesis, |
| * brackets, asterisks, or other special chars here. |
| */ |
| time_t when = pLogMessage->GetWhen(); |
| const char* fmt = NULL; |
| #if defined(HAVE_LOCALTIME_R) |
| ptm = localtime_r(&when, &tmBuf); |
| #else |
| ptm = localtime(&when); |
| #endif |
| switch (headerFmt) { |
| case LogPrefsDialog::kHFFull: |
| case LogPrefsDialog::kHFInternal: |
| fmt = "%m-%d %H:%M:%S"; |
| break; |
| case LogPrefsDialog::kHFBrief: |
| case LogPrefsDialog::kHFMinimal: |
| fmt = "%H:%M:%S"; |
| break; |
| default: |
| break; |
| } |
| if (fmt != NULL) |
| strftime(timeBuf, sizeof(timeBuf), fmt, ptm); |
| else |
| strcpy(timeBuf, "-"); |
| |
| const int kMaxExtraNewlines = 2; |
| char hdrNewline[2]; |
| char finalNewlines[kMaxExtraNewlines+1 +1]; |
| |
| if (mSingleLine) |
| hdrNewline[0] = ' '; |
| else |
| hdrNewline[0] = '\n'; |
| hdrNewline[1] = '\0'; |
| |
| assert(mExtraSpacing <= kMaxExtraNewlines); |
| int i; |
| for (i = 0; i < mExtraSpacing+1; i++) |
| finalNewlines[i] = '\n'; |
| finalNewlines[i] = '\0'; |
| |
| wxTextAttr msgColor; |
| switch (pLogMessage->GetPriority()) { |
| case ANDROID_LOG_WARN: |
| msgColor.SetTextColour(*wxBLUE); |
| break; |
| case ANDROID_LOG_ERROR: |
| msgColor.SetTextColour(*wxRED); |
| break; |
| case ANDROID_LOG_VERBOSE: |
| case ANDROID_LOG_DEBUG: |
| case ANDROID_LOG_INFO: |
| default: |
| msgColor.SetTextColour(*wxBLACK); |
| break; |
| } |
| if (pLogMessage->GetInternal()) |
| msgColor.SetTextColour(*wxGREEN); |
| |
| /* |
| * Construct a buffer containing the log header. |
| */ |
| bool splitHeader = true; |
| outBuf = msgBuf; |
| switch (headerFmt) { |
| case LogPrefsDialog::kHFFull: |
| splitHeader = true; |
| msgLen = android_snprintfBuffer(&outBuf, sizeof(msgBuf), |
| "[ %s %5d %c/%-6.6s]%s", |
| timeBuf, pLogMessage->GetPid(), priChar, |
| pLogMessage->GetTag(), hdrNewline); |
| break; |
| case LogPrefsDialog::kHFBrief: |
| splitHeader = true; |
| msgLen = android_snprintfBuffer(&outBuf, sizeof(msgBuf), |
| "[%s %5d]%s", |
| timeBuf, pLogMessage->GetPid(), hdrNewline); |
| break; |
| case LogPrefsDialog::kHFMinimal: |
| splitHeader = false; |
| msgLen = android_snprintfBuffer(&outBuf, sizeof(msgBuf), |
| "%s %5d- %s", |
| timeBuf, pLogMessage->GetPid(), pLogMessage->GetMsg()); |
| break; |
| case LogPrefsDialog::kHFInternal: |
| splitHeader = false; |
| msgLen = android_snprintfBuffer(&outBuf, sizeof(msgBuf), |
| "[%s] %s", timeBuf, pLogMessage->GetMsg()); |
| break; |
| default: |
| fprintf(stderr, "Sim: unexpected header format %d\n", headerFmt); |
| assert(false); |
| break; |
| } |
| |
| if (msgLen < 0) { |
| fprintf(stderr, "WHOOPS\n"); |
| assert(outBuf == msgBuf); |
| return; |
| } |
| |
| if (splitHeader) { |
| if (mUseColor) |
| pTextCtrl->SetDefaultStyle(wxTextAttr(*wxLIGHT_GREY)); |
| pTextCtrl->AppendText(wxString::FromAscii(outBuf)); |
| if (mUseColor) |
| pTextCtrl->SetDefaultStyle(msgColor); |
| pTextCtrl->AppendText(wxString::FromAscii(pLogMessage->GetMsg())); |
| if (mUseColor) |
| pTextCtrl->SetDefaultStyle(*wxBLACK); |
| pTextCtrl->AppendText(wxString::FromAscii(finalNewlines)); |
| } else { |
| if (mUseColor) |
| pTextCtrl->SetDefaultStyle(msgColor); |
| pTextCtrl->AppendText(wxString::FromAscii(outBuf)); |
| if (mUseColor) |
| pTextCtrl->SetDefaultStyle(*wxBLACK); |
| pTextCtrl->AppendText(wxString::FromAscii(finalNewlines)); |
| } |
| |
| /* if we allocated storage for this message, free it */ |
| if (outBuf != msgBuf) |
| free(outBuf); |
| } |
| |
| /* |
| * Write the message to the log file. |
| * |
| * We can't just do this in FormatMessage(), because that re-writes all |
| * messages on the display whenever the output format or filter changes. |
| * |
| * Use a one-log-per-line format here to make "grep" useful. |
| */ |
| void LogWindow::LogToFile(const LogMessage* pLogMessage) |
| { |
| #if defined(HAVE_LOCALTIME_R) |
| struct tm tmBuf; |
| #endif |
| struct tm* ptm; |
| char timeBuf[32]; |
| char msgBuf[256]; |
| int msgLen; |
| char* outBuf; |
| char priChar; |
| |
| assert(mLogFp != NULL); |
| |
| time_t when = pLogMessage->GetWhen(); |
| #if defined(HAVE_LOCALTIME_R) |
| ptm = localtime_r(&when, &tmBuf); |
| #else |
| ptm = localtime(&when); |
| #endif |
| |
| strftime(timeBuf, sizeof(timeBuf), "%m-%d %H:%M:%S", ptm); |
| priChar = ((const char*)GetPriorityString(pLogMessage->GetPriority()))[0]; |
| |
| outBuf = msgBuf; |
| if (pLogMessage->GetInternal()) { |
| msgLen = android_snprintfBuffer(&outBuf, sizeof(msgBuf), |
| "[%s %5d *] %s\n", |
| timeBuf, pLogMessage->GetPid(), pLogMessage->GetMsg()); |
| } else { |
| msgLen = android_snprintfBuffer(&outBuf, sizeof(msgBuf), |
| "[%s %5d %c] %s)\n", |
| timeBuf, pLogMessage->GetPid(), priChar, |
| pLogMessage->GetMsg()); |
| } |
| if (fwrite(outBuf, msgLen, 1, mLogFp) != 1) |
| fprintf(stderr, "Sim: WARNING: partial log write\n"); |
| fflush(mLogFp); |
| |
| /* if we allocated storage for this message, free it */ |
| if (outBuf != msgBuf) |
| free(outBuf); |
| } |
| |
| /* |
| * Get the modification date of a file. |
| */ |
| static bool GetFileModDate(const char* fileName, time_t* pModWhen) |
| { |
| struct stat sb; |
| |
| if (stat(fileName, &sb) < 0) |
| return false; |
| |
| *pModWhen = sb.st_mtime; |
| return true; |
| } |
| |
| /* |
| * Open or close the log file as appropriate. |
| */ |
| void LogWindow::PrepareLogFile(void) |
| { |
| const int kLogFileMaxAge = 8 * 60 * 60; // 8 hours |
| |
| if (!mWriteFile && mLogFp != NULL) { |
| printf("Sim: closing log file\n"); |
| fclose(mLogFp); |
| mLogFp = NULL; |
| } else if (mWriteFile && mLogFp == NULL) { |
| printf("Sim: opening log file '%s'\n", (const char*)mFileName.ToAscii()); |
| time_t now, modWhen = 0; |
| const char* openFlags; |
| |
| now = time(NULL); |
| if (!mTruncateOld || |
| (GetFileModDate(mFileName.ToAscii(), &modWhen) && |
| modWhen + kLogFileMaxAge > now)) |
| { |
| if (modWhen != 0) { |
| printf("--- log file is %.3f hours old, appending\n", |
| (now - modWhen) / 3600.0); |
| } |
| openFlags = "a"; // open for append (text mode) |
| } else { |
| if (modWhen != 0) { |
| printf("--- log file is %.3f hours old, truncating\n", |
| (now - modWhen) / 3600.0); |
| } |
| openFlags = "w"; // open for writing, truncate (text mode) |
| } |
| |
| mLogFp = fopen(mFileName.ToAscii(), openFlags); |
| if (mLogFp == NULL) { |
| fprintf(stderr, "Sim: failed opening log file '%s': %s\n", |
| (const char*) mFileName.ToAscii(), strerror(errno)); |
| } else { |
| fprintf(mLogFp, "\n\n"); |
| fflush(mLogFp); |
| } |
| } |
| } |
| |
| /* |
| * Add a new log message. |
| * |
| * This function can be called from any thread. It makes a copy of the |
| * stuff in "*pBundle" and sends it to the main UI thread. |
| */ |
| /*static*/ void LogWindow::PostLogMsg(const android_LogBundle* pBundle) |
| { |
| LogMessage* pNewMessage = LogMessage::Create(pBundle); |
| |
| SendToWindow(pNewMessage); |
| } |
| |
| /* |
| * Post a simple string to the log. |
| */ |
| /*static*/ void LogWindow::PostLogMsg(const char* msg) |
| { |
| LogMessage* pNewMessage = LogMessage::Create(msg); |
| |
| SendToWindow(pNewMessage); |
| } |
| |
| /* |
| * Post a simple wxString to the log. |
| */ |
| /*static*/ void LogWindow::PostLogMsg(const wxString& msg) |
| { |
| LogMessage* pNewMessage = LogMessage::Create(msg.ToAscii()); |
| |
| SendToWindow(pNewMessage); |
| } |
| |
| /* |
| * Send a log message to the log window. |
| */ |
| /*static*/ void LogWindow::SendToWindow(LogMessage* pMessage) |
| { |
| if (pMessage != NULL) { |
| wxWindow* pMainFrame = ((MyApp*)wxTheApp)->GetMainFrame(); |
| UserEventMessage* pUem = new UserEventMessage; |
| pUem->CreateLogMessage(pMessage); |
| |
| UserEvent uev(0, (void*) pUem); |
| |
| pMainFrame->AddPendingEvent(uev); |
| } else { |
| fprintf(stderr, "Sim: failed to add new log message\n"); |
| } |
| } |
| |
| |
| /* |
| * This is a sanity check. We need to stop somewhere to avoid trashing |
| * the system on bad input. |
| */ |
| #define kMaxLen 65536 |
| |
| #define VSNPRINTF vsnprintf // used to worry about _vsnprintf |
| |
| |
| /* |
| * Print a formatted message into a buffer. Pass in a buffer to try to use. |
| * |
| * If the buffer isn't big enough to hold the message, allocate storage |
| * with malloc() and return that instead. The caller is responsible for |
| * freeing the storage. |
| * |
| * Returns the length of the string, or -1 if the printf call failed. |
| */ |
| static int android_vsnprintfBuffer(char** pBuf, int bufLen, const char* format, va_list args) |
| { |
| int charsOut; |
| char* localBuf = NULL; |
| |
| assert(pBuf != NULL && *pBuf != NULL); |
| assert(bufLen > 0); |
| assert(format != NULL); |
| |
| while (1) { |
| /* |
| * In some versions of libc, vsnprintf only returns 0 or -1, where |
| * -1 indicates the the buffer wasn't big enough. In glibc 2.1 |
| * and later, it returns the actual size needed. |
| * |
| * MinGW is just returning -1, so we have to retry there. |
| */ |
| char* newBuf; |
| |
| charsOut = VSNPRINTF(*pBuf, bufLen, format, args); |
| |
| if (charsOut >= 0 && charsOut < bufLen) |
| break; |
| |
| //fprintf(stderr, "EXCEED: %d vs %d\n", charsOut, bufLen); |
| if (charsOut < 0) { |
| /* exact size not known, double previous size */ |
| bufLen *= 2; |
| if (bufLen > kMaxLen) |
| goto fail; |
| } else { |
| /* exact size known, just use that */ |
| |
| bufLen = charsOut + 1; |
| } |
| //fprintf(stderr, "RETRY at %d\n", bufLen); |
| |
| newBuf = (char*) realloc(localBuf, bufLen); |
| if (newBuf == NULL) |
| goto fail; |
| *pBuf = localBuf = newBuf; |
| } |
| |
| // On platforms where snprintf() doesn't return the number of |
| // characters output, we would need to call strlen() here. |
| |
| return charsOut; |
| |
| fail: |
| if (localBuf != NULL) { |
| free(localBuf); |
| *pBuf = NULL; |
| } |
| return -1; |
| } |
| |
| /* |
| * Variable-arg form of the above. |
| */ |
| static int android_snprintfBuffer(char** pBuf, int bufLen, const char* format, ...) |
| { |
| va_list args; |
| int result; |
| |
| va_start(args, format); |
| result = android_vsnprintfBuffer(pBuf, bufLen, format, args); |
| va_end(args); |
| |
| return result; |
| } |
| |
| |