#include "stdafx.h"
#include "ip_view.h"
using namespace editwing;
using namespace editwing::view;
//=========================================================================
//---- ip_draw.cpp 描画・他
//
// 折り返しとか色とかを考慮しつつ、実際に描画処理を
// 行うのがここ。あとメッセージディスパッチャなども
// ついでにこのファイルに。^^;
//
//---- ip_text.cpp 文字列操作・他
//---- ip_parse.cpp キーワード解析
//---- ip_wrap.cpp 折り返し
//---- ip_scroll.cpp スクロール
//---- ip_cursor.cpp カーソルコントロール
//=========================================================================
//-------------------------------------------------------------------------
// Viewの初期化・解放
//-------------------------------------------------------------------------
View::ClsName
View::className_ = TEXT("EditWing View");
View::View( doc::Document& d, HWND wnd )
: WndImpl( className_, WS_CHILD|WS_VISIBLE|WS_VSCROLL|WS_HSCROLL )
, doc_ ( d.impl() )
{
static bool ClassRegistered = false;
if( !ClassRegistered )
{
// 初回構築時のみ、クラス登録を行う
ClassRegistered = true;
WNDCLASSEX wc = {0};
wc.lpszClassName = className_;
wc.style = CS_DBLCLKS | CS_OWNDC;
wc.hCursor = app().LoadOemCursor( IDC_IBEAM );
// GlobalIMEを有効にする
ATOM a = WndImpl::Register( &wc );
ime().FilterWindows( &a, 1 );
}
// 窓作成
Create( NULL, wnd );
}
View::~View()
{
// 窓破棄
Destroy();
}
void View::on_create( CREATESTRUCT* cs )
{
impl_ = new ViewImpl( *this, doc_ );
doc_.AddHandler( this );
}
void View::on_destroy()
{
doc_.DelHandler( this );
impl_ = NULL;
}
//-------------------------------------------------------------------------
// サブオブジェクトにそのまま回す
//-------------------------------------------------------------------------
void View::SetWrapType( int wt )
{ impl_->SetWrapType( wt ); }
void View::ShowLineNo( bool show )
{ impl_->ShowLineNo( show ); }
void View::SetFont( const VConfig& vc )
{ impl_->SetFont( vc ); }
void View::on_keyword_change()
{ ::InvalidateRect( hwnd(), NULL, FALSE ); }
void View::on_text_update
( const DPos& s, const DPos& e, const DPos& e2, bool bAft, bool mCur )
{ impl_->on_text_update( s, e, e2, bAft, mCur ); }
Cursor& View::cur()
{ return impl_->cur(); }
LRESULT View::on_message( UINT msg, WPARAM wp, LPARAM lp )
{
switch( msg )
{
case WM_PAINT:{
PAINTSTRUCT ps;
::BeginPaint( hwnd(), &ps );
impl_->on_paint( ps );
::EndPaint( hwnd(), &ps );
}break;
case WM_SIZE:
impl_->on_view_resize( LOWORD(lp), HIWORD(lp) );
break;
case WM_HSCROLL:
impl_->on_hscroll( LOWORD(wp) );
break;
case WM_VSCROLL:
impl_->on_vscroll( LOWORD(wp) );
break;
case WM_MOUSEWHEEL:
impl_->on_wheel( HIWORD(wp) );
break;
case WM_SETFOCUS:
cur().on_setfocus();
break;
case WM_KILLFOCUS:
cur().on_killfocus();
break;
case WM_TIMER:
cur().on_timer();
break;
case WM_KEYDOWN:
cur().on_keydown( (int)wp, lp );
break;
case WM_CHAR:
cur().on_char( (TCHAR)wp );
break;
case WM_LBUTTONDOWN:
cur().on_lbutton_down( LOWORD(lp), HIWORD(lp), (wp&MK_SHIFT)!=0 );
break;
case WM_LBUTTONUP:
cur().on_lbutton_up( LOWORD(lp), HIWORD(lp) );
break;
case WM_LBUTTONDBLCLK:
cur().on_lbutton_dbl( LOWORD(lp), HIWORD(lp) );
break;
case WM_MOUSEMOVE:
cur().on_mouse_move( LOWORD(lp), HIWORD(lp) );
break;
case WM_CONTEXTMENU:
if( !cur().on_contextmenu( LOWORD(lp), HIWORD(lp) ) )
return WndImpl::on_message( msg, wp, lp );
break;
case WM_IME_REQUEST:
switch( wp )
{
case IMR_RECONVERTSTRING:
return cur().on_ime_reconvertstring(
reinterpret_cast<RECONVERTSTRING*>(lp) );
case IMR_CONFIRMRECONVERTSTRING:
return cur().on_ime_confirmreconvertstring(
reinterpret_cast<RECONVERTSTRING*>(lp) );
}
break;
case WM_IME_STARTCOMPOSITION:
cur().on_ime_composition( 0 );
return WndImpl::on_message( msg, wp, lp );
case WM_IME_COMPOSITION:
cur().on_ime_composition( lp );
if( lp&GCS_RESULTSTR )
break;
// fall through...
default:
return WndImpl::on_message( msg, wp, lp );
}
return 0;
}
//-------------------------------------------------------------------------
// 線を引くとか四角く塗るとか、そーいう基本的な処理
//-------------------------------------------------------------------------
Painter::Painter( HDC hdc, const VConfig& vc )
: dc_ ( hdc )
, font_ ( ::CreateFontIndirect( &vc.font ) )
, pen_ ( ::CreatePen( PS_SOLID, 0, vc.color[CTL] ) )
, brush_ ( ::CreateSolidBrush( vc.color[BG] ) )
, widthTable_( new int[65536] )
{
// 制御文字を描画するか否か?のフラグを記憶
for( int i=0; i<countof(scDraw_); ++i )
scDraw_[i] = vc.sc[i];
// 文字色を記憶
for( int i=0; i<countof(colorTable_); ++i )
colorTable_[i] = vc.color[i];
colorTable_[3] = vc.color[CMT];
// DCにセット
::SelectObject( dc_, font_ );
::SelectObject( dc_, pen_ );
::SelectObject( dc_, brush_ );
::SetBkMode( dc_, TRANSPARENT );
::SetMapMode( dc_, MM_TEXT );
// 文字幅テーブル初期化(ASCII範囲の文字以外は遅延処理)
memFF( widthTable_, 65536*sizeof(int) );
::GetCharWidthW( dc_, L' ', L'~', widthTable_+L' ' );
widthTable_[L'\t'] = W() * Max(1,vc.tabstep);
// 下位サロゲートは文字幅ゼロ
mem00( widthTable_+0xDC00, (0xE000 - 0xDC00)*sizeof(int) );
// 数字の最大幅を計算
figWidth_ = 0;
for( unicode ch=L'0'; ch<=L'9'; ++ch )
if( figWidth_ < widthTable_[ch] )
figWidth_ = widthTable_[ch];
// 高さの情報
TEXTMETRIC met;
::GetTextMetrics( dc_, &met );
height_ = met.tmHeight;
// LOGFONT
::GetObject( font_, sizeof(LOGFONT), &logfont_ );
}
Painter::~Painter()
{
// 適当な別オブジェクトをくっつけて自分を解放する
::SelectObject( dc_, ::GetStockObject( OEM_FIXED_FONT ) );
::SelectObject( dc_, ::GetStockObject( BLACK_PEN ) );
::SelectObject( dc_, ::GetStockObject( WHITE_BRUSH ) );
::DeleteObject( font_ );
::DeleteObject( pen_ );
::DeleteObject( brush_ );
delete [] widthTable_;
}
inline void Painter::CharOut( unicode ch, int x, int y )
{
::TextOutW( dc_, x, y, &ch, 1 );
}
inline void Painter::StringOut
( const unicode* str, int len, int x, int y )
{
::TextOutW( dc_, x, y, str, len );
}
inline void Painter::SetColor( int i )
{
::SetTextColor( dc_, colorTable_[i] );
}
inline void Painter::Fill( const RECT& rc )
{
::FillRect( dc_, &rc, brush_ );
}
inline void Painter::Invert( const RECT& rc )
{
::InvertRect( dc_, &rc );
}
inline void Painter::DrawLine( int x1, int y1, int x2, int y2 )
{
::MoveToEx( dc_, x1, y1, NULL );
::LineTo( dc_, x2, y2 );
}
inline void Painter::SetClip( const RECT& rc )
{
::IntersectClipRect( dc_, rc.left, rc.top, rc.right, rc.bottom );
}
inline void Painter::ClearClip()
{
::SelectClipRgn( dc_, NULL );
}
void Painter::DrawHSP( int x, int y, int times )
{
// 半角スペース記号(ホチキスの芯型)を描く
const int w=Wc(L' '), h=H();
POINT pt[4] = {
{ x , y+h-4 },
{ x , y+h-2 },
{ x+w-3, y+h-2 },
{ x+w-3, y+h-5 }
};
while( times-- )
{
if( 0 <= pt[3].x )
::Polyline( dc_, pt, countof(pt) );
pt[0].x += w;
pt[1].x += w;
pt[2].x += w;
pt[3].x += w;
}
}
void Painter::DrawZSP( int x, int y, int times )
{
// 全角スペース記号(平たい四角)を描く
const int w=Wc(0x3000/*L' '*/), h=H();
POINT pt[4] = {
{ x , y+h-4 },
{ x , y+h-2 },
{ x+w-3, y+h-2 },
{ x+w-3, y+h-4 }
};
while( times-- )
{
if( 0 <= pt[3].x )
::Polygon( dc_, pt, countof(pt) );
pt[0].x += w;
pt[1].x += w;
pt[2].x += w;
pt[3].x += w;
}
}
//-------------------------------------------------------------------------
// 再描画したい範囲を Invalidate する。
//-------------------------------------------------------------------------
void ViewImpl::ReDraw( ReDrawType r, const DPos* s )
{
// まずスクロールバーを更新
UpdateScrollBar();
switch( r )
{
case ALL: // 全画面
::InvalidateRect( hwnd_, NULL, FALSE );
break;
case LNAREA: // 行番号表示域のみ
if( lna() > 0 )
{
RECT rc = { 0, 0, lna(), bottom() };
::InvalidateRect( hwnd_, &rc, FALSE );
}
break;
case LINE: // 指定した行の後半
case AFTER: // 指定した行以下全部
{
DPos st = ( s->ad==0 ? *s : doc_.leftOf(*s,true) );
InvalidateView( st, r==AFTER );
}
}
}
//-------------------------------------------------------------------------
// WM_PAINTハンドラ
//-------------------------------------------------------------------------
void ViewImpl::on_paint( const PAINTSTRUCT& ps )
{
// 描画範囲の情報を詳しく取得
Painter& p = cvs_.getPainter();
VDrawInfo v( ps.rcPaint );
GetDrawPosInfo( v );
if( ps.rcPaint.right <= lna() )
{
// case A: 行番号表示域のみ更新
DrawLNA( v, p );
}
else if( lna() <= ps.rcPaint.left )
{
// case B: テキスト表示域のみ更新
DrawTXT( v, p );
}
else
{
// case C: 両方更新
DrawLNA( v, p );
p.SetClip( cvs_.zone() );
DrawTXT( v, p );
p.ClearClip();
}
}
//-------------------------------------------------------------------------
// 行番号ゾーン描画
//-------------------------------------------------------------------------
void ViewImpl::DrawLNA( const VDrawInfo& v, Painter& p )
{
//
// 文字列のまま足し算を行うルーチン
//
struct strint {
strint( ulong num ) {
int i=11;
while( num ) digit[--i] = (unicode)(L'0'+(num%10)), num/=10;
while( i ) digit[--i] = L' ';
}
void operator++() {
int i=10;
do
if( digit[i] == L'9' )
digit[i] = L'0';
else
{ ++digit[i]; return; }
while( digit[--i] != L' ' );
digit[i] = L'1';
}
void Output( Painter& f, int x, int y ) {
for( unicode* p=digit+10; *p!=L' '; --p,x-=f.F() )
f.CharOut( *p, x, y );
}
unicode digit[11];
};
// 背面消去
RECT rc = { v.rc.left, v.rc.top, lna(), v.rc.bottom };
p.Fill( rc );
if( v.rc.top < v.YMAX )
{
// 境界線表示
int line = lna() - p.F()/2;
p.DrawLine( line, v.rc.top, line, v.YMAX );
p.SetColor( LN );
// 行番号表示
strint n = v.TLMIN+1;
int y = v.YMIN;
int edge = lna() - p.F()*2;
for( ulong i=v.TLMIN; y<v.YMAX; ++i,++n )
{
n.Output( p, edge, y );
y += p.H() * rln(i);
}
}
}
//-------------------------------------------------------------------------
// テキスト描画
//-------------------------------------------------------------------------
inline void ViewImpl::Inv( int y, int xb, int xe, Painter& p )
{
RECT rc = {
Max( left(), xb ), y,
Min( right(), xe ), y+p.H()-1
};
p.Invert( rc );
}
void ViewImpl::DrawTXT( const VDrawInfo v, Painter& p )
{
// 定数1
// const int TAB = p.T();
const int H = p.H();
const ulong TLM = doc_.tln()-1;
// 作業用変数1
RECT a = { 0, v.YMIN, 0, v.YMIN+p.H() };
int clr = -1;
register int x, x2;
register ulong i, i2;
// 論理行単位のLoop
for( ulong tl=v.TLMIN; a.top<v.YMAX; ++tl )
{
// 定数2
const unicode* str = doc_.tl(tl);
const uchar* flg = doc_.pl(tl);
const int rYMAX = Min<int>( v.YMAX, a.top+rln(tl)*H );
// 作業用変数2
ulong stt=0, end, t, n;
// 表示行単位のLoop
for( ulong rl=0; a.top<rYMAX; ++rl,a.top+=H,a.bottom+=H,stt=end )
{
// 作業用変数3
end = rlend(tl,rl);
if( a.bottom<=v.YMIN )
continue;
// テキストデータ描画
for( x2=x=0,i2=i=stt; x<=v.XMAX && i<end; x=x2,i=i2 )
{
// n := 次のTokenの頭
t = (flg[i]>>5);
n = i + t;
if( n >= end )
n = end;
else if( t==7 || t==0 )
while( n<end && (flg[n]>>5)==0 )
++n;
// x2, i2 := このTokenの右端
i2 ++;
x2 = (str[i]==L'\t' ? p.nextTab(x2) : x2+p.W(&str[i]));
// if( x2 <= v.XMIN )
// x=x2, i=i2;
while( i2<n && x2<=v.XMAX )
x2 += p.W( &str[i2++] );
// 再描画すべき範囲と重なっていない
if( x2<=v.XMIN )
continue;
// x, i := このトークンの左端
if( x<v.XMIN )
{
// tabの分が戻りすぎ?
x = x2, i = i2;
while( v.XMIN<x )
x -= p.W( &str[--i] );
}
// 背景塗りつぶし
a.left = x + v.XBASE;
a.right = x2 + v.XBASE;
p.Fill( a );
// 描画
switch( str[i] )
{
case L'\t':
if( p.sc(scTAB) )
{
p.SetColor( clr=CTL );
for( ; i<i2; ++i, x=p.nextTab(x) )
p.CharOut( L'>', x+v.XBASE, a.top );
}
break;
case L' ':
if( p.sc(scHSP) )
p.DrawHSP( x+v.XBASE, a.top, i2-i );
break;
case 0x3000://L' ':
if( p.sc(scZSP) )
p.DrawZSP( x+v.XBASE, a.top, i2-i );
break;
default:
if( clr != (flg[i]&3) )
p.SetColor( clr=(flg[i]&3) );
p.StringOut( str+i, i2-i, x+v.XBASE, a.top );
//p.StringOut( str+i, i2-i, x+v.XBASE, a.top );
// 何故だか2度描きしないとうまくいかん…
break;
}
}
// 選択範囲だったら反転
if( v.SYB<=a.top && a.top<=v.SYE )
Inv( a.top, a.top==v.SYB?v.SXB:(v.XBASE),
a.top==v.SYE?v.SXE:(v.XBASE+x), p );
// 行末より後ろの余白を背景色塗
if( x<v.XMAX )
{
a.left = v.XBASE + Max( v.XMIN, x );
a.right= v.XBASE + v.XMAX;
p.Fill( a );
}
}
// 行末記号描画反転
SpecialChars sc = (tl==TLM ? scEOF : scEOL);
if( i==doc_.len(tl) && -32768<x+v.XBASE )
{
if( p.sc(sc) )
{
static const unicode* const sstr[] = { L"[EOF]", L"/" };
static const int slen[] = { 5, 1 };
p.SetColor( clr=CTL );
p.StringOut( sstr[sc], slen[sc], x+v.XBASE, a.top-H );
}
if( v.SYB<a.top && a.top<=v.SYE && sc==scEOL )
Inv( a.top-H, x+v.XBASE, x+v.XBASE+p.Wc(L'/'), p );
}
}
// EOF後余白を背景色塗
if( a.top < v.rc.bottom )
{
a.left = v.rc.left;
a.right = v.rc.right;
a.bottom = v.rc.bottom;
p.Fill( a );
}
}