#include #include #include #include #include #include #include "pawstextwrap.h" #include "pawsmanager.h" #include "pawsprefmanager.h" #include "pawscrollbar.h" #include "util/log.h" #include "util/strutil.h" #include "util/psstring.h" #define EDIT_TEXTBOX_MOUSE_SCROLL_AMOUNT 3 #define BLINK_TICKS 1000 pawsMultilineEditTextBox::pawsMultilineEditTextBox(){ blink = true; cursorPosition = 0; cursorLine = 0; topLine=0; vScrollBar = NULL; topLine = 0; clock = CS_QUERY_REGISTRY( PawsManager::GetSingleton().GetObjectRegistry(), iVirtualClock ); blinkTicks = clock->GetCurrentTicks(); // Find the line height; int dummy; GetFont()->GetMaxSize( dummy, lineHeight ); lineHeight -=2; lines.Push("Test text"); // Create the optional scroll bar here as well but hidden. usingScrollBar = false; } pawsMultilineEditTextBox::~pawsMultilineEditTextBox() { } //our editing is based on editable widget, but changed to fit the csArray model of the //wrapped static text widget bool pawsMultilineEditTextBox::OnKeyDown( int code, int key, int modifiers ) { canDrawLines = screenFrame.Height() / lineHeight; bool changed = false; blink = true; int startLoc=0; size_t oldLength=text.Length(); switch ( key ) { case CSKEY_DEL: { if (cursorPosition>=lines[cursorLine].Length()){ if(cursorLine == lines.Length() - 1) //End of text, do nothing break; //Bump to position 0 of the next line cursorLine++; cursorPosition = 0; //continue as if this is where we started, and delete the character } //this if might be unnecessary if ( cursorPosition != (size_t)-1 ) { lines[cursorLine].DeleteAt(cursorPosition); //at this point, it's possible that cursorPosition == curLine.Length } //endif cursor position > 0 changed = true; break; } case CSKEY_BACKSPACE: if ( cursorPosition > 0 ) { cursorPosition--; //Now it's just like the "DEL" case lines[cursorLine].DeleteAt(cursorPosition); } else if (cursorLine > 0) { cursorLine--; cursorPosition = lines[cursorLine].Length()-1; lines[cursorLine].DeleteAt(cursorPosition); } else break; //don't want to mark as 'changed' if nothing changed! changed = true; break; // End case CSKEY_BACKSPACE case CSKEY_LEFT: if ( cursorPosition > 0 ) cursorPosition--; else if (cursorLine > 0) { cursorLine--; if (cursorLinetopLine+canDrawLines) topLine++; } else if (cursorLine == lines.Length()-1 && cursorPosition == lines[cursorLine].Length()-1) { //the case for the very last line, we allow to go to after the last character cursorPosition++; } break; case CSKEY_UP: if (cursorLine>0) cursorLine--; if (cursorLine lines[cursorLine].Length() -1) cursorPosition = lines[cursorLine].Length() -1; break; case CSKEY_DOWN: if (cursorLine < lines.Length()-1) cursorLine++; if (cursorLine>topLine+canDrawLines) topLine++; break; case CSKEY_END: //really should find end of paragraph or EOT if(cursorLine < lines.Length() - 1) cursorPosition=lines[cursorLine].Length()-1; else cursorPosition = lines[cursorLine].Length(); break; case CSKEY_HOME: cursorPosition=0; break; default: if ( !isprint((unsigned char)key) && !(key==CSKEY_ENTER)) { //unhandled non-printing character break; } if ( key == CSKEY_ALT || key == CSKEY_CTRL ) { break; } if (key == CSKEY_ENTER) { //our text flow algorithm will then break this into two lines. lines[cursorLine].Insert( cursorPosition, '\n'); } // else if ( cursorPosition >= lines[cursorLine].Length() ) // { // //hypothetically only happens on the very last line // lines[cursorLine].Append( (char)key ); // } else { lines[cursorLine].Insert( cursorPosition, (char)key ); } //we've inserted a character, move the cursor to keep up :) cursorPosition++; changed = true; } if (oldLength) text.DeleteAt(startLoc,oldLength); text.Insert(startLoc,lines[cursorLine]); if (changed) { //Re-flow the text int endline = cursorLine; LayoutText(lines, cursorLine,endline); //lines.DeleteRange(cursorLine, endline); // for(int i = 0; i < newlines.Length(); i++) lines.Insert(cursorLine, newlines[i]); if(lines.Length() > canDrawLines && vScrollBar){ vScrollBar->SetMaxValue((int)lines.Length() - canDrawLines ); if( !usingScrollBar) { usingScrollBar = true; vScrollBar->Show(); vScrollBar->SetCurrentValue(0); } } if(lines.Length() <= canDrawLines && usingScrollBar){ // turn off scrollbar usingScrollBar = false; vScrollBar->Hide(); } if (subscribedVar) PawsManager::GetSingleton().Publish(subscribedVar, text); parent->OnChange(this); } if ( isprint((unsigned char)key) ) return true; if (!(code== CSKEY_ENTER)) { pawsWidget::OnKeyDown( code, key, modifiers ); } return true; } bool pawsMultilineEditTextBox::OnMouseDown( int button, int modifiers, int x, int y ) { canDrawLines = screenFrame.Height() / lineHeight; if (button == csmbWheelUp) { if (vScrollBar) vScrollBar->SetCurrentValue(vScrollBar->GetCurrentValue() - EDIT_TEXTBOX_MOUSE_SCROLL_AMOUNT); return true; } else if (button == csmbWheelDown) { if (vScrollBar) vScrollBar->SetCurrentValue(vScrollBar->GetCurrentValue() + EDIT_TEXTBOX_MOUSE_SCROLL_AMOUNT); return true; } x -= (screenFrame.xmin+4); // Adjust x to be relative to the text box //Force the cursor to blink blink = true; // Find the line the mouse is on and move the cursor there cursorLine=(y-screenFrame.ymin)/lineHeight+topLine; if (cursorLine >= lines.Length()){ cursorLine = lines.Length()-1; cursorPosition = lines[(int)lines.Length()-1].Length();//*after* the last letter of last line return true; } // Basic comparison to see if it was clicked before the line if (x<=0) { cursorPosition=0; return true; } //should position cursor before clicked letter cursorPosition = GetFont()->GetLength(lines[cursorLine],x); return pawsWidget::OnMouseDown( button, modifiers, x ,y); } void pawsMultilineEditTextBox::OnUpdateData(const char *dataname,PAWSData& value) { // This is called automatically whenever subscribed data is published. SetText( value.GetStr(), false ); } bool pawsMultilineEditTextBox::OnScroll( int direction, pawsScrollBar* widget ) { topLine = (int)widget->GetCurrentValue(); return true; } //going in, startline is the first line that might have had its contents changed //and endline is the last //coming out, endLine is the last line that was actually reflowed, which will be >= the incoming endLine //the returned array will of course be the text to replace startLine..endLine with csArray pawsMultilineEditTextBox::LayoutText(const csArray& text, int startLine, int& endLine){ //start with the current line and build up a new array of replacement lines //to replace startline..endline with //relies on font metrics and some usableWidth parameter csRef font = GetFont(); csArray newText; //initialize this... newText.Push(""); int srcLine = startLine; //the line we're grabbing text from int destLine = 0; int startWidth = screenFrame.Width(); //if(usingScrollBar) startWidth = startWidth - 24; int remainingWidth = startWidth; while(srcLine < (int)text.Length()){ size_t srcPos = 0; //the first char that hasn't yet been grabbed. while(srcPos < text[srcLine].Length()){ //try to grab a word. //if word doesn't fit go to next line UNLESS this is the first word on a line then we do bad thing size_t a = text[srcLine].FindFirst(" \t\n",srcPos); //if a is '-1', the char wasn't found so we grab to end of the line, else we grab up to and including //the found character csString x = text[srcLine].Slice(srcPos, a==(size_t)-1?(size_t)-1: a-srcPos+1); //we should never get a 0 length string.. I think int width, height; //we don't need height font->GetDimensions(x,width,height); //if it'll fit.. if(width <= remainingWidth){ //then attach it newText[destLine].Append(x); remainingWidth -= width; } else { destLine++; if(width < startWidth){ //make a new line, and add it newText.Push(x); remainingWidth = startWidth - width; //adjust cursor position if(cursorLine > srcLine) cursorLine++; else if(cursorLine == srcLine && cursorPosition > srcPos){ cursorLine++; cursorPosition = cursorPosition-srcPos; } } else { //the case where we have a single unbroken word wider than our page size_t max_subword_pos = font->GetLength(x,remainingWidth); newText[destLine].Append(x.Slice(0, max_subword_pos-1)); //this should be a loop for a multi-line word, this will be the last edge //case to handle newText.Push(x.Slice(max_subword_pos,(size_t)-1)); remainingWidth = 2*startWidth-width; } } //does our word end in a newline? if(x[x.Length()-1] == '\n'){ //standard 'create a new destination line' procedure destLine++; newText.Push(""); remainingWidth = startWidth; //adjust cursor position if(cursorLine > srcLine) cursorLine++; else if(cursorLine == srcLine && cursorPosition > a){ cursorLine++; cursorPosition = cursorPosition- a - 1; } } if(a != (size_t) -1) srcPos = a + 1; else break; //a was -1, so there was no ' ' or \n before the end of the line, go to the next source line } //ok, so after chewing up a source line, move to the next one. How hard can it be? srcLine++; } endLine = srcLine; //this is contrary to the initial intent of this function, but it seems to be the more practical route lines.DeleteRange(startLine, endLine); for(int i = 0; i < newText.Length(); i++) lines.Insert(startLine+i, newText[i]); return newText; } //our draw is based on static, wrapped widget void pawsMultilineEditTextBox::Draw() { canDrawLines = screenFrame.Height() / lineHeight; if(!vScrollBar && usingScrollBar){ vScrollBar = (pawsScrollBar*) PawsManager::GetSingleton().CreateWidget ( "pawsScrollBar" ); vScrollBar->SetParent( this ); vScrollBar->SetRelativeFrame( screenFrame.Width() - 24, 0, 24, screenFrame.Height() - 12 ); int attach = ATTACH_TOP | ATTACH_BOTTOM | ATTACH_RIGHT; vScrollBar->SetAttachFlags( attach ); vScrollBar->PostSetup(); vScrollBar->SetTickValue( 1.0 ); AddChild( vScrollBar ); } if ( clock->GetCurrentTicks() - blinkTicks > BLINK_TICKS ) { blink = !blink; blinkTicks = clock->GetCurrentTicks(); } pawsWidget::Draw(); pawsWidget::ClipToParent(); int drawX = screenFrame.xmin; int drawY = screenFrame.ymin; for (size_t x = topLine; x < (topLine+canDrawLines); x++ ) { if ( x >= lines.Length() ) return; csString temp; //chomp newline characters if(lines[x].Length() > 0){ if(lines[x].GetAt(lines[x].Length()-1) == '\n') temp = lines[x].Slice(0,lines[x].Length()-1); else temp = lines[x]; DrawWidgetText( (const char*)temp, drawX, drawY); } if (blink && hasFocus && x==cursorLine) // Draw the cursor { int width, height; GetFont()->GetDimensions( lines[x].Slice(0,cursorPosition).GetDataSafe(), width, height ); graphics2D->DrawLine( (float)(drawX + width + 1), (float)(drawY), (float)(drawX + width + 1), (float)(drawY + lineHeight), GetFontColour() ); } drawY+=lineHeight; } } bool pawsMultilineEditTextBox::SelfPopulate( iDocumentNode *node) { if (node->GetAttributeValue("text")) { SetText(node->GetAttributeValue("text")); return true; } else return false; } bool pawsMultilineEditTextBox::Setup( iDocumentNode* node ) { csRef textNode = node->GetNode( "text" ); if ( textNode ) { csRef textAttribute = textNode->GetAttribute("string"); if ( textAttribute ) { SetText( textAttribute->GetValue() ); } } return true; } void pawsMultilineEditTextBox::SetText( const char* newText, bool publish ) { if (publish && subscribedVar) PawsManager::GetSingleton().Publish(subscribedVar, newText); canDrawLines = screenFrame.Height() / lineHeight; cursorPosition = 0; cursorLine = 0; topLine = 0; //Try layout without scroll, if we're over the limit then we have to re-layout in a //slightly narrower space usingScrollBar = false; lines.Empty(); lines.Push(newText); int endLine = 0; LayoutText(lines,0,endLine); if(lines.Length()>canDrawLines){ usingScrollBar = true; lines.Empty(); lines.Push(newText); LayoutText(lines, 0, endLine); if (vScrollBar) { vScrollBar->Show(); vScrollBar->SetMaxValue(lines.Length() - canDrawLines ); vScrollBar->SetCurrentValue(0); } } } const char* pawsMultilineEditTextBox::GetText() { text = ""; for(int i = 0; i