/* ============================================================================== This file is part of the JUCE library - "Jules' Utility Class Extensions" Copyright 2004-9 by Raw Material Software Ltd. ------------------------------------------------------------------------------ JUCE can be redistributed and/or modified under the terms of the GNU General Public License (Version 2), as published by the Free Software Foundation. A copy of the license is included in the JUCE distribution, or can be found online at www.gnu.org/licenses. JUCE 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 General Public License for more details. ------------------------------------------------------------------------------ To release a closed-source product which uses JUCE, commercial licenses are available: visit www.rawmaterialsoftware.com/juce for more information. ============================================================================== */ #include "../../jucer_Headers.h" #include "jucer_ComponentEditor.h" //============================================================================== class ComponentCanvas : public Component, public ValueTree::Listener { public: ComponentCanvas (ComponentEditor& editor_) : editor (editor_), borderThickness (4) { setOpaque (true); addAndMakeVisible (componentHolder = new Component()); addAndMakeVisible (overlay = new OverlayComponent (*this)); setSize (500, 500); getDocument().getRoot().addListener (this); updateComponents(); } ~ComponentCanvas() { getDocument().getRoot().removeListener (this); deleteAllChildren(); } void paint (Graphics& g) { g.fillAll (Colours::white); g.setColour (Colour::greyLevel (0.9f)); g.drawRect (0, 0, getWidth(), getHeight(), borderThickness); } void resized() { componentHolder->setBounds (borderThickness, borderThickness, getWidth() - borderThickness * 2, getHeight() - borderThickness * 2); overlay->setBounds (componentHolder->getBounds()); } void zoom (float newScale, const Point& centre) { } ComponentEditor& getEditor() { return editor; } ComponentDocument& getDocument() { return editor.getDocument(); } ComponentDocument::SelectedItems& getSelection() { return selection; } Component* findComponentFor (const ValueTree& state) { ComponentDocument& doc = getDocument(); for (int i = componentHolder->getNumChildComponents(); --i >= 0;) { Component* c = componentHolder->getChildComponent (i); if (doc.isStateForComponent (state, c)) return c; } return 0; } void updateComponents() { ComponentDocument& doc = getDocument(); int i; for (i = componentHolder->getNumChildComponents(); --i >= 0;) { Component* c = componentHolder->getChildComponent (i); if (! doc.containsComponent (c)) { selection.deselect (c->getComponentUID()); delete c; } } const int num = doc.getNumComponents(); for (i = 0; i < num; ++i) { const ValueTree v (doc.getComponent (i)); Component* c = findComponentFor (v); if (c == 0) { c = doc.createComponent (i); componentHolder->addAndMakeVisible (c); } else { doc.updateComponent (c); } } } void valueTreePropertyChanged (ValueTree& treeWhosePropertyHasChanged, const var::identifier& property) { updateComponents(); } void valueTreeChildrenChanged (ValueTree& treeWhoseChildHasChanged) { updateComponents(); } void valueTreeParentChanged (ValueTree& treeWhoseParentHasChanged) { } Component* getComponentHolder() const { return componentHolder; } const Array getSelectedComps() const { Array comps; for (int i = 0; i < selection.getNumSelected(); ++i) { Component* c = getComponentForUID (selection.getSelectedItem (i)); jassert (c != 0); if (c != 0) comps.add (c); } return comps; } private: ComponentEditor& editor; const int borderThickness; //============================================================================== class ComponentResizeFrame : public Component, public ComponentListener { public: ComponentResizeFrame (ComponentCanvas& canvas_, Component* componentToAttachTo) : canvas (canvas_), component (componentToAttachTo), borderThickness (4) { componentMovedOrResized (*componentToAttachTo, true, true); componentToAttachTo->addComponentListener (this); } ~ComponentResizeFrame() { if (component != 0) component->removeComponentListener (this); } void paint (Graphics& g) { g.setColour (Colours::red.withAlpha (0.1f)); g.drawRect (0, 0, getWidth(), getHeight(), borderThickness); } void mouseEnter (const MouseEvent& e) { //repaint(); updateDragZone (e.getPosition()); } void mouseExit (const MouseEvent& e) { //repaint(); updateDragZone (e.getPosition()); } void mouseMove (const MouseEvent& e) { updateDragZone (e.getPosition()); } void mouseDown (const MouseEvent& e) { jassert (component != 0); if (component != 0) { updateDragZone (e.getPosition()); canvas.getDocument().beginDrag (canvas.getSelectedComps(), e, dragZone); } } void mouseDrag (const MouseEvent& e) { if (component != 0) canvas.getDocument().continueDrag (e); } void mouseUp (const MouseEvent& e) { if (component != 0) canvas.getDocument().endDrag (e); updateDragZone (e.getPosition()); } void componentMovedOrResized (Component&, bool wasMoved, bool wasResized) { if (component != 0) setBounds (component->getBounds().expanded (borderThickness, borderThickness)); } bool hitTest (int x, int y) { return ! getCentreArea().contains (x, y); } uint32 getTargetComponentUID() const { return component == 0 ? 0 : component->getComponentUID(); } private: ComponentCanvas& canvas; Component::SafePointer component; ResizableBorderComponent::Zone dragZone; const int borderThickness; const Rectangle getCentreArea() const { return getLocalBounds().reduced (borderThickness, borderThickness); } void updateDragZone (const Point& p) { ResizableBorderComponent::Zone newZone = ResizableBorderComponent::Zone::fromPositionOnBorder (getLocalBounds(), BorderSize (borderThickness), p); if (dragZone != newZone) { dragZone = newZone; setMouseCursor (newZone.getMouseCursor()); } } }; //============================================================================== class OverlayComponent : public Component, public LassoSource , public ChangeListener { public: OverlayComponent (ComponentCanvas& canvas_) : canvas (canvas_) { setAlwaysOnTop (true); setWantsKeyboardFocus (true); canvas.getSelection().addChangeListener (this); } ~OverlayComponent() { canvas.getSelection().removeChangeListener (this); lasso = 0; deleteAllChildren(); } void mouseDown (const MouseEvent& e) { lasso = 0; mouseDownCompUID = 0; isDraggingClickedComp = false; if (e.mods.isPopupMenu()) { PopupMenu m; canvas.getDocument().addNewComponentMenuItems (m); const int r = m.show(); canvas.getDocument().performNewComponentMenuItem (r); } else { Component* underMouse = canvas.getComponentHolder()->getComponentAt (e.x, e.y); if (underMouse == canvas.getComponentHolder()) underMouse = 0; if (underMouse == 0 || e.mods.isAltDown()) { addAndMakeVisible (lasso = new LassoComponent ()); lasso->beginLasso (e, this); } else { mouseDownCompUID = underMouse->getComponentUID(); mouseDownResult = canvas.getSelection().addToSelectionOnMouseDown (mouseDownCompUID, e.mods); } } } void mouseDrag (const MouseEvent& e) { if (lasso != 0) { lasso->dragLasso (e); } else if (mouseDownCompUID != 0 && (! e.mouseWasClicked()) && (! e.mods.isPopupMenu())) { if (! isDraggingClickedComp) { isDraggingClickedComp = true; canvas.getSelection().addToSelectionOnMouseUp (mouseDownCompUID, e.mods, true, mouseDownResult); canvas.getDocument().beginDrag (canvas.getSelectedComps(), e, ResizableBorderComponent::Zone (ResizableBorderComponent::Zone::centre)); } canvas.getDocument().continueDrag (e); } } void mouseUp (const MouseEvent& e) { if (lasso != 0) { lasso->endLasso(); lasso = 0; if (e.mouseWasClicked()) canvas.getSelection().deselectAll(); } else if (! e.mods.isPopupMenu()) { if (! isDraggingClickedComp) canvas.getSelection().addToSelectionOnMouseUp (mouseDownCompUID, e.mods, ! e.mouseWasClicked(), mouseDownResult); } } void findLassoItemsInArea (Array & itemsFound, int x, int y, int width, int height) { const Rectangle lassoArea (x, y, width, height); for (int i = canvas.getComponentHolder()->getNumChildComponents(); --i >= 0;) { Component* c = canvas.getComponentHolder()->getChildComponent(i); if (c->getBounds().intersects (lassoArea)) itemsFound.add (c->getComponentUID()); } } ComponentDocument::SelectedItems& getLassoSelection() { return canvas.getSelection(); } void changeListenerCallback (void*) { updateSelectedComponentResizeFrames(); } void modifierKeysChanged (const ModifierKeys&) { Desktop::getInstance().getMainMouseSource().triggerFakeMove(); } private: ComponentCanvas& canvas; ScopedPointer > lasso; bool mouseDownResult, isDraggingClickedComp; uint32 mouseDownCompUID; ComponentResizeFrame* getSelectorFrameFor (Component* c) const { for (int i = getNumChildComponents(); --i >= 0;) { ComponentResizeFrame* resizer = dynamic_cast (getChildComponent(i)); if (resizer != 0 && resizer->getTargetComponentUID() == c->getComponentUID()) return resizer; } return 0; } void updateSelectedComponentResizeFrames() { ComponentDocument::SelectedItems& selection = canvas.getSelection(); int i; for (i = getNumChildComponents(); --i >= 0;) { ComponentResizeFrame* resizer = dynamic_cast (getChildComponent(i)); if (resizer != 0 && ! selection.isSelected (resizer->getTargetComponentUID())) delete resizer; } for (i = canvas.getComponentHolder()->getNumChildComponents(); --i >= 0;) { Component* c = canvas.getComponentHolder()->getChildComponent(i); if (c != this && selection.isSelected (c->getComponentUID()) && getSelectorFrameFor (c) == 0) addAndMakeVisible (new ComponentResizeFrame (canvas, c)); } } }; Component* componentHolder; OverlayComponent* overlay; ComponentDocument::SelectedItems selection; Component* getComponentForUID (const uint32 uid) const { for (int i = componentHolder->getNumChildComponents(); --i >= 0;) if (componentHolder->getChildComponent (i)->getComponentUID() == uid) return componentHolder->getChildComponent (i); return 0; } }; //============================================================================== ComponentEditor::ComponentEditor (OpenDocumentManager::Document* document, Project* project_, ComponentDocument* componentDocument_) : DocumentEditorComponent (document), project (project_), componentDocument (componentDocument_) { setOpaque (true); addAndMakeVisible (viewport = new Viewport()); if (document != 0) viewport->setViewedComponent (new ComponentCanvas (*this)); } ComponentEditor::~ComponentEditor() { deleteAllChildren(); } void ComponentEditor::paint (Graphics& g) { g.fillAll (Colours::white); } void ComponentEditor::resized() { viewport->setBounds (0, 0, getWidth(), getHeight()); } void ComponentEditor::getAllCommands (Array & commands) { DocumentEditorComponent::getAllCommands (commands); const CommandID ids[] = { CommandIDs::undo, CommandIDs::redo }; commands.addArray (ids, numElementsInArray (ids)); } void ComponentEditor::getCommandInfo (CommandID commandID, ApplicationCommandInfo& result) { result.setActive (document != 0); switch (commandID) { case CommandIDs::undo: result.setInfo ("Undo", "Undoes the last change", CommandCategories::general, 0); result.defaultKeypresses.add (KeyPress ('z', ModifierKeys::commandModifier, 0)); break; case CommandIDs::redo: result.setInfo ("Redo", "Redoes the last change", CommandCategories::general, 0); result.defaultKeypresses.add (KeyPress ('z', ModifierKeys::shiftModifier | ModifierKeys::commandModifier, 0)); result.defaultKeypresses.add (KeyPress ('y', ModifierKeys::commandModifier, 0)); break; default: DocumentEditorComponent::getCommandInfo (commandID, result); break; } } bool ComponentEditor::perform (const InvocationInfo& info) { switch (info.commandID) { case CommandIDs::undo: getDocument().getUndoManager()->undo(); return true; case CommandIDs::redo: getDocument().getUndoManager()->redo(); return true; default: break; } return DocumentEditorComponent::perform (info); }