mirror of https://github.com/BOINC/boinc.git
989 lines
34 KiB
C++
Executable File
989 lines
34 KiB
C++
Executable File
// This file is part of BOINC.
|
|
// http://boinc.berkeley.edu
|
|
// Copyright (C) 2008 University of California
|
|
//
|
|
// BOINC is free software; you can redistribute it and/or modify it
|
|
// under the terms of the GNU Lesser General Public License
|
|
// as published by the Free Software Foundation,
|
|
// either version 3 of the License, or (at your option) any later version.
|
|
//
|
|
// BOINC is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
// See the GNU Lesser General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Lesser General Public License
|
|
// along with BOINC. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
#define TESTBIGICONPOPUP 0
|
|
|
|
#define SORTTASKLIST 1 /* TRUE to sort task selection control alphabetically */
|
|
|
|
#include "stdwx.h"
|
|
#include "miofile.h"
|
|
#include "Events.h"
|
|
#include "BOINCGUIApp.h"
|
|
#include "SkinManager.h"
|
|
#include "MainDocument.h"
|
|
#include "sg_TaskCommandPopup.h"
|
|
#include "sg_TaskPanel.h"
|
|
#include "boinc_api.h"
|
|
#include "filesys.h"
|
|
|
|
|
|
enum { redDot, yellowDot, greenDot };
|
|
|
|
|
|
IMPLEMENT_DYNAMIC_CLASS(CSlideShowPanel, wxPanel)
|
|
|
|
BEGIN_EVENT_TABLE(CSlideShowPanel, wxPanel)
|
|
// EVT_ERASE_BACKGROUND(CSlideShowPanel::OnEraseBackground)
|
|
EVT_TIMER(ID_CHANGE_SLIDE_TIMER, CSlideShowPanel::OnSlideShowTimer)
|
|
EVT_PAINT(CSlideShowPanel::OnPaint)
|
|
END_EVENT_TABLE()
|
|
|
|
#define SLIDESHOWBORDER 3
|
|
|
|
CSlideShowPanel::CSlideShowPanel() {
|
|
}
|
|
|
|
|
|
CSlideShowPanel::CSlideShowPanel( wxWindow* parent ) :
|
|
wxPanel( parent, wxID_ANY, wxDefaultPosition, wxSize(290+(2*SLIDESHOWBORDER), 126+(2*SLIDESHOWBORDER)), wxSTATIC_BORDER )
|
|
{
|
|
SetBackgroundColour(*wxBLACK);
|
|
|
|
m_ChangeSlideTimer = new wxTimer(this, ID_CHANGE_SLIDE_TIMER);
|
|
m_ChangeSlideTimer->Start(10000);
|
|
|
|
m_SlideBitmap = wxNullBitmap;
|
|
m_bCurrentSlideIsDefault = false;
|
|
|
|
#ifdef __WXMAC__
|
|
// Tell accessibility aids to ignore this panel (but not its contents)
|
|
HIObjectSetAccessibilityIgnored((HIObjectRef)GetHandle(), true);
|
|
#endif
|
|
}
|
|
|
|
CSlideShowPanel::~CSlideShowPanel()
|
|
{
|
|
if ( m_ChangeSlideTimer->IsRunning() ) {
|
|
m_ChangeSlideTimer->Stop();
|
|
}
|
|
delete m_ChangeSlideTimer;
|
|
}
|
|
|
|
|
|
void CSlideShowPanel::OnSlideShowTimer(wxTimerEvent& WXUNUSED(event)) {
|
|
AdvanceSlideShow(true);
|
|
}
|
|
|
|
|
|
void CSlideShowPanel::AdvanceSlideShow(bool changeSlide) {
|
|
double xRatio, yRatio, ratio;
|
|
TaskSelectionData* selData = ((CSimpleTaskPanel*)GetParent())->GetTaskSelectionData();
|
|
if (selData == NULL) return;
|
|
|
|
int numSlides = (int)selData->slideShowFileNames.size();
|
|
|
|
if (numSlides <= 0) {
|
|
if (m_bCurrentSlideIsDefault) return;
|
|
|
|
CSkinSimple* pSkinSimple = wxGetApp().GetSkinManager()->GetSimple();
|
|
wxASSERT(pSkinSimple);
|
|
wxASSERT(wxDynamicCast(pSkinSimple, CSkinSimple));
|
|
|
|
m_SlideBitmap = *pSkinSimple->GetWorkunitAnimationImage()->GetBitmap();
|
|
if (m_SlideBitmap.Ok()) {
|
|
m_bCurrentSlideIsDefault = true;
|
|
}
|
|
} else {
|
|
// TODO: Should we allow slide show to advance if task is not running?
|
|
if (selData->dotColor == greenDot) { // Advance only if running
|
|
if (changeSlide) {
|
|
if (++(selData->lastSlideShown) >= numSlides) {
|
|
selData->lastSlideShown = 0;
|
|
}
|
|
}
|
|
}
|
|
if (selData->lastSlideShown < 0) {
|
|
selData->lastSlideShown = 0;
|
|
}
|
|
|
|
wxBitmap *bm = new wxBitmap();
|
|
bm->LoadFile(selData->slideShowFileNames[selData->lastSlideShown], wxBITMAP_TYPE_ANY);
|
|
m_SlideBitmap = *bm;
|
|
delete bm;
|
|
|
|
if (m_SlideBitmap.Ok()) {
|
|
m_bCurrentSlideIsDefault = false;
|
|
}
|
|
}
|
|
if (m_SlideBitmap.Ok()) {
|
|
// Check to see if they need to be rescaled to fit in the window
|
|
ratio = 1.0;
|
|
xRatio = ((double) GetSize().GetWidth() - (2*SLIDESHOWBORDER))/((double) m_SlideBitmap.GetWidth());
|
|
yRatio = ((double) GetSize().GetHeight() - (2*SLIDESHOWBORDER))/((double) m_SlideBitmap.GetHeight());
|
|
if ( xRatio < ratio ) {
|
|
ratio = xRatio;
|
|
}
|
|
if ( yRatio < ratio ) {
|
|
ratio = yRatio;
|
|
}
|
|
if ( ratio < 1.0 ) {
|
|
wxImage img = m_SlideBitmap.ConvertToImage();
|
|
img.Rescale((int) (m_SlideBitmap.GetWidth()*ratio), (int) (m_SlideBitmap.GetHeight()*ratio));
|
|
wxBitmap *bm = new wxBitmap(img);
|
|
m_SlideBitmap = *bm;
|
|
delete bm;
|
|
}
|
|
Refresh();
|
|
}
|
|
}
|
|
|
|
|
|
void CSlideShowPanel::OnPaint(wxPaintEvent& WXUNUSED(event))
|
|
{
|
|
wxPaintDC dc(this);
|
|
int w, h;
|
|
wxPen oldPen = dc.GetPen();
|
|
wxBrush oldBrush = dc.GetBrush();
|
|
int oldMode = dc.GetBackgroundMode();
|
|
wxPen bgPen(*wxLIGHT_GREY, 3);
|
|
dc.SetBackgroundMode(wxSOLID);
|
|
dc.SetPen(bgPen);
|
|
dc.SetBrush(*wxBLACK_BRUSH);
|
|
|
|
GetSize(&w, &h);
|
|
dc.DrawRectangle(0, 0, w, h);
|
|
// Restore Mode, Pen and Brush
|
|
dc.SetBackgroundMode(oldMode);
|
|
dc.SetPen(oldPen);
|
|
dc.SetBrush(oldBrush);
|
|
|
|
if(m_SlideBitmap.Ok())
|
|
{
|
|
dc.DrawBitmap(m_SlideBitmap,
|
|
(w - m_SlideBitmap.GetWidth())/2,
|
|
(h - m_SlideBitmap.GetHeight())/2
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
IMPLEMENT_DYNAMIC_CLASS(CSimpleTaskPanel, CSimplePanelBase)
|
|
|
|
BEGIN_EVENT_TABLE(CSimpleTaskPanel, CSimplePanelBase)
|
|
EVT_BOINCBITMAPCOMBOBOX(ID_SGTASKSELECTOR, CSimpleTaskPanel::OnTaskSelection)
|
|
// EVT_BUTTON(ID_TASKSCOMMANDBUTTON, CSimpleTaskPanel::OnTasksCommand)
|
|
// EVT_LEFT_DOWN(CSimpleTaskPanel::OnTasksCommand)
|
|
EVT_TIMER(ID_SIMPLE_PROGRESSPULSETIMER, CSimpleTaskPanel::OnPulseProgressIndicator)
|
|
#ifdef __WXMAC__
|
|
EVT_ERASE_BACKGROUND(CSimpleTaskPanel::OnEraseBackground)
|
|
#endif
|
|
END_EVENT_TABLE()
|
|
|
|
#if TESTBIGICONPOPUP
|
|
static wxString tempArray[] = {_T("String1"), _T("String2"), _T("String3") };
|
|
#endif
|
|
|
|
CSimpleTaskPanel::CSimpleTaskPanel() {
|
|
}
|
|
|
|
CSimpleTaskPanel::CSimpleTaskPanel( wxWindow* parent ) :
|
|
CSimplePanelBase( parent )
|
|
{
|
|
wxSize sz;
|
|
int w, h;
|
|
wxString str = wxEmptyString;
|
|
|
|
m_pulseTimer = new wxTimer(this, ID_SIMPLE_PROGRESSPULSETIMER);
|
|
m_oldWorkCount = -1;
|
|
error_time = 0;
|
|
m_GotBGBitMap = false; // Can't be made until parent has been laid out.
|
|
m_bStableTaskInfoChanged = false;
|
|
m_CurrentTaskSelection = -1;
|
|
m_sNoProjectsString = _("You don't have any projects. Please Add a Project.");
|
|
m_sNotAvailableString = _("Not available");
|
|
m_progressBarRect = NULL;
|
|
|
|
SetForegroundColour(*wxBLACK);
|
|
|
|
wxBoxSizer* bSizer1;
|
|
bSizer1 = new wxBoxSizer( wxVERTICAL );
|
|
|
|
wxBoxSizer* bSizer2;
|
|
bSizer2 = new wxBoxSizer( wxHORIZONTAL );
|
|
|
|
m_myTasksLabel = new CTransparentStaticText( this, wxID_ANY, _("Tasks:"), wxDefaultPosition, wxDefaultSize, 0 );
|
|
m_myTasksLabel->Wrap( -1 );
|
|
bSizer2->Add( m_myTasksLabel, 0, wxRIGHT, 5 );
|
|
|
|
#if TESTBIGICONPOPUP
|
|
m_TaskSelectionCtrl = new CBOINCBitmapComboBox( this, ID_SGTASKSELECTOR, wxT(""), wxDefaultPosition, wxDefaultSize, 3, tempArray, wxCB_READONLY );
|
|
m_TaskSelectionCtrl->SetItemBitmap(0, m_RedDot);
|
|
m_TaskSelectionCtrl->SetItemBitmap(1, m_GreenDot);
|
|
m_TaskSelectionCtrl->SetItemBitmap(2, m_YellowDot);
|
|
// m_TaskSelectionCtrl->SetStringSelection(tempArray[1]);
|
|
m_TaskSelectionCtrl->SetSelection(1);
|
|
#else
|
|
m_TaskSelectionCtrl = new CBOINCBitmapComboBox( this, ID_SGTASKSELECTOR, wxT(""), wxDefaultPosition, wxDefaultSize, 0, NULL, wxCB_READONLY );
|
|
#endif
|
|
// TODO: Might want better wording for Task Selection Combo Box tooltip
|
|
str = _("Select a task to access");
|
|
m_TaskSelectionCtrl->SetToolTip(str);
|
|
bSizer2->Add( m_TaskSelectionCtrl, 1, wxRIGHT | wxEXPAND, SIDEMARGINS );
|
|
|
|
bSizer1->Add( bSizer2, 0, wxEXPAND | wxTOP | wxLEFT, 10 );
|
|
|
|
bSizer1->AddSpacer(5);
|
|
|
|
wxBoxSizer* bSizer3;
|
|
bSizer3 = new wxBoxSizer( wxHORIZONTAL );
|
|
|
|
m_TaskProjectLabel = new CTransparentStaticText( this, wxID_ANY, _("From:"), wxDefaultPosition, wxDefaultSize, 0 );
|
|
m_TaskProjectLabel->Wrap( -1 );
|
|
bSizer3->Add( m_TaskProjectLabel, 0, wxRIGHT, 5 );
|
|
|
|
m_TaskProjectName = new CTransparentStaticText( this, wxID_ANY, wxT("SETI@home"), wxDefaultPosition, wxDefaultSize, wxST_NO_AUTORESIZE );
|
|
m_TaskProjectName->Wrap( -1 );
|
|
wxFont theFont = m_TaskProjectName->GetFont();
|
|
theFont.SetWeight(wxFONTWEIGHT_BOLD);
|
|
m_TaskProjectName->SetFont(theFont);
|
|
bSizer3->Add( m_TaskProjectName, 1, 0, 0 );
|
|
|
|
bSizer1->Add( bSizer3, 0, wxLEFT | wxRIGHT | wxEXPAND, SIDEMARGINS );
|
|
|
|
#if SELECTBYRESULTNAME
|
|
m_TaskApplicationName = new CTransparentStaticText( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxST_NO_AUTORESIZE );
|
|
m_TaskApplicationName->Wrap( -1 );
|
|
|
|
bSizer1->Add( m_TaskApplicationName, 0, wxLEFT | wxRIGHT | wxEXPAND, SIDEMARGINS );
|
|
#endif
|
|
|
|
bSizer1->AddSpacer(10);
|
|
|
|
m_SlideShowArea = new CSlideShowPanel(this);
|
|
m_SlideShowArea->SetMinSize(wxSize(290+(2*SLIDESHOWBORDER), 126+(2*SLIDESHOWBORDER)));
|
|
m_SlideShowArea->Enable( false );
|
|
|
|
bSizer1->Add( m_SlideShowArea, 0, wxLEFT | wxRIGHT | wxEXPAND, SIDEMARGINS );
|
|
|
|
bSizer1->AddSpacer(10);
|
|
|
|
m_ElapsedTimeValue = new CTransparentStaticText( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxST_NO_AUTORESIZE );
|
|
m_ElapsedTimeValue->Wrap( -1 );
|
|
bSizer1->Add( m_ElapsedTimeValue, 0, wxLEFT | wxRIGHT | wxEXPAND, SIDEMARGINS );
|
|
|
|
bSizer1->AddSpacer(7);
|
|
|
|
m_TimeRemainingValue = new CTransparentStaticText( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxST_NO_AUTORESIZE );
|
|
m_TimeRemainingValue->Wrap( -1 );
|
|
bSizer1->Add( m_TimeRemainingValue, 0, wxLEFT | wxRIGHT | wxEXPAND, SIDEMARGINS );
|
|
|
|
bSizer1->AddSpacer(7);
|
|
|
|
wxBoxSizer* bSizer4;
|
|
bSizer4 = new wxBoxSizer( wxHORIZONTAL );
|
|
|
|
// TODO: Standard Mac progress indicator's animation uses lots of CPU
|
|
// time, and also triggers unnecessary Erase events. Should we use a
|
|
// non-standard progress indicator on Mac? See also optimizations in
|
|
// CSimpleGUIPanel::OnEraseBackground and CSimpleTaskPanel::OnEraseBackground.
|
|
m_ProgressBar = new wxGauge( this, wxID_ANY, 100, wxDefaultPosition, wxDefaultSize, wxGA_HORIZONTAL );
|
|
m_iPctDoneX10 = 1000;
|
|
m_ProgressBar->SetValue( 100 );
|
|
GetTextExtent(wxT("0"), &w, &h);
|
|
m_ProgressBar->SetMinSize(wxSize(245, h));
|
|
m_ProgressBar->SetToolTip(_("This task's progress"));
|
|
bSizer4->Add( m_ProgressBar, 0, wxRIGHT, 5 );
|
|
|
|
m_ProgressValueText = new CTransparentStaticText( this, wxID_ANY, wxT("100.0%"), wxDefaultPosition, wxDefaultSize, wxALIGN_RIGHT | wxST_NO_AUTORESIZE );
|
|
m_ProgressValueText->Wrap( -1 );
|
|
bSizer4->Add( m_ProgressValueText, 0, wxALIGN_RIGHT, 0 );
|
|
|
|
bSizer1->Add( bSizer4, 0, wxLEFT | wxRIGHT | wxEXPAND, SIDEMARGINS );
|
|
|
|
bSizer1->AddSpacer(7);
|
|
|
|
// TODO: Can we determine the longest status string and initialize with it?
|
|
m_StatusValueText = new CTransparentStaticText( this, wxID_ANY, m_sNoProjectsString, wxDefaultPosition, wxDefaultSize, wxST_NO_AUTORESIZE );
|
|
// m_StatusValueText = new CTransparentStaticText( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxST_NO_AUTORESIZE );
|
|
m_StatusValueText->Wrap( -1 );
|
|
bSizer1->Add( m_StatusValueText, 0, wxLEFT | wxRIGHT | wxEXPAND, SIDEMARGINS );
|
|
|
|
bSizer1->AddSpacer(7);
|
|
|
|
m_TaskCommandsButton = new CSimpleTaskPopupButton( this, ID_TASKSCOMMANDBUTTON, _("Task Commands"), wxDefaultPosition, wxDefaultSize, 0 );
|
|
m_TaskCommandsButton->SetToolTip(_("Pop up a menu of commands to apply to this task"));
|
|
bSizer1->Add( m_TaskCommandsButton, 0, wxLEFT | wxRIGHT | wxEXPAND | wxALIGN_CENTER_HORIZONTAL, SIDEMARGINS );
|
|
|
|
bSizer1->AddSpacer(10);
|
|
|
|
this->SetSizer( bSizer1 );
|
|
this->Layout();
|
|
|
|
m_ProgressRect = m_ProgressBar->GetRect();
|
|
m_ProgressRect.Inflate(1, 0);
|
|
}
|
|
|
|
|
|
CSimpleTaskPanel::~CSimpleTaskPanel()
|
|
{
|
|
TaskSelectionData *selData;
|
|
int count = m_TaskSelectionCtrl->GetCount();
|
|
for(int j = count-1; j >=0; --j) {
|
|
selData = (TaskSelectionData*)m_TaskSelectionCtrl->GetClientData(j);
|
|
selData->slideShowFileNames.Clear();
|
|
delete selData;
|
|
m_TaskSelectionCtrl->SetClientData(j, NULL);
|
|
}
|
|
m_TaskSelectionCtrl->Clear();
|
|
|
|
m_pulseTimer->Stop();
|
|
delete m_pulseTimer;
|
|
|
|
if (m_progressBarRect) {
|
|
delete m_progressBarRect;
|
|
}
|
|
}
|
|
|
|
|
|
void CSimpleTaskPanel::OnTaskSelection(wxCommandEvent& /*event*/)
|
|
{
|
|
int sel = m_TaskSelectionCtrl->GetSelection();
|
|
if (sel != m_CurrentTaskSelection) {
|
|
m_CurrentTaskSelection = sel;
|
|
m_bStableTaskInfoChanged = true;
|
|
Update();
|
|
}
|
|
}
|
|
|
|
|
|
void CSimpleTaskPanel::Update(bool delayShow) {
|
|
wxString s = wxEmptyString;
|
|
wxString projName = wxEmptyString;
|
|
TaskSelectionData *selData;
|
|
CMainDocument* pDoc = wxGetApp().GetDocument();
|
|
wxASSERT(pDoc);
|
|
int workCount = pDoc->GetSimpleGUIWorkCount();
|
|
|
|
// Workaround for Linux refresh problem
|
|
static bool wasDelayed = false;
|
|
|
|
#ifndef __WXMAC__
|
|
Freeze();
|
|
#endif
|
|
|
|
if ((workCount <= 0) || delayShow) {
|
|
if ((workCount != m_oldWorkCount) || delayShow) {
|
|
wasDelayed = true;
|
|
m_myTasksLabel->Hide();
|
|
m_TaskSelectionCtrl->Hide();
|
|
m_TaskProjectLabel->Hide();
|
|
m_TaskProjectName->Hide();
|
|
#if SELECTBYRESULTNAME
|
|
m_TaskApplicationName->Hide();
|
|
#endif
|
|
m_SlideShowArea->Hide();
|
|
m_ElapsedTimeValue->Hide();
|
|
m_TimeRemainingValue->Hide();
|
|
m_ProgressValueText->Hide();
|
|
m_TaskCommandsButton->Hide();
|
|
if (m_iPctDoneX10 >= 0) {
|
|
m_iPctDoneX10 = -1;
|
|
m_ProgressBar->Pulse();
|
|
m_pulseTimer->Start(100);
|
|
}
|
|
this->Layout();
|
|
}
|
|
|
|
DisplayIdleState();
|
|
|
|
} else {
|
|
if ((m_oldWorkCount == 0) || wasDelayed) {
|
|
wasDelayed = false;
|
|
m_myTasksLabel->Show();
|
|
m_TaskSelectionCtrl->Show();
|
|
m_TaskProjectLabel->Show();
|
|
m_TaskProjectName->Show();
|
|
#if SELECTBYRESULTNAME
|
|
m_TaskApplicationName->Show();
|
|
#endif
|
|
m_SlideShowArea->Show();
|
|
m_ElapsedTimeValue->Show();
|
|
m_TimeRemainingValue->Show();
|
|
m_ProgressValueText->Show();
|
|
m_TaskCommandsButton->Show();
|
|
this->Layout();
|
|
}
|
|
|
|
UpdateTaskSelectionList(false);
|
|
|
|
// We now have valid result pointers, so extract our data
|
|
int count = m_TaskSelectionCtrl->GetCount();
|
|
if (count <= 0) {
|
|
m_CurrentTaskSelection = -1;
|
|
} else {
|
|
if ((m_CurrentTaskSelection < 0) || (m_CurrentTaskSelection > count -1)) {
|
|
m_TaskSelectionCtrl->SetSelection(0);
|
|
m_CurrentTaskSelection = 0;
|
|
m_bStableTaskInfoChanged = true;
|
|
}
|
|
selData = (TaskSelectionData*)m_TaskSelectionCtrl->GetClientData(m_CurrentTaskSelection);
|
|
RESULT* result = selData->result;
|
|
if (result) {
|
|
if (m_bStableTaskInfoChanged) {
|
|
#if SELECTBYRESULTNAME
|
|
wxString str = wxEmptyString;
|
|
GetApplicationAndProjectNames(result, &s, &projName);
|
|
str.Printf(_("Application: %s"), s.c_str());
|
|
UpdateStaticText(&m_TaskApplicationName, str);
|
|
UpdateStaticText(&m_TaskProjectName, projName);
|
|
#else
|
|
GetApplicationAndProjectNames(result, NULL, &projName);
|
|
#endif
|
|
UpdateStaticText(&m_TaskProjectName, projName);
|
|
m_SlideShowArea->AdvanceSlideShow(false);
|
|
m_bStableTaskInfoChanged = false;
|
|
}
|
|
float f = result->elapsed_time;
|
|
if (f == 0.) f = result->current_cpu_time;
|
|
// f = result->final_elapsed_time;
|
|
UpdateStaticText(&m_ElapsedTimeValue, GetElapsedTimeString(f));
|
|
UpdateStaticText(&m_TimeRemainingValue, GetTimeRemainingString(result->estimated_cpu_time_remaining));
|
|
int pctDoneX10 = result->fraction_done * 1000.0;
|
|
if (m_iPctDoneX10 != pctDoneX10) {
|
|
if (m_iPctDoneX10 < 0) {
|
|
m_pulseTimer->Stop();
|
|
}
|
|
int pctDone = pctDoneX10 / 10;
|
|
if (pctDone != (m_iPctDoneX10 / 10)) {
|
|
m_ProgressBar->SetValue(pctDone);
|
|
}
|
|
s.Printf(_("%d.%d%%"), pctDoneX10 / 10, pctDoneX10 % 10 );
|
|
m_iPctDoneX10 = pctDoneX10;
|
|
UpdateStaticText(&m_ProgressValueText, s);
|
|
}
|
|
UpdateStaticText(&m_StatusValueText, GetStatusString(result));
|
|
} else {
|
|
UpdateStaticText(&m_TaskProjectName, m_sNotAvailableString);
|
|
#if SELECTBYRESULTNAME
|
|
UpdateStaticText(&m_TaskApplicationName, _("Application: Not available") );
|
|
#endif
|
|
UpdateStaticText(&m_ElapsedTimeValue, GetElapsedTimeString(-1.0));
|
|
UpdateStaticText(&m_TimeRemainingValue, GetTimeRemainingString(-1.0));
|
|
if (m_iPctDoneX10 >= 0) {
|
|
m_iPctDoneX10 = -1;
|
|
m_ProgressBar->Pulse();
|
|
m_pulseTimer->Start(100);
|
|
}
|
|
UpdateStaticText(&m_ProgressValueText, wxEmptyString);
|
|
UpdateStaticText(&m_StatusValueText, GetStatusString(NULL));
|
|
}
|
|
}
|
|
}
|
|
m_oldWorkCount = workCount;
|
|
|
|
#ifndef __WXMAC__
|
|
Thaw();
|
|
#endif
|
|
}
|
|
|
|
|
|
void CSimpleTaskPanel::ReskinInterface() {
|
|
CSimplePanelBase::ReskinInterface();
|
|
m_SlideShowArea->AdvanceSlideShow(false);
|
|
UpdateTaskSelectionList(true);
|
|
}
|
|
|
|
|
|
void CSimpleTaskPanel::OnPulseProgressIndicator(wxTimerEvent& /*event*/) {
|
|
m_ProgressBar->Pulse();
|
|
}
|
|
|
|
|
|
TaskSelectionData* CSimpleTaskPanel::GetTaskSelectionData() {
|
|
int count = m_TaskSelectionCtrl->GetCount();
|
|
if (count <= 0) {
|
|
return NULL;
|
|
}
|
|
|
|
int n = m_TaskSelectionCtrl->GetSelection();
|
|
return (TaskSelectionData*)m_TaskSelectionCtrl->GetClientData(n);
|
|
}
|
|
|
|
|
|
// Either appName argument or projName argument may be NULL
|
|
void CSimpleTaskPanel::GetApplicationAndProjectNames(RESULT* result, wxString* appName, wxString* projName) {
|
|
CMainDocument* pDoc = wxGetApp().GetDocument();
|
|
RESULT* state_result = NULL;
|
|
wxString strAppBuffer = wxEmptyString;
|
|
wxString strClassBuffer = wxEmptyString;
|
|
|
|
wxASSERT(pDoc);
|
|
wxASSERT(wxDynamicCast(pDoc, CMainDocument));
|
|
|
|
state_result = pDoc->state.lookup_result(result->project_url, result->name);
|
|
if (!state_result) {
|
|
pDoc->ForceCacheUpdate();
|
|
state_result = pDoc->state.lookup_result(result->project_url, result->name);
|
|
}
|
|
|
|
if (!state_result) return;
|
|
|
|
if (appName != NULL) {
|
|
WORKUNIT* wup = state_result->wup;
|
|
if (!wup) return;
|
|
APP* app = wup->app;
|
|
if (!app) return;
|
|
APP_VERSION* avp = state_result->avp;
|
|
if (!avp) return;
|
|
|
|
if (strlen(app->user_friendly_name)) {
|
|
strAppBuffer = wxString(state_result->app->user_friendly_name, wxConvUTF8);
|
|
} else {
|
|
strAppBuffer = wxString(state_result->avp->app_name, wxConvUTF8);
|
|
}
|
|
|
|
if (strlen(avp->plan_class)) {
|
|
strClassBuffer.Printf(
|
|
wxT(" (%s)"),
|
|
wxString(avp->plan_class, wxConvUTF8).c_str()
|
|
);
|
|
}
|
|
|
|
appName->Printf(
|
|
wxT("%s%s %d.%02d %s"),
|
|
state_result->project->anonymous_platform?_("Local: "):wxT(""),
|
|
strAppBuffer.c_str(),
|
|
state_result->avp->version_num / 100,
|
|
state_result->avp->version_num % 100,
|
|
strClassBuffer.c_str()
|
|
);
|
|
}
|
|
|
|
if (projName != NULL) {
|
|
*projName = wxString(state_result->project->project_name.c_str(), wxConvUTF8 );
|
|
if (projName->IsEmpty()) {
|
|
*projName = _("Not Available");
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
wxString CSimpleTaskPanel::GetElapsedTimeString(double f) {
|
|
wxString s = wxEmptyString;
|
|
wxString str = wxEmptyString;
|
|
|
|
if (f < 0) {
|
|
s = m_sNotAvailableString;
|
|
} else {
|
|
s = FormatTime(f);
|
|
}
|
|
str.Printf(_("Elapsed: %s"), s.c_str());
|
|
return str;
|
|
}
|
|
|
|
|
|
wxString CSimpleTaskPanel::GetTimeRemainingString(double f) {
|
|
wxString s = wxEmptyString;
|
|
wxString str = wxEmptyString;
|
|
|
|
if (f < 0) {
|
|
s = m_sNotAvailableString;
|
|
} else {
|
|
s = FormatTime(f);
|
|
}
|
|
str.Printf(_("Remaining: %s"), s.c_str());
|
|
return str;
|
|
}
|
|
|
|
|
|
wxString CSimpleTaskPanel::GetStatusString(RESULT* result) {
|
|
wxString s = wxEmptyString;
|
|
wxString str = wxEmptyString;
|
|
|
|
if (result == NULL) {
|
|
s = m_sNotAvailableString;
|
|
} else {
|
|
s = result_description(result, false);
|
|
}
|
|
|
|
str.Printf(_("Status: %s"), s.c_str());
|
|
return str;
|
|
}
|
|
|
|
|
|
wxString CSimpleTaskPanel::FormatTime(float fBuffer) {
|
|
wxInt32 iHour = 0;
|
|
wxInt32 iMin = 0;
|
|
wxInt32 iSec = 0;
|
|
wxTimeSpan ts;
|
|
wxString strBuffer= wxEmptyString;
|
|
|
|
if (0 >= fBuffer) {
|
|
strBuffer = wxT("---");
|
|
} else {
|
|
iHour = (wxInt32)(fBuffer / (60 * 60));
|
|
iMin = (wxInt32)(fBuffer / 60) % 60;
|
|
iSec = (wxInt32)(fBuffer) % 60;
|
|
|
|
ts = wxTimeSpan(iHour, iMin, iSec);
|
|
|
|
strBuffer = ts.Format();
|
|
}
|
|
return strBuffer;
|
|
}
|
|
|
|
|
|
void CSimpleTaskPanel::FindSlideShowFiles(TaskSelectionData *selData) {
|
|
RESULT* state_result;
|
|
char urlDirectory[1024];
|
|
char fileName[1024];
|
|
char resolvedFileName[1024];
|
|
int j;
|
|
CMainDocument* pDoc = wxGetApp().GetDocument();
|
|
wxASSERT(pDoc);
|
|
|
|
|
|
selData->slideShowFileNames.Clear();
|
|
state_result = pDoc->state.lookup_result(selData->result->project_url, selData->result->name);
|
|
if (!state_result) {
|
|
pDoc->ForceCacheUpdate();
|
|
state_result = pDoc->state.lookup_result(selData->result->project_url, selData->result->name);
|
|
}
|
|
if (state_result) {
|
|
url_to_project_dir(state_result->project->master_url, urlDirectory);
|
|
for(j=0; j<99; ++j) {
|
|
sprintf(fileName, "%s/slideshow_%s_%02d", urlDirectory, state_result->app->name, j);
|
|
if(boinc_resolve_filename(fileName, resolvedFileName, sizeof(resolvedFileName)) == 0) {
|
|
if (boinc_file_exists(resolvedFileName)) {
|
|
selData->slideShowFileNames.Add(wxString(resolvedFileName,wxConvUTF8));
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( selData->slideShowFileNames.size() == 0 ) {
|
|
for(j=0; j<99; ++j) {
|
|
sprintf(fileName, "%s/slideshow_%02d", urlDirectory, j);
|
|
if(boinc_resolve_filename(fileName, resolvedFileName, sizeof(resolvedFileName)) == 0) {
|
|
if (boinc_file_exists(resolvedFileName)) {
|
|
selData->slideShowFileNames.Add(wxString(resolvedFileName,wxConvUTF8));
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
selData->lastSlideShown = -1;
|
|
}
|
|
|
|
|
|
void CSimpleTaskPanel::UpdateTaskSelectionList(bool reskin) {
|
|
int i, j, count, newColor;
|
|
TaskSelectionData *selData;
|
|
RESULT* result;
|
|
RESULT* ctrlResult;
|
|
PROJECT* project;
|
|
std::vector<bool>is_alive;
|
|
bool needRefresh = false;
|
|
wxString resname;
|
|
CMainDocument* pDoc = wxGetApp().GetDocument();
|
|
CSkinSimple* pSkinSimple = wxGetApp().GetSkinManager()->GetSimple();
|
|
|
|
wxASSERT(pDoc);
|
|
wxASSERT(pSkinSimple);
|
|
wxASSERT(wxDynamicCast(pSkinSimple, CSkinSimple));
|
|
|
|
count = m_TaskSelectionCtrl->GetCount();
|
|
// Mark all inactive (this lets us loop only once)
|
|
for (i=0; i<count; ++i) {
|
|
is_alive.push_back(false);
|
|
}
|
|
|
|
// First update existing entries and add new ones
|
|
for(int i = 0; i < (int) pDoc->results.results.size(); i++) {
|
|
bool found = false;
|
|
|
|
result = pDoc->result(i);
|
|
// only check tasks that are active
|
|
if ( result == NULL || !result->active_task ) {
|
|
continue;
|
|
}
|
|
|
|
resname = wxEmptyString;
|
|
#if SELECTBYRESULTNAME
|
|
resname = FromUTF8(result->name);
|
|
#else
|
|
GetApplicationAndProjectNames(result, &resname, NULL);
|
|
#endif
|
|
|
|
// loop through the items already in Task Selection Control to find this result
|
|
count = m_TaskSelectionCtrl->GetCount();
|
|
for(j = 0; j < count; ++j) {
|
|
selData = (TaskSelectionData*)m_TaskSelectionCtrl->GetClientData(j);
|
|
if (!strcmp(result->name, selData->result_name) &&
|
|
!strcmp(result->project_url, selData->project_url)
|
|
) {
|
|
selData->result = result;
|
|
found = true;
|
|
is_alive.at(j) = true;
|
|
break; // skip out of this loop
|
|
}
|
|
#if SORTTASKLIST
|
|
if ((m_TaskSelectionCtrl->GetString(j)).Cmp(resname) > 0) {
|
|
break; // Insert the new item here (sorted by item label)
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// if it isn't currently in the list then we have a new one! lets add it
|
|
if (!found) {
|
|
selData = new TaskSelectionData;
|
|
selData->result = result;
|
|
strncpy(selData->result_name, result->name, sizeof(selData->result_name));
|
|
strncpy(selData->project_url, result->project_url, sizeof(selData->project_url));
|
|
selData->dotColor = -1;
|
|
FindSlideShowFiles(selData);
|
|
project = pDoc->state.lookup_project(result->project_url);
|
|
if (project) {
|
|
selData->project_files_downloaded_time = project->project_files_downloaded_time;
|
|
} else {
|
|
selData->project_files_downloaded_time = 0.0;
|
|
}
|
|
|
|
#if SORTTASKLIST
|
|
if (j < count) {
|
|
std::vector<bool>::iterator iter = is_alive.begin();
|
|
m_TaskSelectionCtrl->Insert(resname, wxNullBitmap, j, (void*)selData);
|
|
is_alive.insert(iter+j, true);
|
|
if (j <= m_CurrentTaskSelection) {
|
|
++m_CurrentTaskSelection;
|
|
m_TaskSelectionCtrl->SetSelection(m_CurrentTaskSelection);
|
|
}
|
|
} else
|
|
#endif
|
|
{
|
|
m_TaskSelectionCtrl->Append(resname, wxNullBitmap, (void*)selData);
|
|
is_alive.push_back(true);
|
|
}
|
|
++count;
|
|
}
|
|
}
|
|
|
|
// Check items in descending order so deletion won't change indexes of items yet to be checked
|
|
for(j = count-1; j >=0; --j) {
|
|
if(! is_alive.at(j)) {
|
|
selData = (TaskSelectionData*)m_TaskSelectionCtrl->GetClientData(j);
|
|
selData->slideShowFileNames.Clear();
|
|
delete selData;
|
|
m_TaskSelectionCtrl->Delete(j);
|
|
if (j == m_CurrentTaskSelection) {
|
|
int newCount = m_TaskSelectionCtrl->GetCount();
|
|
if (m_CurrentTaskSelection < newCount) {
|
|
// Select the next item if one exists
|
|
m_TaskSelectionCtrl->SetSelection(m_CurrentTaskSelection);
|
|
} else if (newCount > 0) {
|
|
// Select the previous item if one exists
|
|
m_CurrentTaskSelection = newCount-1;
|
|
m_TaskSelectionCtrl->SetSelection(m_CurrentTaskSelection);
|
|
} else {
|
|
m_CurrentTaskSelection = -1;
|
|
m_TaskSelectionCtrl->SetSelection(wxNOT_FOUND);
|
|
}
|
|
m_bStableTaskInfoChanged = true;
|
|
needRefresh = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((m_CurrentTaskSelection >= 0) && !m_bStableTaskInfoChanged) {
|
|
selData = (TaskSelectionData*)m_TaskSelectionCtrl->GetClientData(m_CurrentTaskSelection);
|
|
project = pDoc->state.lookup_project(selData->project_url);
|
|
if ( project && (project->project_files_downloaded_time > selData->project_files_downloaded_time) ) {
|
|
FindSlideShowFiles(selData);
|
|
selData->project_files_downloaded_time = project->project_files_downloaded_time;
|
|
}
|
|
}
|
|
|
|
count = m_TaskSelectionCtrl->GetCount();
|
|
for(j = 0; j < count; ++j) {
|
|
selData = (TaskSelectionData*)m_TaskSelectionCtrl->GetClientData(j);
|
|
ctrlResult = selData->result;
|
|
if (Suspended() || ctrlResult->suspended_via_gui || ctrlResult->project_suspended_via_gui) {
|
|
newColor = redDot;
|
|
} else if (isRunning(ctrlResult)) {
|
|
newColor = greenDot;
|
|
} else if (ctrlResult->scheduler_state == CPU_SCHED_PREEMPTED) {
|
|
newColor = yellowDot;
|
|
} else {
|
|
newColor = redDot;
|
|
}
|
|
|
|
if (reskin || (newColor != selData->dotColor)) {
|
|
switch (newColor) {
|
|
case greenDot:
|
|
m_TaskSelectionCtrl->SetItemBitmap(j, *pSkinSimple->GetWorkunitRunningImage()->GetBitmap());
|
|
break;
|
|
case yellowDot:
|
|
m_TaskSelectionCtrl->SetItemBitmap(j, *pSkinSimple->GetWorkunitWaitingImage()->GetBitmap());
|
|
break;
|
|
case redDot:
|
|
m_TaskSelectionCtrl->SetItemBitmap(j, *pSkinSimple->GetWorkunitSuspendedImage()->GetBitmap());
|
|
break;
|
|
}
|
|
selData->dotColor = newColor;
|
|
needRefresh = true;
|
|
}
|
|
}
|
|
if (needRefresh) {
|
|
m_TaskSelectionCtrl->Refresh();
|
|
}
|
|
}
|
|
|
|
|
|
bool CSimpleTaskPanel::isRunning(RESULT* result) {
|
|
bool outcome = false;
|
|
|
|
// It must be scheduled to be running
|
|
if ( result->scheduler_state == CPU_SCHED_SCHEDULED ) {
|
|
// If either the project or task have been suspended, then it cannot be running
|
|
if ( !result->suspended_via_gui && !result->project_suspended_via_gui ) {
|
|
CC_STATUS status;
|
|
CMainDocument* pDoc = wxGetApp().GetDocument();
|
|
wxASSERT(pDoc);
|
|
|
|
pDoc->GetCoreClientStatus(status);
|
|
// Make sure that the core client isn't global suspended for some reason
|
|
if ( status.task_suspend_reason == 0 || status.task_suspend_reason == SUSPEND_REASON_CPU_THROTTLE ) {
|
|
outcome = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return outcome;
|
|
}
|
|
|
|
|
|
bool CSimpleTaskPanel::DownloadingResults() {
|
|
bool return_value = false;
|
|
CMainDocument* pDoc = wxGetApp().GetDocument();
|
|
wxASSERT(pDoc);
|
|
|
|
if ( pDoc->results.results.size() > 0 ) {
|
|
RESULT* result;
|
|
for(unsigned int i=0; !return_value && i < pDoc->results.results.size(); i++ ) {
|
|
result = pDoc->result(i);
|
|
if ( result != NULL && result->state == RESULT_FILES_DOWNLOADING ) {
|
|
return_value = true;
|
|
}
|
|
}
|
|
}
|
|
return return_value;
|
|
}
|
|
|
|
bool CSimpleTaskPanel::Suspended() {
|
|
CC_STATUS status;
|
|
CMainDocument* pDoc = wxGetApp().GetDocument();
|
|
wxASSERT(pDoc);
|
|
|
|
bool result = false;
|
|
pDoc->GetCoreClientStatus(status);
|
|
if ( pDoc->IsConnected() && status.task_suspend_reason > 0 && status.task_suspend_reason != SUSPEND_REASON_DISK_SIZE && status.task_suspend_reason != SUSPEND_REASON_CPU_THROTTLE ) {
|
|
result = true;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// Check to see if a project update is scheduled or in progress
|
|
bool CSimpleTaskPanel::ProjectUpdateScheduled() {
|
|
PROJECT* project;
|
|
CMainDocument* pDoc = wxGetApp().GetDocument();
|
|
wxASSERT(pDoc);
|
|
|
|
int prjCount = pDoc->GetSimpleProjectCount();
|
|
for(int i=0; i<prjCount; i++) {
|
|
project = pDoc->state.projects[i];
|
|
if ( project->sched_rpc_pending || project->master_url_fetch_pending || project->scheduler_rpc_in_progress ) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void CSimpleTaskPanel::DisplayIdleState() {
|
|
CMainDocument* pDoc = wxGetApp().GetDocument();
|
|
wxASSERT(pDoc);
|
|
|
|
if ( pDoc->IsReconnecting() ) {
|
|
error_time = 0;
|
|
UpdateStaticText(&m_StatusValueText, _("Retrieving current status."));
|
|
} else if ( pDoc->IsConnected() && pDoc->state.projects.size() == 0) {
|
|
error_time = 0;
|
|
UpdateStaticText(&m_StatusValueText, m_sNoProjectsString);
|
|
} else if ( DownloadingResults() ) {
|
|
error_time = 0;
|
|
UpdateStaticText(&m_StatusValueText, _("Downloading work from the server."));
|
|
} else if ( Suspended() ) {
|
|
CC_STATUS status;
|
|
pDoc->GetCoreClientStatus(status);
|
|
if ( status.task_suspend_reason & SUSPEND_REASON_BATTERIES ) {
|
|
UpdateStaticText(&m_StatusValueText, _("Processing Suspended: Running On Batteries."));
|
|
} else if ( status.task_suspend_reason & SUSPEND_REASON_USER_ACTIVE ) {
|
|
UpdateStaticText(&m_StatusValueText, _("Processing Suspended: User Active."));
|
|
} else if ( status.task_suspend_reason & SUSPEND_REASON_USER_REQ ) {
|
|
UpdateStaticText(&m_StatusValueText, _("Processing Suspended: User paused processing."));
|
|
} else if ( status.task_suspend_reason & SUSPEND_REASON_TIME_OF_DAY ) {
|
|
UpdateStaticText(&m_StatusValueText, _("Processing Suspended: Time of Day."));
|
|
} else if ( status.task_suspend_reason & SUSPEND_REASON_BENCHMARKS ) {
|
|
UpdateStaticText(&m_StatusValueText, _("Processing Suspended: Benchmarks Running."));
|
|
} else {
|
|
UpdateStaticText(&m_StatusValueText, _("Processing Suspended."));
|
|
}
|
|
} else if ( ProjectUpdateScheduled() ) {
|
|
error_time = 0;
|
|
UpdateStaticText(&m_StatusValueText, _("Waiting to contact project servers."));
|
|
} else {
|
|
if ( error_time == 0 ) {
|
|
error_time = time(NULL) + 10;
|
|
UpdateStaticText(&m_StatusValueText, _("Retrieving current status"));
|
|
} else if ( error_time < time(NULL) ) {
|
|
// TODO: should we display "ERROR" like old Simple GUI?
|
|
if ( pDoc->IsConnected() ) {
|
|
UpdateStaticText(&m_StatusValueText, _("No work available to process"));
|
|
} else {
|
|
UpdateStaticText(&m_StatusValueText, _("Unable to connect to the core client"));
|
|
}
|
|
} else {
|
|
UpdateStaticText(&m_StatusValueText, _("Retrieving current status"));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#ifdef __WXMAC__
|
|
// Avoid unnecessary drawing due to Mac progress indicator's animation
|
|
void CSimpleTaskPanel::OnEraseBackground(wxEraseEvent& event) {
|
|
wxRect clipRect;
|
|
wxDC *dc = event.GetDC();
|
|
|
|
if (m_progressBarRect == NULL) {
|
|
m_progressBarRect = new wxRect(m_ProgressBar->GetRect());
|
|
m_progressBarRect->Inflate(1, 0);
|
|
}
|
|
dc->GetClippingBox(&clipRect.x, &clipRect.y, &clipRect.width, &clipRect.height);
|
|
if (clipRect.IsEmpty() || m_progressBarRect->Contains(clipRect)) {
|
|
return;
|
|
}
|
|
|
|
CSimplePanelBase::OnEraseBackground(event);
|
|
}
|
|
#endif
|