2 * This file is part of ShapeFusion (Copyright 2000 Tito Dal Canton)
4 * ShapeFusion is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
9 * ShapeFusion is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with ShapeFusion; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 #include "wx/dcbuffer.h"
20 #include "SequenceView.h"
21 #include "ShapesDocument.h"
22 #include "utilities.h"
24 DEFINE_EVENT_TYPE(wxEVT_SEQUENCEVIEW)
26 BEGIN_EVENT_TABLE(SequenceView, wxScrolledWindow)
27 EVT_PAINT(SequenceView::OnPaint)
28 EVT_SIZE(SequenceView::OnSize)
29 EVT_LEFT_DOWN(SequenceView::OnMouseDown)
30 EVT_LEFT_DCLICK(SequenceView::OnMouseDoubleClick)
31 EVT_RIGHT_DOWN(SequenceView::OnMouseDown)
32 EVT_LEFT_UP(SequenceView::OnMouseUp)
33 EVT_KEY_DOWN(SequenceView::OnKeyDown)
34 EVT_MOTION(SequenceView::OnMouseMove)
37 const char arrow_left_bits[] = { 0x08, 0x0c, 0x0e, 0x0f, 0x0e, 0x0c, 0x08 },
38 arrow_right_bits[] = { 0x01, 0x03, 0x07, 0x0f, 0x07, 0x03, 0x01 };
40 const int THUMB_ARROW_MARGIN = 2;
41 const int THUMB_ARROW_BUTTON_MARGIN = 2;
43 SequenceView::SequenceView(wxWindow *parent, wxWindowID id):
44 wxScrolledWindow(parent, id, wxDefaultPosition, wxDefaultSize, wxSUNKEN_BORDER | wxFULL_REPAINT_ON_RESIZE),
45 mColorTable(NULL), mThumbnailSize(64), mMargin(7), mPrevBtnIcon(arrow_left_bits, 4, 7),
46 mNextBtnIcon(arrow_right_bits, 4, 7), mAutoSize(false), mSelection(-1), mFrameIndexes(NULL), mView(0)
48 SetBackgroundStyle(wxBG_STYLE_CUSTOM);
49 SetBackgroundColour(wxColour(255, 255, 255));
50 mThumbnailPen.SetColour(200, 200, 200);
51 mSelectionPen.SetColour(0, 0, 0);
52 mSelectionPen.SetWidth(3);
53 mAngleBrush.SetColour(200, 200, 200);
55 mAngleLabelSpace = 30; // TODO get this from real font extent
56 mWhiteTransparency = true;
59 mAnimationType = UNANIMATED;
62 void SequenceView::OnPaint(wxPaintEvent& e)
64 wxAutoBufferedPaintDC tempdc(this);
68 if (mFrameIndexes == NULL)
73 GetClientSize(&cw, &ch);
74 CalcUnscrolledPosition(0, 0, &rx, &ry);
75 // get mouse position in scrolled coords
76 mouse = ScreenToClient(wxGetMousePosition());
80 tempdc.SetPen(mThumbnailPen);
81 tempdc.SetBrush(*wxTRANSPARENT_BRUSH);
82 tempdc.SetTextForeground(wxColour(0, 0, 0));
83 tempdc.SetBackgroundMode(wxTRANSPARENT);
84 for (unsigned int i = 0; i < mThumbnails.size(); i++) {
85 int x = mThumbnailPositions[i].x,
86 y = mThumbnailPositions[i].y,
87 bw = mThumbnails[i].GetWidth(),
88 bh = mThumbnails[i].GetHeight();
90 if ((int)i == mSelection) {
91 tempdc.DrawBitmap(mThumbnails[i], x + mThumbnailSize/2 - bw/2, y + mThumbnailSize/2 - bh/2);
92 tempdc.SetPen(mSelectionPen);
93 tempdc.DrawRectangle(x-2, y-2, mThumbnailSize+4, mThumbnailSize+4);
94 tempdc.SetPen(mThumbnailPen);
96 tempdc.DrawRectangle(x-1, y-1, mThumbnailSize+2, mThumbnailSize+2);
97 tempdc.DrawBitmap(mThumbnails[i], x + mThumbnailSize/2 - bw/2, y + mThumbnailSize/2 - bh/2);
99 // draw arrow buttons if mouse is over this thumbnail
100 wxRect tnrect(x, y, mThumbnailSize, mThumbnailSize);
102 if (tnrect.Contains(mouse)) {
103 wxString label = wxString::Format(wxT("%d"), (*mFrameIndexes)[i]);
106 tempdc.SetFont(*wxSMALL_FONT);
107 tempdc.DrawBitmap(mPrevBtnIcon, x + 2, y + mThumbnailSize - mPrevBtnIcon.GetHeight() - 2);
108 tempdc.DrawBitmap(mNextBtnIcon, x + mThumbnailSize - mNextBtnIcon.GetWidth() - 2, y + mThumbnailSize - mNextBtnIcon.GetHeight() - 2);
109 tempdc.GetTextExtent(label, &labelw, &labelh);
110 tempdc.SetTextForeground(wxColour(255, 255, 255));
111 tempdc.DrawText(label, x + (mThumbnailSize-labelw) / 2 - 1, y + mThumbnailSize - labelh - 2);
112 tempdc.DrawText(label, x + (mThumbnailSize-labelw) / 2 + 1, y + mThumbnailSize - labelh - 2);
113 tempdc.DrawText(label, x + (mThumbnailSize-labelw) / 2, y + mThumbnailSize - labelh - 2 - 1);
114 tempdc.DrawText(label, x + (mThumbnailSize-labelw) / 2, y + mThumbnailSize - labelh - 2 + 1);
115 tempdc.SetTextForeground(wxColour(0, 0, 0));
116 tempdc.DrawText(label, x + (mThumbnailSize-labelw) / 2, y + mThumbnailSize - labelh - 2);
119 if (mThumbnails.size() > 0 && mAnimationType != UNANIMATED && mAnimationType != ANIMATED_1) {
122 wxString deg2(deg, wxConvISO8859_1); // FIXME does not work on OS X (draws infinity char)
124 tempdc.SetFont(*wxSMALL_FONT);
125 tempdc.SetBrush(mAngleBrush);
126 for (int i = 0; i < mNumberOfViews; i++) {
127 wxString label = wxString::Format(wxT("%d"), i * 360 / mNumberOfViews) + deg2;
130 tempdc.GetTextExtent(label, &tw, &th);
131 tempdc.DrawRectangle(mMargin-1, mMargin + i * (mThumbnailSize + mMargin) - 1, mAngleLabelSpace+2, mThumbnailSize+2);
132 tempdc.DrawText(label, mMargin + mAngleLabelSpace - tw, mMargin + i * (mThumbnailSize + mMargin) + mThumbnailSize/2 - th/2);
137 // widget resized, recalculate virtual size to correctly wrap thumbnails
138 void SequenceView::OnSize(wxSizeEvent& e)
143 void SequenceView::OnMouseDown(wxMouseEvent& e)
149 mouse = e.GetLogicalPosition(dc);
150 switch (e.GetButton()) {
151 case wxMOUSE_BTN_LEFT:
152 // handle frame selection
154 int new_selection = -1;
156 for (unsigned int i = 0; i < mThumbnailPositions.size(); i++) {
157 wxRect test(mThumbnailPositions[i].x, mThumbnailPositions[i].y,
158 mThumbnailSize, mThumbnailSize);
160 if (test.Contains(mouse)) {
165 if (new_selection != mSelection) {
166 mSelection = new_selection;
169 // send selection event
170 wxCommandEvent event(wxEVT_SEQUENCEVIEW, GetId());
172 event.SetEventObject(this);
173 event.SetInt(mSelection);
174 GetEventHandler()->ProcessEvent(event);
178 case wxMOUSE_BTN_RIGHT:
184 void SequenceView::OnMouseUp(wxMouseEvent& e)
190 mouse = e.GetLogicalPosition(dc);
191 switch (e.GetButton()) {
192 case wxMOUSE_BTN_LEFT:
193 // clicks on the little arrows change the associated frame index
194 // FIXME mouse tracking like real buttons (starting with a mouse down)
196 // find wether the mouse was released over a thumbnail
197 int touched_thumbnail = -1;
199 for (unsigned int i = 0; i < mThumbnailPositions.size(); i++) {
200 wxRect test(mThumbnailPositions[i].x, mThumbnailPositions[i].y,
201 mThumbnailSize, mThumbnailSize);
203 if (test.Contains(mouse)) {
204 touched_thumbnail = i;
208 if (touched_thumbnail > -1) {
209 // find wether an arrow was touched
210 if (GetPrevArrowButtonRect(touched_thumbnail).Contains(mouse)) {
211 if ((*mFrameIndexes)[touched_thumbnail] > -1) {
212 (*mFrameIndexes)[touched_thumbnail]--;
213 RebuildThumbnail(touched_thumbnail);
216 static_cast<ShapesDocument*>(mView->GetDocument())->Modify(true);
219 } else if (GetNextArrowButtonRect(touched_thumbnail).Contains(mouse)) {
220 if ((*mFrameIndexes)[touched_thumbnail] < ((int)mFrames.size()-1)) {
221 (*mFrameIndexes)[touched_thumbnail]++;
222 RebuildThumbnail(touched_thumbnail);
225 static_cast<ShapesDocument*>(mView->GetDocument())->Modify(true);
236 void SequenceView::OnMouseMove(wxMouseEvent &e)
241 void SequenceView::OnKeyDown(wxKeyEvent &e)
244 // edit frame index of selection
245 switch (e.GetKeyCode()) {
247 if (mSelection >= 0) {
248 if ((*mFrameIndexes)[mSelection] > -1) {
249 (*mFrameIndexes)[mSelection]--;
250 RebuildThumbnail(mSelection);
253 static_cast<ShapesDocument*>(mView->GetDocument())->Modify(true);
259 if (mSelection >= 0) {
260 if ((*mFrameIndexes)[mSelection] < ((int)mFrames.size()-1)) {
261 (*mFrameIndexes)[mSelection]++;
262 RebuildThumbnail(mSelection);
265 static_cast<ShapesDocument*>(mView->GetDocument())->Modify(true);
275 // edit selection with arrow keys
276 int new_selection = mSelection;
278 if (mSelection >= 0 && mSelection < (mNumberOfViews * mFramesPerView)) {
279 switch (e.GetKeyCode()) {
281 if (mSelection % mFramesPerView > 0)
285 if (mSelection % mFramesPerView < (mFramesPerView-1))
289 if (mSelection / mFramesPerView > 0)
290 new_selection -= mFramesPerView;
293 if (mSelection / mFramesPerView < (mNumberOfViews-1))
294 new_selection += mFramesPerView;
297 case WXK_NUMPAD_ENTER:
298 PopupFrameIndexDialog(mSelection);
303 } else if (mNumberOfViews * mFramesPerView > 0) {
306 // TODO scroll to show the new position
307 if (new_selection != mSelection) {
308 mSelection = new_selection;
311 // send selection event
312 wxCommandEvent event(wxEVT_SEQUENCEVIEW, GetId());
314 event.SetEventObject(this);
315 event.SetInt(mSelection);
316 GetEventHandler()->ProcessEvent(event);
321 void SequenceView::OnMouseDoubleClick(wxMouseEvent& e)
327 mouse = e.GetLogicalPosition(dc);
328 switch (e.GetButton()) {
329 case wxMOUSE_BTN_LEFT:
332 for (unsigned int i = 0; i < mThumbnailPositions.size(); ++i) {
333 wxRect test(mThumbnailPositions[i].x, mThumbnailPositions[i].y, mThumbnailSize, mThumbnailSize);
334 if (test.Contains(mouse)) {
340 if (selection != -1) {
341 if (!GetPrevArrowButtonRect(selection).Contains(mouse) &&
342 !GetNextArrowButtonRect(selection).Contains(mouse)) {
343 PopupFrameIndexDialog(selection);
351 int SequenceView::GetSelection(void) const
356 void SequenceView::SetThumbnailSize(int size)
359 mThumbnailSize = size;
370 void SequenceView::SetTranspPixelsDisplay(bool show)
372 mWhiteTransparency = show;
377 // add a new ShapesFrame to the thumbnail list
378 void SequenceView::AddFrame(ShapesFrame *fp)
381 mFrames.push_back(fp);
384 // add a ShapesBitmap to the bitmap pointer list. Call before adding frames!
385 void SequenceView::AddBitmap(ShapesBitmap *bp)
388 if (bp->Pixels() != NULL)
389 mBitmaps.push_back(bp);
391 wxLogError(wxT("SequenceView: someone tried to add a bitmap with NULL pixels"));
395 // set sequence parameters: frames per view, animation type and frame index array.
396 // Guess number of views from animation type. Also create thumbnails (add frames,
397 // bitmaps and set color table before!)
398 void SequenceView::SetSeqParameters(int animtype, int fpv, vector<short> *indexes, wxView* view)
400 mAnimationType = animtype;
401 mNumberOfViews = ActualNumberOfViews(animtype);
402 mFramesPerView = fpv;
403 mFrameIndexes = indexes;
410 // clear the thumbnail list
411 void SequenceView::Clear(void)
416 mThumbnailPositions.clear();
422 // call before adding frames!
423 void SequenceView::SetColorTable(ShapesColorTable *ct)
430 // calculate and set the wxWindow virtual size, based on the
431 // number of thumbnails, thumbnail dimensions and given visible
432 // size. Also pre-calculate thumbnail positions
433 // FIXME behave like a FrameBrowser if mAnimationType == UNANIMATED,
434 // because frames are just to be chosen randomly and not really
436 void SequenceView::UpdateVirtualSize(void)
439 int numframes = mThumbnails.size(),
441 additional_pad = (mAnimationType == UNANIMATED || mAnimationType == ANIMATED_1)
442 ? 0 : (mAngleLabelSpace + mMargin);
444 if (numframes < 1 || mFrameIndexes == NULL) {
445 SetVirtualSize(0, 0);
448 GetClientSize(&width, &height);
449 if (mAutoSize && numframes > 0) {
450 // calculate the best tn_size
451 // (its maximum value not requiring window scrolling)
452 int max_bitmap_dimension = 0,
456 // find greatest dimension among all referenced bitmaps
457 for (unsigned int i = 0; i < mFrameIndexes->size(); i++) {
458 int frame_index = (*mFrameIndexes)[i];
460 if (frame_index < 0 || frame_index >= (int)mFrames.size())
463 int bitmap_index = mFrames[frame_index]->BitmapIndex();
465 if (bitmap_index < 0 || bitmap_index >= (int)mBitmaps.size())
467 if (mBitmaps[bitmap_index]->Width() > max_bitmap_dimension)
468 max_bitmap_dimension = mBitmaps[bitmap_index]->Width();
469 if (mBitmaps[bitmap_index]->Height() > max_bitmap_dimension)
470 max_bitmap_dimension = mBitmaps[bitmap_index]->Height();
472 // start with a minimum size and increase it until overflow
473 for (new_tn_size = 2*mMargin; ; new_tn_size++) {
474 int numcols = mFramesPerView,
475 numrows = mNumberOfViews,
476 total_width = numcols * (new_tn_size + mMargin) + mMargin + additional_pad,
477 total_height = numrows * (new_tn_size + mMargin) + mMargin;
479 if (total_width > width || total_height > height) {
484 if (max_bitmap_dimension > 0 && new_tn_size >= max_bitmap_dimension) {
485 // no point in wasting window space with huge
486 // thumbnails showing small bitmaps at their center
490 if (new_tn_size != mThumbnailSize) {
491 mThumbnailSize = new_tn_size;
498 // now row & column count is simply mNumberOfViews and mFramesPerView,
499 // not the mess needed in the FrameBrowser
500 int numcols = mFramesPerView,
501 numrows = mNumberOfViews;
503 SetVirtualSize(numcols * (mThumbnailSize + mMargin) + mMargin + additional_pad,
504 numrows * (mThumbnailSize + mMargin) + mMargin);
506 // recalculate thumbnail positions
507 mThumbnailPositions.clear();
508 for (int r = 0; r < numrows; r++) {
509 for (int c = 0; c < numcols; c++)
510 mThumbnailPositions.push_back(wxPoint(mMargin + (mThumbnailSize + mMargin) * c + additional_pad,
511 mMargin + (mThumbnailSize + mMargin) * r));
515 // transform an ShapesFrame to a wxBitmap thumbnail
516 wxBitmap SequenceView::CreateThumbnail(ShapesFrame *fp)
518 if (fp->BitmapIndex() < 0 || fp->BitmapIndex() >= (int)mBitmaps.size()) {
519 // invalid or unset bitmap
520 // FIXME we are rebuilding this wxBitmap each time! Build it once
521 // and store it somewhere (rebuild upon tn_size change)
522 return BadThumbnail(mThumbnailSize);
525 ShapesBitmap *bp = mBitmaps[fp->BitmapIndex()];
526 wxImage newimg(bp->Width(), bp->Height());
528 // decode the bitmap to a wxImage
530 newimg = ShapesBitmapToImage(bp, mColorTable, mWhiteTransparency);
532 // apply frame transformations
533 if (fp->IsXmirrored())
534 newimg = newimg.Mirror(true);
535 if (fp->IsYmirrored())
536 newimg = newimg.Mirror(false);
538 // TODO apply transfer mode
540 return ImageThumbnail(newimg, mThumbnailSize, true);
544 void SequenceView::RebuildThumbnail(unsigned int i)
546 if (i < mThumbnails.size() && i < mFrameIndexes->size()) {
547 if ((*mFrameIndexes)[i] >= 0 && (*mFrameIndexes)[i] < (int)mFrames.size())
548 mThumbnails[i] = CreateThumbnail(mFrames[(*mFrameIndexes)[i]]);
550 mThumbnails[i] = BadThumbnail(mThumbnailSize);
554 // redecode frames to bitmaps (after color table change, frame parameter variations etc)
555 void SequenceView::RebuildThumbnails(void)
558 if (mFrameIndexes != NULL) {
559 for (unsigned int i = 0; i < mFrameIndexes->size(); i++) {
560 if ((*mFrameIndexes)[i] >= 0 && (*mFrameIndexes)[i] < (int)mFrames.size())
561 mThumbnails.push_back(CreateThumbnail(mFrames[(*mFrameIndexes)[i]]));
563 mThumbnails.push_back(BadThumbnail(mThumbnailSize));
568 void SequenceView::PopupFrameIndexDialog(int selection)
571 if (wxGetTextFromUser(_("Change frame:"), _("Chage frame"), wxString::Format(wxT("%i"), (*mFrameIndexes)[selection]), GetParent()).ToLong(&v)) {
572 if (v >= -1 && v <= static_cast<int>(mFrames.size()) - 1) {
573 (*mFrameIndexes)[selection] = v;
574 RebuildThumbnail(selection);
577 static_cast<ShapesDocument*>(mView->GetDocument())->Modify(true);
583 wxRect SequenceView::GetNextArrowButtonRect(int thumbnail_index) const
585 int thumb_x = mThumbnailPositions[thumbnail_index].x;
586 int thumb_y = mThumbnailPositions[thumbnail_index].y;
588 int x = thumb_x + mThumbnailSize - mNextBtnIcon.GetWidth() - THUMB_ARROW_MARGIN;
589 int y = thumb_y + mThumbnailSize - mNextBtnIcon.GetHeight() - THUMB_ARROW_MARGIN;
590 int w = mPrevBtnIcon.GetWidth();
591 int h = mPrevBtnIcon.GetHeight();
593 return wxRect(x - THUMB_ARROW_BUTTON_MARGIN,
594 y - THUMB_ARROW_BUTTON_MARGIN,
595 w + 2 * THUMB_ARROW_BUTTON_MARGIN,
596 h + 2 * THUMB_ARROW_BUTTON_MARGIN);
599 wxRect SequenceView::GetPrevArrowButtonRect(int thumbnail_index) const
601 int thumb_x = mThumbnailPositions[thumbnail_index].x;
602 int thumb_y = mThumbnailPositions[thumbnail_index].y;
604 int x = thumb_x + THUMB_ARROW_MARGIN;
605 int y = thumb_y + mThumbnailSize - mNextBtnIcon.GetHeight() - THUMB_ARROW_MARGIN;
606 int w = mPrevBtnIcon.GetWidth();
607 int h = mPrevBtnIcon.GetHeight();
609 return wxRect(x - THUMB_ARROW_BUTTON_MARGIN,
610 y - THUMB_ARROW_BUTTON_MARGIN,
611 w + 2 * THUMB_ARROW_BUTTON_MARGIN,
612 h + 2 * THUMB_ARROW_BUTTON_MARGIN);