/* ============================================================================== This file is part of the JUCE library - "Jules' Utility Class Extensions" Copyright 2004-10 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. ============================================================================== */ #ifndef __JUCER_EDITORPANEL_H_8E192A99__ #define __JUCER_EDITORPANEL_H_8E192A99__ #include "../../utility/jucer_TickIterator.h" #include "jucer_EditorCanvas.h" //============================================================================== class EditorPanelBase : public Component { public: EditorPanelBase() : rulerX (true), rulerY (false), markersVisible (true), snappingEnabled (true), canvas (0) { setOpaque (true); background = ImageCache::getFromMemory (BinaryData::brushed_aluminium_png, BinaryData::brushed_aluminium_pngSize); addAndMakeVisible (&toolbar); toolbar.setStyle (Toolbar::textOnly); addAndMakeVisible (&viewport); addAndMakeVisible (&rulerX); addAndMakeVisible (&rulerY); addChildComponent (&tree); tree.setRootItemVisible (true); tree.setMultiSelectEnabled (true); tree.setDefaultOpenness (true); tree.setColour (TreeView::backgroundColourId, Colour::greyLevel (0.92f)); tree.setIndentSize (15); } ~EditorPanelBase() { jassert (infoPanel == 0); // remember to call shutdown() } void initialise (EditorCanvasBase* canvas_, ToolbarItemFactory& toolbarFactory, TreeViewItem* treeRootItem) { canvas = canvas_; toolbar.addDefaultItems (toolbarFactory); viewport.setViewedComponent (canvas); addAndMakeVisible (infoPanel = new InfoPanel (this)); tree.setRootItem (treeRootItem); resized(); } void shutdown() { tree.deleteRootItem(); infoPanel = 0; } //============================================================================== void showOrHideProperties() { infoPanel->setVisible (! infoPanel->isVisible()); resized(); } bool arePropertiesVisible() const { return infoPanel->isVisible(); } void showOrHideTree() { tree.setVisible (! tree.isVisible()); resized(); } bool isTreeVisible() const { return tree.isVisible(); } void showOrHideMarkers() { markersVisible = ! markersVisible; commandManager->commandStatusChanged(); } bool areMarkersVisible() const { return markersVisible; } void toggleSnapping() { snappingEnabled = ! snappingEnabled; commandManager->commandStatusChanged(); } bool isSnappingEnabled() const { return snappingEnabled; } //============================================================================== virtual SelectedItemSet& getSelection() = 0; virtual void getSelectedItemProperties (Array& newComps) = 0; void paint (Graphics& g) { g.setTiledImageFill (background, 0, 0, 1.0f); g.fillAll(); } void resized() { const int toolbarHeight = 22; toolbar.setBounds (0, 0, getWidth(), toolbarHeight); int contentL = 0, contentR = getWidth(); if (infoPanel != 0 && infoPanel->isVisible()) { contentR -= 200; infoPanel->setBounds (contentR, toolbar.getBottom(), getWidth() - contentR, getHeight() - toolbar.getBottom()); } if (tree.isVisible()) { contentL = 200; tree.setBounds (0, toolbar.getBottom(), contentL, getHeight() - toolbar.getBottom()); } const int rulerThickness = 16; viewport.setBounds (contentL + rulerThickness, toolbar.getBottom() + rulerThickness, contentR - contentL - rulerThickness, getHeight() - toolbar.getBottom() - rulerThickness); rulerX.setBounds (viewport.getX(), viewport.getY() - rulerThickness, viewport.getWidth(), rulerThickness); rulerY.setBounds (viewport.getX() - rulerThickness, viewport.getY(), rulerThickness, viewport.getHeight()); updateRulers(); } void updateRulers() { if (canvas != 0) { rulerX.update (canvas->getScale(), canvas->getComponentHolder()); rulerY.update (canvas->getScale(), canvas->getComponentHolder()); } updateMarkers(); } void updateMarkers() { if (canvas != 0) { const int vw = viewport.getMaximumVisibleWidth(); const int vh = viewport.getMaximumVisibleHeight(); rulerX.updateMarkers (canvas->getMarkerList (true), canvas, vw, vh); rulerY.updateMarkers (canvas->getMarkerList (false), canvas, vw, vh); } } private: //============================================================================== class InfoPanel : public Component, public ChangeListener { public: InfoPanel (EditorPanelBase* owner_) : owner (owner_) { setOpaque (true); addAndMakeVisible (props = new PropertyPanel()); owner->getSelection().addChangeListener (this); } ~InfoPanel() { owner->getSelection().removeChangeListener (this); props->clear(); deleteAllChildren(); } void changeListenerCallback (void*) { Array newComps; owner->getSelectedItemProperties (newComps); props->clear(); props->addProperties (newComps); } void paint (Graphics& g) { g.fillAll (Colour::greyLevel (0.92f)); } void resized() { props->setSize (getWidth(), getHeight()); } private: EditorPanelBase* owner; PropertyPanel* props; }; //============================================================================== class RulerComponent : public Component { public: RulerComponent (const bool isX_) : isX (isX_), range (0.0, 100.0), canvas (0) { } ~RulerComponent() { } void update (const EditorCanvasBase::Scale& scale, Component* contentHolder) { const Point origin (contentHolder->relativePositionToOtherComponent (this, scale.origin)); const double start = isX ? origin.getX() : origin.getY(); const Range newRange (-start * scale.scale, ((isX ? getWidth() : getHeight()) - start) * scale.scale); if (range != newRange) { range = newRange; repaint(); } } void updateMarkers (MarkerListBase& markerList, EditorCanvasBase* canvas_, const int viewportWidth, const int viewportHeight) { canvas = canvas_; const int num = markerList.size(); Array requiredMarkers; requiredMarkers.ensureStorageAllocated (num); int i; for (i = 0; i < num; ++i) requiredMarkers.add (markerList.getMarker (i)); for (i = markers.size(); --i >= 0;) { MarkerComponent* marker = markers.getUnchecked (i); const int index = requiredMarkers.indexOf (marker->marker); if (index >= 0) { marker->updatePosition (viewportWidth, viewportHeight); requiredMarkers.removeValue (marker->marker); } else { if (marker->isMouseButtonDown()) marker->setBounds (-1, -1, 1, 1); else markers.remove (i); } } for (i = requiredMarkers.size(); --i >= 0;) { MarkerComponent* marker = new MarkerComponent (*this, canvas, requiredMarkers.getReference(i), isX, isX ? getHeight() : getWidth()); markers.add (marker); getParentComponent()->addAndMakeVisible (marker); marker->updatePosition (viewportWidth, viewportHeight); } } void paint (Graphics& g) { g.setFont (10.0f); g.setColour (Colour::greyLevel (0.9f)); TickIterator ticks (range.getStart(), range.getEnd(), range.getLength() / (isX ? getWidth() : getHeight()), 10, isX ? 50 : 80); float pos, tickLength; String label; while (ticks.getNextTick (pos, tickLength, label)) { if (pos > 0) { if (isX) { g.drawVerticalLine ((int) pos, getHeight() - tickLength * getHeight(), (float) getHeight()); g.drawSingleLineText (label, (int) pos + 2, getHeight() - 6); } else { g.drawHorizontalLine ((int) pos, getWidth() - tickLength * getWidth(), (float) getWidth()); g.drawTextAsPath (label, AffineTransform::rotation (float_Pi / -2.0f) .translated (getWidth() - 6.0f, pos - 2.0f)); } } } } void mouseDoubleClick (const MouseEvent& e) { if (isX) canvas->getMarkerList (true).createMarker (canvas->getMarkerList (true).getNonexistentMarkerName ("Marker"), xToPosition (e.x)); else canvas->getMarkerList (false).createMarker (canvas->getMarkerList (false).getNonexistentMarkerName ("Marker"), xToPosition (e.y)); } double xToPosition (const int x) const { return range.getStart() + x * range.getLength() / (isX ? getWidth() : getHeight()); } int positionToX (const double position) const { const float proportion = (float) ((position - range.getStart()) / range.getLength()); return isX ? proportionOfWidth (proportion) : proportionOfHeight (proportion); } //============================================================================== class MarkerComponent : public Component { public: MarkerComponent (RulerComponent& ruler_, EditorCanvasBase* const canvas_, const ValueTree& marker_, bool isX_, int headSize_) : ruler (ruler_), canvas (canvas_), marker (marker_), isX (isX_), headSize (headSize_ - 2), isDragging (false) { } ~MarkerComponent() { } void paint (Graphics& g) { g.setColour (Colours::lightblue.withAlpha (isMouseOverOrDragging() ? 0.9f : 0.5f)); g.fillPath (path); } void updatePosition (const int viewportWidth, const int viewportHeight) { RelativeCoordinate coord (getMarkerList().getCoordinate (marker)); const double pos = coord.resolve (&getMarkerList()); if (! ruler.range.contains (pos)) { setVisible (false); } else { setVisible (true); Point anchorPoint; if (isX) anchorPoint.setXY (ruler.positionToX (pos), ruler.getHeight()); else anchorPoint.setXY (ruler.getWidth(), ruler.positionToX (pos)); Component* const parent = getParentComponent(); anchorPoint = ruler.relativePositionToOtherComponent (parent, anchorPoint); const int width = 8; if (isX) setBounds (anchorPoint.getX() - width, anchorPoint.getY() - headSize, width * 2, viewportHeight + headSize); else setBounds (anchorPoint.getX() - headSize, anchorPoint.getY() - width, viewportWidth + headSize, width * 2); } labelText = "name: " + getMarkerList().getName (marker) + "\nposition: " + coord.toString(); updateLabel(); } void updateLabel() { if (isMouseOverOrDragging() && isVisible() && (getWidth() > 1 || getHeight() > 1)) label.update (getParentComponent(), labelText, Colours::darkgreen, isX ? getBounds().getCentreX() : getX() + headSize, isX ? getY() + headSize : getBounds().getCentreY(), true, true); else label.remove(); } bool hitTest (int x, int y) { return (isX ? y : x) < headSize; } void resized() { const float lineThickness = 1.0f; path.clear(); if (isX) { const float centre = getWidth() / 2 + 0.5f; path.addLineSegment (Line (centre, 2.0f, centre, getHeight() + 1.0f), lineThickness); path.addTriangle (1.0f, 0.0f, centre * 2.0f - 1.0f, 0.0f, centre, headSize + 1.0f); } else { const float centre = getHeight() / 2 + 0.5f; path.addLineSegment (Line (2.0f, centre, getWidth() + 1.0f, centre), lineThickness); path.addTriangle (0.0f, centre * 2.0f - 1.0f, 0.0f, 1.0f, headSize + 1.0f, centre); } updateLabel(); } void mouseDown (const MouseEvent& e) { mouseDownPos = e.getMouseDownPosition(); toFront (false); updateLabel(); canvas->getSelection().selectOnly (getMarkerList().getId (marker)); if (e.mods.isPopupMenu()) { isDragging = false; } else { isDragging = true; canvas->getUndoManager().beginNewTransaction(); } } void mouseDrag (const MouseEvent& e) { if (isDragging) { autoScrollForMouseEvent (e.getEventRelativeTo (canvas), isX, ! isX); canvas->getUndoManager().undoCurrentTransactionOnly(); Rectangle axis; if (isX) axis.setBounds (0, 0, getParentWidth(), headSize); else axis.setBounds (0, 0, headSize, getParentHeight()); if (axis.expanded (isX ? 500 : 30, isX ? 30 : 500).contains (e.x, e.y)) { RelativeCoordinate coord (getMarkerList().getCoordinate (marker)); MouseEvent rulerEvent (e.getEventRelativeTo (&ruler)); int rulerPos = isX ? (rulerEvent.x + getWidth() / 2 - mouseDownPos.getX()) : (rulerEvent.y + getHeight() / 2 - mouseDownPos.getY()); coord.moveToAbsolute (canvas->limitMarkerPosition (ruler.xToPosition (rulerPos)), &getMarkerList()); getMarkerList().setCoordinate (marker, coord); } else { getMarkerList().deleteMarker (marker); } } } void mouseUp (const MouseEvent& e) { canvas->getUndoManager().beginNewTransaction(); updateLabel(); } void mouseEnter (const MouseEvent& e) { updateLabel(); repaint(); } void mouseExit (const MouseEvent& e) { updateLabel(); repaint(); } MarkerListBase& getMarkerList() { return canvas->getMarkerList (isX); } ValueTree marker; const bool isX; private: RulerComponent& ruler; EditorCanvasBase* canvas; const int headSize; Path path; bool isDragging; FloatingLabelComponent label; String labelText; Point mouseDownPos; }; Range range; private: const bool isX; OwnedArray markers; EditorCanvasBase* canvas; }; //============================================================================== class CanvasViewport : public Viewport { public: CanvasViewport() { background = ImageCache::getFromMemory (BinaryData::brushed_aluminium_png, BinaryData::brushed_aluminium_pngSize); setOpaque (true); } ~CanvasViewport() { } void paint (Graphics& g) { g.setTiledImageFill (background, 0, 0, 1.0f); g.fillAll(); } void paintOverChildren (Graphics& g) { drawRecessedShadows (g, getMaximumVisibleWidth(), getMaximumVisibleHeight(), 14); } void visibleAreaChanged (int, int , int, int) { EditorPanelBase* p = dynamic_cast (getParentComponent()); if (p != 0) p->updateRulers(); } private: Image background; }; //============================================================================== Toolbar toolbar; CanvasViewport viewport; RulerComponent rulerX, rulerY; ScopedPointer infoPanel; TreeView tree; EditorCanvasBase* canvas; bool markersVisible, snappingEnabled; Image background; }; #endif // __JUCER_EDITORPANEL_H_8E192A99__