ReaWwise

REAPER extension
Log | Files | Refs | Submodules

ImportPreviewComponent.cpp (13402B)


      1 /*----------------------------------------------------------------------------------------
      2 
      3 Copyright (c) 2023 AUDIOKINETIC Inc.
      4 
      5 This file is licensed to use under the license available at:
      6 https://github.com/audiokinetic/ReaWwise/blob/main/License.txt (the "License").
      7 You may not use this file except in compliance with the License.
      8 
      9 Unless required by applicable law or agreed to in writing, software distributed
     10 under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
     11 CONDITIONS OF ANY KIND, either express or implied.  See the License for the
     12 specific language governing permissions and limitations under the License.
     13 
     14 ----------------------------------------------------------------------------------------*/
     15 
     16 #include "ImportPreviewComponent.h"
     17 
     18 #include "Helpers/ImportHelper.h"
     19 #include "Helpers/WwiseHelper.h"
     20 #include "Model/IDs.h"
     21 
     22 #include <set>
     23 #include <unordered_map>
     24 
     25 namespace AK::WwiseTransfer
     26 {
     27 	namespace ImportPreviewComponentConstants
     28 	{
     29 		constexpr int headerHeight = 24;
     30 		constexpr int columnInitialWidth = 10;
     31 		constexpr int columnMinimumWidth = 10;
     32 		constexpr int columnMaximumWidth = 1000;
     33 		const std::initializer_list<juce::String> tableHeaders{"Name", "Object Status", "Originals WAV", "WAV Status"};
     34 		constexpr int defaultColumnPropertyFlags = juce::TableHeaderComponent::ColumnPropertyFlags::visible | juce::TableHeaderComponent::ColumnPropertyFlags::resizable | juce::TableHeaderComponent::ColumnPropertyFlags::sortable;
     35 		constexpr int iconPadding = 2;
     36 		constexpr int disabledFlag = 128;
     37 	} // namespace ImportPreviewComponentConstants
     38 
     39 	enum TreeValueItemColumn
     40 	{
     41 		Name = 1,
     42 		ObjectStatus,
     43 		OriginalsWav,
     44 		WavStatus
     45 	};
     46 
     47 	ImportPreviewComponent::ImportPreviewComponent(juce::ValueTree appState)
     48 		: applicationState(appState)
     49 		, previewItems(applicationState.getChildWithName(IDs::previewItems))
     50 		, rootItem(header, previewItems)
     51 		, previewLoading(applicationState, IDs::previewLoading, nullptr)
     52 	{
     53 		using namespace ImportPreviewComponentConstants;
     54 
     55 		auto featureSupport = appState.getChildWithName(IDs::featureSupport);
     56 		applyTemplateFeatureEnabled.referTo(featureSupport, IDs::applyTemplateFeatureEnabled, nullptr);
     57 
     58 		treeView.setDefaultOpenness(true);
     59 		treeView.setRootItem(&rootItem);
     60 		treeView.setRootItemVisible(false);
     61 		treeView.setMultiSelectEnabled(true);
     62 		treeView.setWantsKeyboardFocus(true);
     63 		treeView.addKeyListener(this);
     64 
     65 		for(const auto& tableHeader : tableHeaders)
     66 		{
     67 			auto index = (&tableHeader - tableHeaders.begin());
     68 			header.addColumn(tableHeader, index + 1, columnInitialWidth, columnMinimumWidth, columnMaximumWidth, defaultColumnPropertyFlags);
     69 		}
     70 
     71 		header.setStretchToFitActive(true);
     72 
     73 		applicationState.addListener(this);
     74 
     75 		emptyState.setText("Current Render settings will not result in any rendered files.", juce::dontSendNotification);
     76 		emptyState.setJustificationType(juce::Justification::centred);
     77 
     78 		addAndMakeVisible(header);
     79 		addAndMakeVisible(treeView);
     80 		addChildComponent(loadingComponent);
     81 
     82 		refreshHeader();
     83 
     84 		addChildComponent(emptyState);
     85 	}
     86 
     87 	ImportPreviewComponent::~ImportPreviewComponent()
     88 	{
     89 		applicationState.removeListener(this);
     90 		treeView.removeKeyListener(this);
     91 	}
     92 
     93 	void ImportPreviewComponent::resized()
     94 	{
     95 		auto area = getLocalBounds();
     96 
     97 		header.setBounds(area.removeFromTop(ImportPreviewComponentConstants::headerHeight));
     98 		header.resizeAllColumnsToFit(area.getWidth());
     99 
    100 		treeView.setBounds(area);
    101 
    102 		loadingComponent.setBounds(area);
    103 
    104 		emptyState.setBounds(area);
    105 	}
    106 
    107 	void ImportPreviewComponent::valueTreePropertyChanged(juce::ValueTree& treeWhosePropertyHasChanged,
    108 		const juce::Identifier& property)
    109 	{
    110 		if(property == IDs::previewLoading)
    111 		{
    112 			triggerAsyncUpdate();
    113 		}
    114 
    115 		if(treeWhosePropertyHasChanged.getType() == IDs::featureSupport && property == IDs::applyTemplateFeatureEnabled)
    116 		{
    117 			refreshHeader();
    118 		}
    119 	}
    120 
    121 	void ImportPreviewComponent::valueTreeChildAdded(juce::ValueTree& parentTree, juce::ValueTree& childWhichHasBeenAdded)
    122 	{
    123 		if(parentTree.getType() == IDs::previewItems)
    124 		{
    125 			triggerAsyncUpdate();
    126 		}
    127 	}
    128 
    129 	void ImportPreviewComponent::valueTreeChildRemoved(juce::ValueTree& parentTree, juce::ValueTree& childWhichHasBeenRemoved, int indexFromWhichChildWasRemoved)
    130 	{
    131 		if(parentTree.getType() == IDs::previewItems)
    132 		{
    133 			triggerAsyncUpdate();
    134 		}
    135 	}
    136 
    137 	void ImportPreviewComponent::handleAsyncUpdate()
    138 	{
    139 		loadingComponent.setVisible(previewLoading);
    140 		emptyState.setVisible(previewItems.getNumChildren() == 0 && !previewLoading);
    141 	}
    142 
    143 	void ImportPreviewComponent::refreshHeader()
    144 	{
    145 		using namespace ImportPreviewComponentConstants;
    146 
    147 		// For some reason, the column width measurement only works properly if the following attribute is set to false
    148 		header.setStretchToFitActive(false);
    149 
    150 		auto currentWidth = header.getColumnWidth(tableHeaders.size());
    151 		auto propertyFlags = defaultColumnPropertyFlags;
    152 
    153 		if(!applyTemplateFeatureEnabled.get())
    154 			propertyFlags |= disabledFlag;
    155 
    156 		header.removeColumn(tableHeaders.size());
    157 		header.addColumn(*(tableHeaders.end() - 1), tableHeaders.size(), currentWidth, columnMinimumWidth, columnMaximumWidth, propertyFlags);
    158 
    159 		header.setStretchToFitActive(true);
    160 	}
    161 
    162 	void ImportPreviewComponent::copySelectedItemsToClipBoard()
    163 	{
    164 		juce::String header("Name\tObject Status\tOriginals Wav\tWav Status\r\n");
    165 
    166 		juce::String body;
    167 		for(int i = 0; i < treeView.getNumSelectedItems(); ++i)
    168 		{
    169 			auto selectedItem = dynamic_cast<ValueTreeItem*>(treeView.getSelectedItem(i));
    170 
    171 			if(selectedItem)
    172 			{
    173 				auto valueTree = selectedItem->getValueTree();
    174 
    175 				auto previewItem = ImportHelper::valueTreeToPreviewItemNode(valueTree);
    176 
    177 				body << valueTree.getType() << "\t" << ImportHelper::objectStatusToReadableString(previewItem.objectStatus) << "\t"
    178 					 << previewItem.audioFilePath << "\t" << ImportHelper::wavStatusToReadableString(previewItem.wavStatus) << "\r\n";
    179 			}
    180 		}
    181 
    182 		if(body.isNotEmpty())
    183 			juce::SystemClipboard::copyTextToClipboard(header << body);
    184 	}
    185 
    186 	bool ImportPreviewComponent::keyPressed(const juce::KeyPress& key, juce::Component* originatingComponent)
    187 	{
    188 		if(key == juce::KeyPress('c', juce::ModifierKeys::ctrlModifier, 0))
    189 		{
    190 			copySelectedItemsToClipBoard();
    191 			return true;
    192 		}
    193 
    194 		return false;
    195 	}
    196 
    197 	ValueTreeItem::ValueTreeItem(juce::TableHeaderComponent& header, juce::ValueTree t)
    198 		: header(header)
    199 		, tree(t)
    200 	{
    201 		setDrawsInLeftMargin(true);
    202 
    203 		tree.addListener(this);
    204 		header.addListener(this);
    205 	}
    206 
    207 	ValueTreeItem::~ValueTreeItem()
    208 	{
    209 		tree.removeListener(this);
    210 		header.removeListener(this);
    211 	}
    212 
    213 	bool ValueTreeItem::mightContainSubItems()
    214 	{
    215 		return tree.getNumChildren() > 0;
    216 	}
    217 
    218 	void ValueTreeItem::itemOpennessChanged(bool isNowOpen)
    219 	{
    220 		if(isNowOpen && getNumSubItems() == 0)
    221 		{
    222 			refreshSubItems();
    223 		}
    224 		else
    225 		{
    226 			// TODO: there may be a performance benefit to call clear subitems. The issue with calling clearSubItems()
    227 			// here is that it loses the "openness" state of the tree.
    228 			// clearSubItems();
    229 		}
    230 	}
    231 
    232 	void ValueTreeItem::refreshSubItems()
    233 	{
    234 		clearSubItems();
    235 
    236 		auto isSorted = header.getSortColumnId() > 0;
    237 
    238 		if(isSorted)
    239 		{
    240 			auto comparator = Comparator(header);
    241 
    242 			for(int i = 0; i < tree.getNumChildren(); ++i)
    243 			{
    244 				addSubItemSorted(comparator, new ValueTreeItem(header, tree.getChild(i)));
    245 			}
    246 		}
    247 		else
    248 		{
    249 			for(int i = 0; i < tree.getNumChildren(); ++i)
    250 			{
    251 				addSubItem(new ValueTreeItem(header, tree.getChild(i)));
    252 			}
    253 		}
    254 	}
    255 
    256 	void ValueTreeItem::valueTreePropertyChanged(juce::ValueTree&, const juce::Identifier&)
    257 	{
    258 		repaintItem();
    259 	}
    260 
    261 	void ValueTreeItem::valueTreeChildAdded(juce::ValueTree& parentTree, juce::ValueTree&)
    262 	{
    263 		treeChildrenChanged(parentTree);
    264 	}
    265 
    266 	void ValueTreeItem::valueTreeChildRemoved(juce::ValueTree& parentTree, juce::ValueTree&, int)
    267 	{
    268 		treeChildrenChanged(parentTree);
    269 	}
    270 
    271 	void ValueTreeItem::valueTreeChildOrderChanged(juce::ValueTree& parentTree, int, int)
    272 	{
    273 		treeChildrenChanged(parentTree);
    274 	}
    275 
    276 	void ValueTreeItem::valueTreeParentChanged(juce::ValueTree&)
    277 	{
    278 	}
    279 
    280 	void ValueTreeItem::treeChildrenChanged(const juce::ValueTree& parentTree)
    281 	{
    282 		if(parentTree == tree)
    283 		{
    284 			refreshSubItems();
    285 			treeHasChanged();
    286 		}
    287 	}
    288 
    289 	void ValueTreeItem::paintItem(juce::Graphics& g, int width, int height)
    290 	{
    291 		if(isSelected())
    292 			g.fillAll(juce::LookAndFeel::getDefaultLookAndFeel().findColour(juce::TextEditor::highlightColourId));
    293 
    294 		using namespace ImportPreviewComponentConstants;
    295 
    296 		auto previewItem = ImportHelper::valueTreeToPreviewItemNode(tree);
    297 
    298 		std::array<juce::String, 4> cellText{
    299 			previewItem.name,
    300 			ImportHelper::objectStatusToReadableString(previewItem.objectStatus),
    301 			previewItem.audioFilePath,
    302 			ImportHelper::wavStatusToReadableString(previewItem.wavStatus),
    303 		};
    304 
    305 		jassert(cellText.size() == header.getNumColumns(true));
    306 
    307 		auto* customLookAndFeel = dynamic_cast<CustomLookAndFeel*>(&getOwnerView()->getLookAndFeel());
    308 		auto textColor = customLookAndFeel->getTextColourForObjectStatus(previewItem.objectStatus);
    309 
    310 		// First column is special since it has indentation + icon
    311 		auto indent = getItemPosition(true).getX();
    312 		int iconSize = height;
    313 		int trueColumnWidth = header.getColumnWidth(1) - indent;
    314 		int textWidth = trueColumnWidth - iconSize;
    315 
    316 		auto icon = customLookAndFeel->getIconForObjectType(previewItem.type);
    317 
    318 		g.setFont(CustomLookAndFeelConstants::smallFontSize);
    319 
    320 		if(trueColumnWidth > iconSize)
    321 		{
    322 			icon->drawWithin(g, juce::Rectangle<float>(iconSize, iconSize).reduced(iconPadding, iconPadding), juce::RectanglePlacement::centred, 1);
    323 
    324 			if(textWidth > 0)
    325 			{
    326 				auto objectNameColor = previewItem.unresolvedWildcard ? getOwnerView()->getLookAndFeel().findColour(ValueTreeItem::errorOutlineColor) : textColor;
    327 				auto objectName = previewItem.unresolvedWildcard ? "<unresolved_wildcard>" : cellText[0];
    328 
    329 				g.setColour(objectNameColor);
    330 				g.drawText(objectName,
    331 					iconSize, 0, textWidth, height,
    332 					juce::Justification::centredLeft, true);
    333 			}
    334 		}
    335 
    336 		if(!previewItem.unresolvedWildcard)
    337 		{
    338 			auto xPosition = trueColumnWidth;
    339 
    340 			g.setColour(textColor);
    341 
    342 			// The rest of the cells are just text, so no special text coordinates to calculate
    343 			for(int i = 1; i < cellText.size(); ++i)
    344 			{
    345 				auto columnWidth = header.getColumnWidth(i + 1);
    346 
    347 				g.drawText(cellText[i],
    348 					xPosition, 0, columnWidth, height,
    349 					juce::Justification::centredLeft, true);
    350 
    351 				xPosition += columnWidth;
    352 			}
    353 		}
    354 	}
    355 
    356 	void ValueTreeItem::tableColumnsChanged(juce::TableHeaderComponent* tableHeader)
    357 	{
    358 	}
    359 
    360 	void ValueTreeItem::tableColumnsResized(juce::TableHeaderComponent* tableHeader)
    361 	{
    362 		repaintItem();
    363 	}
    364 
    365 	void ValueTreeItem::tableSortOrderChanged(juce::TableHeaderComponent* tableHeader)
    366 	{
    367 		if(isOpen())
    368 		{
    369 			auto comparator = Comparator(header);
    370 
    371 			sortSubItems(comparator);
    372 			treeHasChanged();
    373 		}
    374 	}
    375 
    376 	juce::String ValueTreeItem::getUniqueName() const
    377 	{
    378 		return tree.getType().toString();
    379 	}
    380 
    381 	juce::String ValueTreeItem::getComparisonTextForColumn(int column)
    382 	{
    383 		auto previewItem = ImportHelper::valueTreeToPreviewItemNode(tree);
    384 
    385 		switch(column)
    386 		{
    387 		case TreeValueItemColumn::Name:
    388 			return getUniqueName();
    389 		case TreeValueItemColumn::ObjectStatus:
    390 			return ImportHelper::objectStatusToReadableString(previewItem.objectStatus);
    391 		case TreeValueItemColumn::OriginalsWav:
    392 			return previewItem.audioFilePath;
    393 		case TreeValueItemColumn::WavStatus:
    394 			return ImportHelper::wavStatusToReadableString(previewItem.wavStatus);
    395 		default:
    396 			return juce::String();
    397 		}
    398 	}
    399 
    400 	void ValueTreeItem::itemClicked(const juce::MouseEvent& event)
    401 	{
    402 		if(event.mods == juce::ModifierKeys::rightButtonModifier)
    403 		{
    404 			auto onCopy = [this]
    405 			{
    406 				auto treeView = getOwnerView();
    407 
    408 				if(treeView)
    409 				{
    410 					auto parent = treeView->getParentComponent();
    411 
    412 					if(parent)
    413 					{
    414 						auto importPreviewComponent = dynamic_cast<ImportPreviewComponent*>(parent);
    415 
    416 						if(importPreviewComponent)
    417 						{
    418 							importPreviewComponent->copySelectedItemsToClipBoard();
    419 							return;
    420 						}
    421 					}
    422 				}
    423 
    424 				juce::AlertWindow::showMessageBoxAsync(juce::MessageBoxIconType::InfoIcon, "Copy Error", "Unable to add selected items to clipboard");
    425 			};
    426 
    427 			juce::PopupMenu contextMenu;
    428 			contextMenu.addItem("Copy to Clipboard", onCopy);
    429 
    430 			auto treeView = getOwnerView();
    431 
    432 			if(treeView)
    433 				contextMenu.showMenuAsync(juce::PopupMenu::Options().withParentComponent(treeView));
    434 		}
    435 	}
    436 
    437 	juce::ValueTree ValueTreeItem::getValueTree()
    438 	{
    439 		return tree;
    440 	}
    441 
    442 	ValueTreeItem::Comparator::Comparator(const juce::TableHeaderComponent& header)
    443 		: sortColumnId(header.getSortColumnId() == 0 ? 1 : header.getSortColumnId())
    444 		, sortDirectionForward(header.getSortColumnId() == 0 ? true : header.isSortedForwards())
    445 	{
    446 	}
    447 
    448 	int ValueTreeItem::Comparator::compareElements(juce::TreeViewItem* first, juce::TreeViewItem* second)
    449 	{
    450 		auto firstAsValueTreeItem = dynamic_cast<ValueTreeItem*>(first);
    451 		auto secondAsValueTreeItem = dynamic_cast<ValueTreeItem*>(second);
    452 
    453 		if(firstAsValueTreeItem && secondAsValueTreeItem)
    454 		{
    455 			auto firstTextValue = firstAsValueTreeItem->getComparisonTextForColumn(sortColumnId);
    456 			auto secondTextValue = secondAsValueTreeItem->getComparisonTextForColumn(sortColumnId);
    457 
    458 			auto compareVal = firstTextValue.compareNatural(secondTextValue);
    459 
    460 			return sortDirectionForward ? compareVal : -1 * compareVal;
    461 		}
    462 
    463 		return 0;
    464 	}
    465 } // namespace AK::WwiseTransfer