// $Revision: 1.3 $ // Copyright (C) 1994, 1995 Taligent, Inc. All rights reserved. // TilesView.C #ifndef TaligentSample_TILESVIEW #include "TilesView.h" #endif #ifndef TaligentSample_TILESMODEL #include "TilesModel.h" #endif #ifndef TaligentSample_TILESCOMMANDS #include "TilesCommands.h" #endif #ifndef TaligentSample_TILESINTERACTORS #include "TilesInteractors.h" #endif #ifndef Taligent_SCRAPITEMON #include #endif #ifndef TaligentSamples_SIMPLEDRAGANDDROPITEM #include #endif // This macro call matches a macro call to VersionDeclarationsMacro // in the TTilesView class declaration. This macro defines the // versioning utility functions. VersionDefinitionsMacro(TTilesView, kOriginalVersion); // This is how the view is actually constructed, with a TGUIBundle*. TTilesView::TTilesView(TGUIBundle* bundle) : TGUIEmbedderModelView (bundle), MDropAcceptor(TViewHandle(*this)), fTextControl(0), fTextControlState(0), fEditingText(false) {} // Default constructor TTilesView::TTilesView() : TGUIEmbedderModelView(), MDropAcceptor(TViewHandle(*this)), fTextControl(0), fTextControlState(0), fEditingText(false) {} // Copy constructor TTilesView::TTilesView(const TTilesView& copy) : TGUIEmbedderModelView(copy), MDropAcceptor(TViewHandle(*this)), fTextControl(0), fTextControlState(0), fEditingText(false) {} // Destructor TTilesView::~TTilesView() { } // HandleAfterConnectionToViewRoot is called by the Framework when the view is added // to the view hierachy. This usually happens just before the view is made // visible. Initialization routines are usually added here. void TTilesView::HandleAfterConnectionToViewRoot() { // Call the base class version TGUIEmbedderModelView::HandleAfterConnectionToViewRoot (); // Build a map between the TTilesModel's tile types (enums) and the // actual graphics used to render them in the view. BuildTilesGraphicsMapToModel(); // Set default view size SetAllocatedArea (TGArea(TGRect(TGPoint(0,0), TGPoint(200, 100)))); // Return mouse events in local coordinates SetCoordinateView(TViewHandle(*this)); // Create a smart pointer which will lock the model for reading, // and will release the lock on destruction of the pointer. const TModelPointerTo model(GetModelReference()); // Create a selection and set it to the empty selection. TTilesSelection* selection = (TTilesSelection*) model->CreateSelection (); selection->DeselectAll(); // Call MGUIBundle's AdoptCurrentModelSelection to pass ownership of the // selection* to MGUIBundle. This sets the initial current selection to // the empty selection. AdoptCurrentModelSelection (selection); // Create a floating text control for editing text. This text control will // remain hidden (visibility == false) most of the time. When a user clicks on // a piece of text (i.e., a text display) then this text control is positioned // over the text display, filled with its value and made visible for editing. if (!fTextControl) { // TTextActionControlState is a useful default control state. fTextControlState = new TTextActionControlState(TViewHandle(*this)); fTextControl = new TTextControl(fTextControlState); fTextControl->SetTextBorderThickness(1); // Enable text menus so that they "pop up" whenever the text control is active. fTextControl->Activate(); fTextControl->EnableTextMenus(*(GetGUIBundle()->GetWindowGroup())); fTextControl->Deactivate(); // Adopt into view hierarchy, but make hidden for now. AdoptChild(fTextControl); fTextControl->TView::SetVisibility(false); } } // HandleBeforeDisconnectionFromViewRoot is called by the Framework when the view is removed // to the view hierachy. This usually happens just before the view is destroyed. // Cleanup routines are usually added here. void TTilesView::HandleBeforeDisconnectionFromViewRoot() { // call the base class version TGUIEmbedderModelView::HandleBeforeDisconnectionFromViewRoot(); DestroyTilesGraphicsMapToModel(); } // Draw contents renders the data from the model. This is the primary method // of a content view. The TGrafPort is already clipped to the region which needs // to be redrawn, so DrawContents should re-render the entire view and rely on // the port to do any necessary clipping. void TTilesView::DrawContents(TGrafPort& port) const { // Create a smart pointer which will lock the model for reading, // and will release the lock on destruction of the pointer. const TModelPointerTo model(GetModelReference()); // Get a pointer to the current selection. May receive a 0 pointer if there // is no current selection. const TTilesSelection* currentSelection = GetTilesSelection(); TGArea area; GetBounds(area); // Fill the background with white TGrafBundle whitebg (new TColorPaint(TRGBColor(.9,1,1)), TGrafBundle::kFill); port.Draw (area, whitebg); // Get each tile and draw it according to its type, color position and size. bool inCurrentSelection = false; int numTiles = model->GetNumTiles(); for (int i = 0; i < numTiles; i++) { const TTile* tile = model->GetTileForReading(i); // Check if the current selection exists. If it does, see if the // current selection contains the tile to be drawn. inCurrentSelection = (currentSelection && currentSelection->ContainsTile(i)); DrawTile(port, tile, inCurrentSelection); } } bool TTilesView::MouseDown (TMouseDownEvent& mouseDownEvent) { TRGBColor newColor; TileIndex whichTile; // Get the position of the mouse down TGPoint mousePoint = mouseDownEvent.GetEventPosition(); // Create a smart pointer which will lock the model for reading, // and will release the lock on destruction of the pointer. const TModelPointerTo model(GetModelReference()); // If currently editing text using the floating text control, then // a mouse down indicates editing is done. Stuff the contents from // the text control back into the model with a change text command. if (fEditingText) { // Copy text to the Tile, first get a NONCONST model pointer which only // lasts for the scope of this block TModelPointerTo model(GetModelReference()); TStandardText controlText; fTextControl->GetTextState(controlText); // Create a selection and set it to the tile who's text was being edited, // specified by the fEditingTile value, which was set when the text control // was popped up. TTilesSelection* selection = (TTilesSelection*) model->CreateSelection (); selection->SelectTile (fEditingTile); // Create command to change the text. TChangeTextCommand* command = new TChangeTextCommand (controlText); // Create a command binding to bind command and selection // Pass the GUIBundle and a text string for the Undo/Redo menu item. TTilesCommandBinding* commandBinding = new TTilesCommandBinding (command, selection, *GetGUIBundle(), TStandardText("Change Text")); // Access the document and have it adopt and do the command binding AdoptAndDo (commandBinding); // Turn off text editing fEditingText = false; fTextControl->TView::SetVisibility(false); } // Now see if any of the TTiles in the model contain the point which // was clicked on in the view. Count in the opposite direction in // which the TTiles were drawn in DrawContents in order to detect // hits on the frontmost TTiles first. for (whichTile = model->GetNumTiles() - 1; whichTile >= 0; whichTile--) { if (model->GetTileForReading(whichTile)->ContainsPoint(mousePoint)) { break; } } // Create a selection and set it to the tile chosen or to an empty selection. TTilesSelection* selection = (TTilesSelection*) model->CreateSelection (); if (whichTile >= 0) { // A TTile was clicked on. if(TKeyboardInputDevice::GetDefaultKeyboardInputDevice(mouseDownEvent)->GetModifierKeys().IsShiftKeyDown()) { // Shift key is down, drag away color. TColor* color = new TRGBColor(model->GetTileForReading(whichTile)->GetColor()); // A drag appearance is the feedback provided to the user when dragging, in this // case a simple square containing the dragged color. TGrafBundle* bundle = new TGrafBundle(*color, TRGBColor(0,0,0)); bundle->AdoptFramePen(new TSolidPen(1)); MGraphic* dragAppearance = new TPolygon(TGRect(1, 8, 16, 23), bundle); // A scrap item is the object that transports the dragged data. In this case, the // enabler class template TScrapItemOn<> is used to create the scrap item. TScrapItemOn* scrapItem = new TScrapItemOn(color, TGPoint::kOrigin); // The drag and drop item contains one or more scrap items. In this case, the enabler // class TSimpleDragAndDropItem is used to create the drag and drop item. TSimpleDragAndDropItem* dragAndDropItem = new TSimpleDragAndDropItem(TViewHandle(*this)); dragAndDropItem->AdoptScrapItem(scrapItem); // Th interactor contains both the drag and drop item and the drag appearance. TMouseDragAndDropInteractor* interactor = new TMouseDragAndDropInteractor(dragAndDropItem, dragAppearance, TViewHandle(*this)); mouseDownEvent.StartInteractor(interactor); } else { // Shift key is not down, start move interactor selection->SelectTile (whichTile); // Call MGUIBundle's AdoptCurrentModelSelection to pass ownership of the // selection* to MGUIBundle. TMoveInteractor will use create an interactive // TMoveCommand on the current selection. In addition, DrawContents() will // highlight the Tiles in the current selection. The current selection will // also be used by TCommandControlStateOn, which will perform its command on // the current selection. AdoptCurrentModelSelection (selection); // Create and start a mouse interactor. Pass this in the constructor so the // mouse interactor can operate within this view. // Note that a mouse interactor is only created when the current selection // is on a single TTile. TMoveCommand makes this assumption. mouseDownEvent.StartInteractor(new TMoveInteractor(this)); } } else { // No TTile was hit. Set the selection to be empty to deselect whatever // the previous current selection was. selection->DeselectAll(); // Call MGUIBundle's AdoptCurrentModelSelection to pass ownership of the // selection* to MGUIBundle. AdoptCurrentModelSelection (selection); // Now see if any of the the text labels were hit. This is done by creating // a dummy text display that is used for hit detection. The text display is // positioned in the same location as it was position in the DrawTile routine // before hit detection is performed. TTextDisplay textDisplay; // This is a single line text display. textDisplay.SetLineBreaking(TTextDisplay::kNeverBreakLines); const TTile* tile; TGRect boundaryRect; // Reposition the dummy text display for each tile and do hit detection. for (whichTile = model->GetNumTiles() - 1; whichTile >= 0; whichTile--) { tile = model->GetTileForReading(whichTile); textDisplay.SetText(tile->GetText()); TGPoint textSize(textDisplay.GetTextBounds().GetSize()); TGPoint topLeft (tile->GetPosition() + TGPoint((tile->GetSize().fX / 2) - (textSize.fX / 2), tile->GetSize().fY + 10)); TGPoint bottomRight (topLeft + textSize); textDisplay.SetBoundaryRectangle(TGRect(topLeft, bottomRight)); boundaryRect = textDisplay.GetBoundaryRectangle(); if (boundaryRect.Contains(mousePoint)) { break; } } if (whichTile >= 0) { // A text label was hit. Start editing it with the text control. fEditingText = true; // Store the index of the tile to be used later for updating the model // with the edited text. fEditingTile = whichTile; // Stuff the control with the text label of the chosen tile. fTextControl->SetTextState(tile->GetText()); // Adjust to compensate for the text control border so the text control // lines up with the text display. boundaryRect.Inset(TGPoint(-4.0, -4.0)); fTextControl->SetAllocatedArea(TGRect(TGPoint(0,0), boundaryRect.GetSize())); fTextControl->TranslateAllocatedAreaInParentTo(boundaryRect.GetTopLeft()); // "Pop up" the text control fTextControl->TView::SetVisibility(true); // Activate the view and select all the text for editing. // This currently doesn't work because of a known bug. fTextControl->Activate(); fTextControl->SelectText (); } } return true; } // Based on the drop location and a list of available types, search for an acceptable type. // Return true if an acceptable type is found and set chosenType to the acceptable type. // In this case, whereDropped is not important since only TColor's are acceptable. bool TTilesView::ChoosePreferredType(const TGPoint& whereDropped, const TSequenceOf& availableTypes, TTypeDescription& chosenType) const { bool result = false; // Iterate through the list of availableTypes to find an acceptable type. In this // case, only TColor is acceptable. TTypeDescription acceptableType; TScrapItemOn::GetScrapItemType(acceptableType); TIteratorOver* iterator = availableTypes.CreateIterator(); for (TTypeDescription* td = iterator->First(); td; td = iterator->Next()) { if (*td == acceptableType) { // A match is found. Set the chosentType and result to true; chosenType = TTypeDescription(*td); result = true; break; } } delete iterator; return result; } // Now accept the TColor chosen in ChoosePreferredType. bool TTilesView::AcceptDrop(const TGPoint& dropLocation, const TTypeDescription& theType, const TScrapItem &theItem) { // Since ChoosePreferredType only chooses the TScrapItemOn type, that should // be the only type accepted. Check anyway. if (theType == TScrapItemOn::GetScrapItemType()) { // Assume that if TColor is the type, then TScrapItemOn is the scrap item. TScrapItemOn& colorItem = (TScrapItemOn&) theItem; // Similar to MouseDown, the dropLocation is used to identify the affedted tile. TileIndex whichTile = 0; // Create a smart pointer which will lock the model for writing, // and will release the lock on destruction of the pointer. TModelPointerTo model(GetModelReference()); // Now see if any of the TTiles in the model contain the point which // was clicked on in the view. Count in the opposite direction in // which the TTiles were drawn in DrawContents in order to detect // drops on the frontmost TTiles first. for (whichTile = model->GetNumTiles() - 1; whichTile >= 0; whichTile--) { if (model->GetTileForReading(whichTile)->ContainsPoint(dropLocation)) { break; } } // Only process if dropped on a TTile if (whichTile < model->GetNumTiles()) { // Create a selection and set it to the tile chosen or to an empty selection. TTilesSelection* selection = (TTilesSelection*) model->CreateSelection (); // A TTile was dropped on. selection->SelectTile (whichTile); // Create command to change to the new color. TChangeColorCommand* command = new TChangeColorCommand (*colorItem.GetObject()); // Create a command binding to bind command and selection // Pass the GUIBundle and a text string for the Undo/Redo menu item. TTilesCommandBinding* commandBinding = new TTilesCommandBinding (command, selection, *GetGUIBundle(), TStandardText("Change Color")); // Access the document and have it adopt and do the command binding AdoptAndDo (commandBinding); } else { // May extend to allow drops on the background here. Left as an excercise. } } return true; } // A convenience function which calls MGUIBundle's GetCurrentModelSelection and // safely downcasts the TModelSelection to a TTilesSelection*. The user should // always check for a 0 pointer after calling this function. const TTilesSelection * TTilesView::GetTilesSelection () const { // Get a pointer to the current selection, if any. const TModelSelection* modelSelection = GetCurrentModelSelection(); if (modelSelection) { // Attempt to safely downcast the TModelSelection to a TTileSelection. // IsA will return 0 if the downcast is not safe. TTilesSelection* tilesSelection; DynamicCastTo(tilesSelection, modelSelection); return tilesSelection; } else { return 0; } } // Draw an individual TTile into the port. Hilight the TTile if it is in the current selection. void TTilesView::DrawTile (TGrafPort& port, const TTile* tile, bool inCurrentSelection) const { // Create a graph bundle specifying color frame and fill properties. If the TTile // is in the current selection then frame and fill to hilight, otherwise just fill. TGrafBundle* bundle = new TGrafBundle (new TColorPaint(tile->GetColor()), new TColorPaint(TColorPaint::GetBlack()), inCurrentSelection ? TGrafBundle::kFillAndFrame : TGrafBundle::kFill); // Set the pen width for the graph bundle. bundle->AdoptFramePen(new TSolidPen(3.0, TPen::kOutsetFrame)); // Copy the MGraphic from the table created by BuildTilesGraphicsMapToModel // Use ::Copy to get a polymorphic copy. MGraphic* tileGraphic = ::CopyPointer(fTilesGraphics[tile->GetType()]); // Scale and translate the graphic tileGraphic->ScaleBy(tile->GetSize()); tileGraphic->TranslateBy(tile->GetPosition()); // Have graphic adopt the drawing attributes tileGraphic->AdoptBundle(bundle); tileGraphic->Draw(port); delete tileGraphic; // Draw the text label, centered under the tile. TTextDisplay text(tile->GetText()); text.SetLineBreaking(TTextDisplay::kNeverBreakLines); TGPoint textSize(text.GetTextBounds().GetSize()); TGPoint topLeft (tile->GetPosition() + TGPoint((tile->GetSize().fX / 2) - (textSize.fX / 2), tile->GetSize().fY + 10)); TGPoint bottomRight (topLeft + textSize); text.SetBoundaryRectangle(TGRect(topLeft, bottomRight)); text.Draw(port); } // Build an array of MGraphics which maps the data in the model (kRock, // kPaper, kScissors) to drawable objects for use by DrawTile. void TTilesView::BuildTilesGraphicsMapToModel () { // Create a circle to represent the kRock, normalized to size 1.0 TEllipse* circle = new TEllipse(TGRect(TGPoint::kOrigin, TGPoint(1.0, 1.0))); // Create a square to represent the kPaper, normalized to size 1.0 TPolygon* square = new TPolygon(TGRect(TGPoint(0.0, 0.0), TGPoint(1.0, 1.0))); // Create a Triangle to represent the kScissors, normalized to size 1.0 TGPointArray pointArray (3); pointArray.SetPoint(0, TGPoint(0.5, 0.0)); pointArray.SetPoint(1, TGPoint(1.0, 1.0)); pointArray.SetPoint(2, TGPoint(0.0, 1.0)); TPolygon* triangle = new TPolygon(TGPolygon(pointArray)); // Place the MGraphics in the array fTilesGraphics[TTile::kRock] = circle; fTilesGraphics[TTile::kPaper] = square; fTilesGraphics[TTile::kScissors] = triangle; } void TTilesView::DestroyTilesGraphicsMapToModel () { delete fTilesGraphics[TTile::kRock]; delete fTilesGraphics[TTile::kPaper]; delete fTilesGraphics[TTile::kScissors]; }