boinc/clientgui/BOINCGridCtrl.cpp

926 lines
28 KiB
C++

// 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/>.
#if defined(__GNUG__) && !defined(__APPLE__)
#pragma implementation "BOINCGridCtrl.h"
#endif
#include "stdwx.h"
#include "BOINCGridCtrl.h"
#include "BOINCBaseView.h"
#include "res/sortascending.xpm"
#include "res/sortdescending.xpm"
/* static compare functions and helper variable */
static bool reverseCompareOrder=false;
/* compare Strings that contains floats */
static int CompareFloatString(const wxString& first,const wxString& second) {
double dFirst;
double dSecond;
first.ToDouble(&dFirst);
second.ToDouble(&dSecond);
if(dFirst < dSecond) {
return reverseCompareOrder ? 1 : -1 ;
}
if(dSecond < dFirst) {
return reverseCompareOrder ? -1 : 1 ;
}
return 0;
}
/* compare Strings case insensitive */
static int CompareStringStringNoCase(const wxString& first,const wxString& second) {
int ret = first.CmpNoCase(second);
return reverseCompareOrder ? ret * (-1) : ret;
}
/* compare Strings that contains int/long numbers */
static int CompareLongString(const wxString& first,const wxString& second) {
long lFirst;
long lSecond;
first.ToLong(&lFirst);
second.ToLong(&lSecond);
if(lFirst < lSecond) {
return reverseCompareOrder ? 1 : -1 ;
}
if(lSecond < lFirst) {
return reverseCompareOrder ? -1 : 1 ;
}
return 0;
}
/* compare strings that contains time periods in format 00:00:00 */
static int CompareTimeString(const wxString& first,const wxString& second) {
long dtFirst,dtSecond;
wxString hours,minutes,seconds;
long lHours,lMinutes,lSeconds;
//converting the first string to long value
hours = first.BeforeFirst(':');
seconds = first.AfterLast(':');
minutes = first.AfterFirst(':').BeforeFirst(':');
hours.ToLong(&lHours);
minutes.ToLong(&lMinutes);
seconds.ToLong(&lSeconds);
dtFirst = lSeconds + lMinutes * 60 + lHours * 3600;
//converting the second string
hours = second.BeforeFirst(':');
seconds = second.AfterLast(':');
minutes = second.AfterFirst(':').BeforeFirst(':');
hours.ToLong(&lHours);
minutes.ToLong(&lMinutes);
seconds.ToLong(&lSeconds);
dtSecond = lSeconds + lMinutes * 60 + lHours * 3600;
if(dtFirst < dtSecond) {
return reverseCompareOrder ? 1 : -1 ;
}
if(dtSecond < dtFirst) {
return reverseCompareOrder ? -1 : 1 ;
}
return 0;
}
/* compare Strings that contains dateTime*/
static int CompareDateString(const wxString& first,const wxString& second) {
wxDateTime dtFirst,dtSecond;
//dtFirst.ParseDateTime(first);
//dtSecond.ParseDateTime(second);
dtFirst.ParseFormat(first,wxT(" %x %X"));
dtSecond.ParseFormat(second,wxT(" %x %X"));
if(dtFirst < dtSecond) {
return reverseCompareOrder ? 1 : -1 ;
}
if(dtSecond < dtFirst) {
return reverseCompareOrder ? -1 : 1 ;
}
return 0;
}
/* ########## grid ctrl implementation ############### */
BEGIN_EVENT_TABLE (CBOINCGridCtrl, wxGrid)
EVT_GRID_LABEL_LEFT_CLICK(CBOINCGridCtrl::OnLabelLClick)
END_EVENT_TABLE ()
/* Constructor, don't call any grid methods here, because they could raise events,
which couldn't be handled correctly while the grid isn't constructed completly.
Instead call Setup() and place all further initialization there
*/
CBOINCGridCtrl::CBOINCGridCtrl(wxWindow* parent, wxWindowID iGridWindowID) : wxGrid(parent, iGridWindowID) {
//init members
sortColumn=-1;
sortAscending=true;
sortNeededByLabelClick=false;
m_pkColumnIndex1=-1;
m_pkColumnIndex2=-1;
m_cursorcol=-1;
m_cursorrow=-1;
m_arrSelectedKeys1.Clear();
m_arrSelectedKeys2.Clear();
//load sorting bitmaps
ascBitmap = wxBitmap(sortascending_xpm);
descBitmap = wxBitmap(sortdescending_xpm);
}
/* make settings for the grid here instead in the constructor */
void CBOINCGridCtrl::Setup() {
//make grid cursor invisible
SetCellHighlightPenWidth(0);
SetCellHighlightROPenWidth(0);
//change default selection colours
SetSelectionBackground(*wxLIGHT_GREY);
SetSelectionForeground(*wxBLACK);
//
SetRowLabelSize(1);//hide row labels
SetColLabelSize(20); //make header row smaller as default
SetColLabelAlignment(wxALIGN_LEFT,wxALIGN_CENTER);
EnableGridLines(false);
EnableDragRowSize(false);//prevent the user from changing the row height with the mouse
EnableDragCell(false);
EnableEditing(false);//make the whole grid read-only
SetDefaultCellBackgroundColour(*wxWHITE);
#ifdef __WXMAC__
SetLabelFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT));
SetDefaultCellFont(wxFont(12, wxSWISS, wxNORMAL, wxNORMAL, FALSE));
#else
SetLabelFont(*wxNORMAL_FONT);
#endif
this->SetScrollLineX(5);
this->SetScrollLineY(5);
}
CBOINCGridCtrl::~CBOINCGridCtrl() {
}
/* use this method instead of wxGrid::GetSelectedRows()
because a bug in wxGrid::GetSelectedRows() doesn't return anything although some rows are selected */
wxArrayInt CBOINCGridCtrl::GetSelectedRows2() {
wxArrayInt ret;
for(int i= 0; i< this->GetRows();i++) {
for(int j=0; j< this->GetCols();j++) {
if(this->IsInSelection(i,j)) {
ret.Add(i);
//break the inner loop here to prevent adding the same row twice
break;
}
}
}
return ret;
}
/* sets the column with unique values */
void CBOINCGridCtrl::SetPrimaryKeyColumns(int col1,int col2) {
m_pkColumnIndex1 = col1;
m_pkColumnIndex2 = col2;
}
/* If the user clicked inside the grid, the former selected cells must be deselected.
Because the code in OnListRender() of the GridViews raises SelectionEvents too,
call to GetBatchCount() here is necessary to decide what to do
*/
void CBOINCGridCtrl::ClearSavedSelection() {
if(this->GetBatchCount()<=0) {
m_arrSelectedKeys1.Empty();
m_arrSelectedKeys2.Empty();
}
}
/* save the key values of the currently selected rows for later restore */
void CBOINCGridCtrl::SaveSelection() {
if(m_pkColumnIndex1>=0) {
wxArrayInt arrSelRows = GetSelectedRows2();
m_arrSelectedKeys1.Empty();
m_arrSelectedKeys2.Empty();
for(unsigned int i=0; i< arrSelRows.GetCount();i++) {
m_arrSelectedKeys1.Add(GetCellValue(arrSelRows[i],m_pkColumnIndex1));
if (m_pkColumnIndex2 >= 0) {
m_arrSelectedKeys2.Add(GetCellValue(arrSelRows[i],m_pkColumnIndex2));
} else {
m_arrSelectedKeys2.Add(wxEmptyString);
}
}
}
}
/* select all rows, that were formerly selected
this raises selection events without user interaction */
void CBOINCGridCtrl::RestoreSelection() {
BeginBatch(); // To prevent flicker
ClearSelection();
for(unsigned int i=0;i < m_arrSelectedKeys1.size();i++) {
int index = GetTable()->FindRowIndexByColValue(
m_pkColumnIndex1,m_arrSelectedKeys1[i],m_pkColumnIndex2,m_arrSelectedKeys2[i]
);
if(index >=0) {
SelectRow(index,true);
}
}
EndBatch();
}
void CBOINCGridCtrl::SaveGridCursorPosition() {
m_cursorcol = GetGridCursorCol();
m_cursorrow = GetGridCursorRow();
if(m_cursorrow>=0 && m_cursorcol >=0) {
m_szCursorKey1 = GetCellValue(m_cursorrow,m_pkColumnIndex1);
if (m_pkColumnIndex2 >= 0) {
m_szCursorKey2 = GetCellValue(m_cursorrow,m_pkColumnIndex2);
}
}
}
void CBOINCGridCtrl::RestoreGridCursorPosition() {
int index = GetTable()->FindRowIndexByColValue(m_pkColumnIndex1,m_szCursorKey1,m_pkColumnIndex2,m_szCursorKey2);
if(index >=0) {
SetGridCursor(index,m_cursorcol);
}
}
int CBOINCGridCtrl::GetFirstSelectedRow() {
int ret= -1;
//Row mode ?
wxArrayInt selRows = this->GetSelectedRows2();
if(selRows.size() >0) {
return selRows[0];
}
//block mode ?
wxGridCellCoordsArray selBlocks = this->GetSelectionBlockTopLeft();
if(selBlocks.size()>0) {
return selBlocks[0].GetRow();
}
//cell mode
wxGridCellCoordsArray selCells = this->GetSelectionBlockTopLeft();
if(selCells.size()>0) {
return selCells[0].GetRow();
}
return ret;
}
/* saves column widths and sorting attributes */
bool CBOINCGridCtrl::OnSaveState(wxConfigBase* pConfig) {
wxString strBaseConfigLocation = wxEmptyString;
wxInt32 iIndex = 0;
wxInt32 iColumnCount = this->GetCols();
wxASSERT(pConfig);
// Retrieve the base location to store configuration information
// Should be in the following form: "/Projects/"
strBaseConfigLocation = pConfig->GetPath() + wxT("/");
// Cycle through the columns recording anything interesting
for (iIndex = 0; iIndex < iColumnCount; iIndex++) {
wxString label = this->GetColLabelValue(iIndex);
// Don't save width for hidden / invisible columns
if (label.IsEmpty()) {
continue;
}
pConfig->SetPath(strBaseConfigLocation + label);
pConfig->Write(wxT("Width"), this->GetColumnWidth(iIndex));
}
//save sorting column
pConfig->SetPath(strBaseConfigLocation);
pConfig->Write(wxT("SortColumn"),this->sortColumn);
pConfig->Write(wxT("SortAscending"),this->sortAscending);
return true;
}
/* restores column widths and sorting attributes */
bool CBOINCGridCtrl::OnRestoreState(wxConfigBase* pConfig) {
wxString strBaseConfigLocation = wxEmptyString;
wxInt32 iIndex = 0;
wxInt32 iTempValue = 0;
wxInt32 iColumnCount = GetCols();
wxASSERT(pConfig);
// Retrieve the base location to store configuration information
// Should be in the following form: "/Projects/"
strBaseConfigLocation = pConfig->GetPath() + wxT("/");
// Cycle through the columns recording anything interesting
for (iIndex = 0; iIndex < iColumnCount; iIndex++) {
wxString label = GetColLabelValue(iIndex);
// Don't restore width for hidden / invisible columns
if (label.IsEmpty()) {
continue;
}
pConfig->SetPath(strBaseConfigLocation + label);
pConfig->Read(wxT("Width"), &iTempValue, -1);
if (-1 != iTempValue) {
SetColSize(iIndex, iTempValue);
}
}
//read sorting
pConfig->SetPath(strBaseConfigLocation);
pConfig->Read(wxT("SortColumn"),&iTempValue,-1);
if(-1 != iTempValue) {
sortColumn = iTempValue;
}
pConfig->Read(wxT("SortAscending"),&iTempValue,-1);
if(-1 != iTempValue) {
sortAscending = iTempValue != 0 ? true : false;
}
return true;
}
/* for convienience purpose only */
void CBOINCGridCtrl::SetColAlignment(int col,int hAlign,int vAlign) {
wxGridCellAttr *attr = m_table->GetAttr(-1, col, wxGridCellAttr::Col );
if(!attr) {
attr = new wxGridCellAttr;
}
attr->SetAlignment(hAlign,vAlign);
SetColAttr(col, attr);
}
/* copied source code from wxGrid to make overloading possible */
void CBOINCGridCtrl::DrawTextRectangle( wxDC& dc,
const wxString& value,
const wxRect& rect,
int horizAlign,
int vertAlign,
int textOrientation )
{
wxArrayString lines;
StringToLines( value, lines );
//Forward to new API.
DrawTextRectangle( dc,
lines,
rect,
horizAlign,
vertAlign,
textOrientation );
}
/* draws text with ellipses instead cutting it */
void CBOINCGridCtrl::DrawTextRectangle( wxDC& dc,
const wxArrayString& lines,
const wxRect& rect,
int horizAlign,
int vertAlign,
int textOrientation )
{
long textWidth, textHeight;
long lineWidth, lineHeight;
unsigned int nLines;
dc.SetClippingRegion( rect );
nLines = (unsigned int)lines.GetCount();
if( nLines > 0 )
{
unsigned int l;
float x = 0.0, y = 0.0;
if( textOrientation == wxHORIZONTAL )
GetTextBoxSize(dc, lines, &textWidth, &textHeight);
else
GetTextBoxSize( dc, lines, &textHeight, &textWidth );
switch( vertAlign )
{
case wxALIGN_BOTTOM:
if( textOrientation == wxHORIZONTAL )
y = rect.y + (rect.height - textHeight - 1);
else
x = rect.x + rect.width - textWidth;
break;
case wxALIGN_CENTRE:
if( textOrientation == wxHORIZONTAL )
y = rect.y + ((rect.height - textHeight)/2);
else
x = rect.x + ((rect.width - textWidth)/2);
break;
case wxALIGN_TOP:
default:
if( textOrientation == wxHORIZONTAL )
y = rect.y + 1;
else
x = rect.x + 1;
break;
}
// Align each line of a multi-line label
for( l = 0; l < nLines; l++ )
{
dc.GetTextExtent(lines[l], &lineWidth, &lineHeight);
switch( horizAlign )
{
case wxALIGN_RIGHT:
if( textOrientation == wxHORIZONTAL )
x = rect.x + (rect.width - lineWidth - 1);
else
y = rect.y + lineWidth + 1;
break;
case wxALIGN_CENTRE:
if( textOrientation == wxHORIZONTAL )
x = rect.x + ((rect.width - lineWidth)/2);
else
y = rect.y + rect.height - ((rect.height - lineWidth)/2);
break;
case wxALIGN_LEFT:
default:
if( textOrientation == wxHORIZONTAL )
x = rect.x + 1;
else
y = rect.y + rect.height - 1;
break;
}
if( textOrientation == wxHORIZONTAL )
{
//changes applies here
wxString formattedText = FormatTextWithEllipses(dc,lines[l],rect.width);
dc.DrawText( formattedText, (int)x, (int)y );
y += lineHeight;
}
else
{
dc.DrawRotatedText( lines[l], (int)x, (int)y, 90.0 );
x += lineHeight;
}
}
}
dc.DestroyClippingRegion();
}
wxString CBOINCGridCtrl::FormatTextWithEllipses(wxDC& dc,const wxString &text,int width)
{
wxString retText;
wxString drawntext, ellipsis;
wxCoord w, h, base_w;
wxListItem item;
// determine if the string can fit inside the current width
dc.GetTextExtent(text, &w, &h);
if (w <= width)
{
retText = text;
}
else // otherwise, truncate and add an ellipsis if possible
{
// determine the base width
ellipsis = wxString(wxT("..."));
dc.GetTextExtent(ellipsis, &base_w, &h);
// continue until we have enough space or only one character left
wxCoord w_c, h_c;
size_t len = text.Length();
drawntext = text.Left(len);
while (len > 1)
{
dc.GetTextExtent(drawntext.Last(), &w_c, &h_c);
drawntext.RemoveLast();
len--;
w -= w_c;
if (w + base_w <= width)
break;
}
// if still not enough space, remove ellipsis characters
while (ellipsis.Length() > 0 && w + base_w > width)
{
ellipsis = ellipsis.Left(ellipsis.Length() - 1);
dc.GetTextExtent(ellipsis, &base_w, &h);
}
// now draw the text
retText = drawntext + ellipsis;
}
return retText;
}
/* not virtual in wxGrid, so code copied and modified her for painting sorting icons */
void CBOINCGridCtrl::DrawColLabel( wxDC& dc, int col )
{
if ( GetColWidth(col) <= 0 || m_colLabelHeight <= 0 )
return;
int colLeft = GetColLeft(col);
wxRect rect;
int colRight = GetColRight(col) - 1;
dc.SetPen( wxPen(wxSystemSettings::GetColour(wxSYS_COLOUR_3DDKSHADOW),1, wxSOLID) );
dc.DrawLine( colRight, 0,
colRight, m_colLabelHeight-1 );
dc.DrawLine( colLeft, 0, colRight, 0 );
dc.DrawLine( colLeft, m_colLabelHeight-1,
colRight+1, m_colLabelHeight-1 );
dc.SetPen( *wxWHITE_PEN );
dc.DrawLine( colLeft, 1, colLeft, m_colLabelHeight-1 );
dc.DrawLine( colLeft, 1, colRight, 1 );
dc.SetBackgroundMode( wxTRANSPARENT );
dc.SetTextForeground( GetLabelTextColour() );
dc.SetFont( GetLabelFont() );
int hAlign, vAlign, orient;
GetColLabelAlignment( &hAlign, &vAlign );
orient = GetColLabelTextOrientation();
rect.SetX( colLeft + 2 );
rect.SetY( 2 );
rect.SetWidth( GetColWidth(col) - 4 );
rect.SetHeight( m_colLabelHeight - 4 );
DrawTextRectangle( dc, GetColLabelValue( col ), rect, hAlign, vAlign, orient );
//paint sorting indicators, if needed
if(col == sortColumn) {
int x = rect.GetRight() - ascBitmap.GetWidth() - 2;
int y = rect.GetY();
dc.DrawBitmap(this->sortAscending ? descBitmap : ascBitmap,x,y,true);
}
}
/* handles left mouse click on column header */
void CBOINCGridCtrl::OnLabelLClick(wxGridEvent& ev) {
if(ev.GetCol() != -1) {
//same column as last time, then change only sort direction
if(sortColumn == ev.GetCol()) {
sortAscending = ! sortAscending;
} else {
int tmpOldColumn = sortColumn;
sortColumn = ev.GetCol();
sortAscending = true;
// Force a repaint of the label
if ( -1 != tmpOldColumn ) {
SetColLabelValue(tmpOldColumn, GetColLabelValue(tmpOldColumn));
}
}
// Force a repaint of the label
SetColLabelValue(ev.GetCol(), GetColLabelValue(ev.GetCol()));
//
sortNeededByLabelClick=true;
// Update and sort data
wxTimerEvent tEvent;
wxDynamicCast(GetParent(),CBOINCBaseView)->FireOnListRender(tEvent);
}
// The base class calls ClearSelection(), so do NOT call ev.Skip();
}
void CBOINCGridCtrl::SortData() {
GetTable()->SortData(sortColumn,sortAscending);
sortNeededByLabelClick=false;
}
void CBOINCGridCtrl::SetColumnSortType(int col,int sortType/*=CST_STRING*/) {
GetTable()->SetColumnSortType(col,sortType);
}
/* ################### generic grid cell renderer #################### */
CBOINCGridCellRenderer::CBOINCGridCellRenderer() : wxGridCellStringRenderer() {
}
void CBOINCGridCellRenderer::Draw(wxGrid& grid, wxGridCellAttr& attr, wxDC& dc, const wxRect& rect, int row, int col, bool isSelected) {
DoNormalTextDrawing(grid,attr,dc,rect,row,col,isSelected);
}
/* draws alternating background colours */
void CBOINCGridCellRenderer::DrawBackground(wxGrid& grid,
wxDC& dc,
const wxRect& rect,
int row,
bool isSelected)
{
dc.SetBackgroundMode( wxSOLID );
// grey out fields if the grid is disabled
if( grid.IsEnabled() )
{
if ( isSelected )
{
dc.SetBrush( wxBrush(grid.GetSelectionBackground(), wxSOLID) );
}
else
{
//alternate background colours
if(row % 2 == 0) {
dc.SetBrush(wxBrush(wxColour(240,240,240)));
}
else {
dc.SetBrush(*wxWHITE_BRUSH);
}
}
}
else
{
dc.SetBrush(wxBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE), wxSOLID));
}
dc.SetPen( *wxTRANSPARENT_PEN );
dc.DrawRectangle(rect);
}
void CBOINCGridCellRenderer::DoNormalTextDrawing(wxGrid& grid, wxGridCellAttr& attr, wxDC& dc, const wxRect& rectCell, int row, int col, bool isSelected) {
wxRect rect = rectCell;
rect.Inflate(-1);
// erase only this cells background, overflow cells should have been erased
this->DrawBackground(grid, dc, rectCell, row, isSelected);
int hAlign, vAlign;
attr.GetAlignment(&hAlign, &vAlign);
int overflowCols = 0;
if (attr.GetOverflow())
{
int cols = grid.GetNumberCols();
int best_width = GetBestSize(grid,attr,dc,row,col).GetWidth();
int cell_rows, cell_cols;
attr.GetSize( &cell_rows, &cell_cols ); // shouldn't get here if <=0
if ((best_width > rectCell.width) && (col < cols) && grid.GetTable())
{
int i, c_cols, c_rows;
for (i = col+cell_cols; i < cols; i++)
{
bool is_empty = true;
for (int j=row; j<row+cell_rows; j++)
{
// check w/ anchor cell for multicell block
grid.GetCellSize(j, i, &c_rows, &c_cols);
if (c_rows > 0) c_rows = 0;
if (!grid.GetTable()->IsEmptyCell(j+c_rows, i))
{
is_empty = false;
break;
}
}
if (is_empty)
rect.width += grid.GetColSize(i);
else
{
i--;
break;
}
if (rect.width >= best_width) break;
}
overflowCols = i - col - cell_cols + 1;
if (overflowCols >= cols) overflowCols = cols - 1;
}
if (overflowCols > 0) // redraw overflow cells w/ proper hilight
{
hAlign = wxALIGN_LEFT; // if oveflowed then it's left aligned
wxRect clip = rect;
clip.x += rectCell.width;
// draw each overflow cell individually
int col_end = col+cell_cols+overflowCols;
if (col_end >= grid.GetNumberCols())
col_end = grid.GetNumberCols() - 1;
for (int i = col+cell_cols; i <= col_end; i++)
{
clip.width = grid.GetColSize(i) - 1;
dc.DestroyClippingRegion();
dc.SetClippingRegion(clip);
SetTextColoursAndFont(grid, attr, dc,
grid.IsInSelection(row,i));
grid.DrawTextRectangle(dc, grid.GetCellValue(row, col),
rect, hAlign, vAlign);
clip.x += grid.GetColSize(i) - 1;
}
rect = rectCell;
rect.Inflate(-1);
rect.width++;
dc.DestroyClippingRegion();
}
}
// now we only have to draw the text
SetTextColoursAndFont(grid, attr, dc, isSelected);
//get a real grid class pointer
CBOINCGridCtrl* bgrid = wxDynamicCast(&grid, CBOINCGridCtrl);
//use the overloaded method here
bgrid->DrawTextRectangle(dc, grid.GetCellValue(row, col),
rect, hAlign, vAlign);
}
/* ############# message cell renderer ######################## */
/* Constructor
prio argument - the column index, that holds prio values
*/
CBOINCGridCellMessageRenderer::CBOINCGridCellMessageRenderer(int priocol){
column = priocol;
}
void CBOINCGridCellMessageRenderer::Draw(wxGrid& grid, wxGridCellAttr& attr, wxDC& dc, const wxRect& rect, int row, int col, bool isSelected) {
wxString szError(wxT("Error"));
if(grid.GetCellValue(row,column).Trim(false).IsSameAs(szError)) {
attr.SetTextColour(*wxRED);
}
else {
attr.SetTextColour(*wxBLACK);
}
CBOINCGridCellRenderer::Draw(grid,attr,dc,rect,row,col,isSelected);
}
/* ############# progress cell renderer ########################## */
CBOINCGridCellProgressRenderer::CBOINCGridCellProgressRenderer(int col,bool percentAppending/*=true*/) : CBOINCGridCellRenderer()
{
column = col;
m_bDoPercentAppending = percentAppending;
}
void CBOINCGridCellProgressRenderer::Draw(wxGrid& grid, wxGridCellAttr& attr, wxDC& dc, const wxRect& rect, int row, int col, bool isSelected) {
if(col==column) {
DoProgressDrawing(grid,attr,dc,rect,row,col,isSelected);
}
else {
DoNormalTextDrawing(grid,attr,dc,rect,row,col,isSelected);
}
}
/* paints the progress bar */
void CBOINCGridCellProgressRenderer::DoProgressDrawing(wxGrid& grid, wxGridCellAttr& attr, wxDC& dc, const wxRect& rectCell, int row, int col, bool isSelected) {
wxRect rect = rectCell;
rect.Inflate(-1);
// erase only this cells background, overflow cells should have been erased
this->DrawBackground(grid, dc, rectCell, row, isSelected);
// set text attributes
int hAlign, vAlign;
attr.GetAlignment(&hAlign, &vAlign);
SetTextColoursAndFont(grid, attr, dc, isSelected);
//calculate the two parts of the progress rect
//
double dv = 0.0;
wxString strValue = grid.GetCellValue(row,col);
if(m_bDoPercentAppending) {
strValue = strValue + wxT(" %");
}
// Project view uses the format: %0.0f (%0.2f%%)
// Everyone else uses: %.3f%%
if (strValue.Find(wxT("(")) != wxNOT_FOUND) {
strValue.SubString(strValue.Find(wxT("(")) + 1, strValue.Find(wxT(")")) - 1).ToDouble( &dv );
} else {
strValue.ToDouble ( &dv ); // NOTE: we should do error-checking/reporting here!!
}
wxRect p1(rect);
wxRect p2(rect);
int r = (int)((rect.GetRight()-rect.GetLeft())*dv / 100.0);
p1.SetRight(rect.GetLeft()+r);
p2.SetLeft(rect.GetLeft()+r+1);
p2.SetRight(rect.GetRight()-1);
//start drawing
dc.SetClippingRegion(rect);
wxBrush old = dc.GetBrush();
wxColour progressColour = wxTheColourDatabase->Find(wxT("LIGHT BLUE"));
wxBrush* progressBrush = wxTheBrushList->FindOrCreateBrush(progressColour);
wxPen* progressPen = wxThePenList->FindOrCreatePen(progressColour,1,wxSOLID);
//draw the outline rectangle
dc.SetBrush(*wxTRANSPARENT_BRUSH);
dc.SetPen(*progressPen);
dc.DrawRectangle(rect);
// Draw the left part
dc.SetBrush(*progressBrush);
dc.DrawRectangle(p1);
//draw the right part
dc.SetBrush(old);
dc.DrawRectangle(p2);
//
dc.DestroyClippingRegion();
// draw the text
grid.DrawTextRectangle(dc, strValue, rect, hAlign, vAlign);
}
/* enables or disbale the appendign of the percent sign to the progress text */
void CBOINCGridCellProgressRenderer::SetPercentAppending(bool enable/*=true*/) {
m_bDoPercentAppending = enable;
}
/* ############ GridTable class ############ */
CBOINCGridTable::CBOINCGridTable(int rows, int cols) : wxGridStringTable(rows,cols) {
//init column sort types with CST_STRING
for(int i=0; i< cols; i++) {
arrColumnSortTypes.Add(CST_STRING);
}
}
CBOINCGridTable::~CBOINCGridTable() {
}
void CBOINCGridTable::SetColumnSortType(int col,int sortType/*=CST_STRING*/) {
if(col>=0 && col < ((int)arrColumnSortTypes.GetCount())) {
arrColumnSortTypes[col] = sortType;
}
}
void CBOINCGridTable::SortData(int col,bool ascending) {
//valid column index ?
if(col<0) {
return;
}
wxArrayString arColValues;
//get the values of the sorting column
for(int i=0; i < this->GetNumberRows(); i++) {
arColValues.Add(this->GetValue(i,col));
}
//sort the sorting column values
reverseCompareOrder = !ascending;
//decide, which sort function to call
switch(arrColumnSortTypes[col]) {
case CST_FLOAT:
arColValues.Sort(CompareFloatString);
break;
case CST_LONG:
arColValues.Sort(CompareLongString);
break;
case CST_TIME:
arColValues.Sort(CompareTimeString);
break;
case CST_DATETIME:
arColValues.Sort(CompareDateString);
break;
default:
arColValues.Sort(CompareStringStringNoCase);
break;
}
//rebuild the underlaying 2-dimensional string array
wxGridStringArray newArray;
for(unsigned int i=0; i< arColValues.GetCount();i++) {
//find the original row index
int indexold = FindRowIndexByColValue(col,arColValues[i],-1,arColValues[i]);
wxArrayString rowArray;
for(int j=0; j < this->GetNumberCols(); j++) {
rowArray.Add(this->GetValue(indexold,j));
//delete the value in the original string array
//to prevent finding the wrong index in case of identical values
//f.e. the project name is the same for multiple work units or transfers
this->SetValue(indexold,j,wxEmptyString);
}
newArray.Add(rowArray);
}
//fill the table data array with the sorted values
for(unsigned int i=0; i< newArray.GetCount(); i++) {
for(unsigned int j=0; j < newArray[i].GetCount();j++) {
this->SetValue(i,j,newArray[i][j]);
}
}
}
/* finds the first row index for the cell with value value1 in column col1 and value2 in col2
ignores col2 and value2 if col2 < 0
!! only use this (outside sorting method) with a pair of columns, that have unique value pairs !!
*/
int CBOINCGridTable::FindRowIndexByColValue(int col1,wxString& value1,int col2,wxString& value2) {
for(int i=0; i < this->GetNumberRows(); i++) {
if(! this->GetValue(i,col1).IsSameAs(value1)) continue;
if (col2 < 0) return i;
if(this->GetValue(i,col2).IsSameAs(value2)) return i;
}
return -1;
}
/* for convinience purposes only */
CBOINCGridTable* CBOINCGridCtrl::GetTable() {
return wxDynamicCast(wxGrid::GetTable(), CBOINCGridTable);
}