MIDI2LR 6.1.0.0
MIDI2LR is an application that interfaces MIDI controllers with Lightroom 6+/CC Classic. It processes MIDI input into develop parameter updates and photo actions, and sends MIDI output when parameters are changed for motorized feedback (on controllers that have motorized faders). A listing of available LightRoom commands is in the Wiki. Assistance on the code and design is welcome.
Loading...
Searching...
No Matches
MainContentComponent Class Referencefinal

#include <MainComponent.h>

+ Inheritance diagram for MainContentComponent:

Public Member Functions

 MainContentComponent (const CommandSet &command_set, Profile &profile, ProfileManager &profile_manager, SettingsManager &settings_manager, LrIpcOut &lr_ipc_out, MidiReceiver &midi_receiver, MidiSender &midi_sender)
 
 MainContentComponent (const MainContentComponent &other)=delete
 
 MainContentComponent (MainContentComponent &&other)=delete
 
 ~MainContentComponent ()
 
void Init ()
 
MainContentComponentoperator= (const MainContentComponent &other)=delete
 
MainContentComponentoperator= (MainContentComponent &&other)=delete
 
void SaveProfile () const
 

Private Member Functions

void handleAsyncUpdate () override
 
void LrIpcOutCallback (bool, bool)
 
void MidiCmdCallback (rsj::MidiMessage mm)
 
void paint (juce::Graphics &) override
 
void ProfileChanged (juce::XmlElement *xml_element, const juce::String &file_name)
 
void timerCallback () override
 
void UpdateConnectionLabel (const char *text, juce::Colour colour)
 

Private Attributes

juce::Label command_label_ {"Command", ""}
 
CommandTable command_table_ {"Table", nullptr}
 
CommandTableModel command_table_model_
 
juce::Label connection_label_ {"Connection", juce::translate("Not connected to Lightroom")}
 
juce::TextButton disconnect_button_ {juce::translate("Halt sending to Lightroom")}
 
juce::String last_command_
 
juce::TextButton load_button_ {juce::translate("Load")}
 
LrIpcOutlr_ipc_out_
 
MidiReceivermidi_receiver_
 
MidiSendermidi_sender_
 
Profileprofile_
 
ProfileManagerprofile_manager_
 
juce::Label profile_name_label_ {"ProfileNameLabel", ""}
 
juce::TextButton remove_row_button_ {juce::translate("Clear ALL rows")}
 
juce::TextButton remove_unassigned_button_ {juce::translate("Remove unassigned rows")}
 
juce::TextButton rescan_button_ {juce::translate("Rescan MIDI devices")}
 
size_t row_to_select_ {0}
 
juce::TextButton save_button_ {juce::translate("Save")}
 
juce::TextButton settings_button_ {juce::translate("Settings")}
 
std::unique_ptr< juce::DialogWindow > settings_dialog_
 
SettingsManagersettings_manager_
 
juce::Label title_label_ {"Title", "MIDI2LR"}
 
juce::DropShadowEffect title_shadow_
 
juce::Label version_label_
 

Constructor & Destructor Documentation

◆ MainContentComponent() [1/3]

MainContentComponent::MainContentComponent ( const CommandSet command_set,
Profile profile,
ProfileManager profile_manager,
SettingsManager settings_manager,
LrIpcOut lr_ipc_out,
MidiReceiver midi_receiver,
MidiSender midi_sender 
)
65 : ResizableLayout{this}, command_table_model_(command_set, profile), lr_ipc_out_{lr_ipc_out},
66 midi_receiver_{midi_receiver}, midi_sender_{midi_sender}, profile_(profile),
67 profile_manager_(profile_manager), settings_manager_(settings_manager) {
68 setSize(kMainWidth, kMainHeight);
69}
70
71catch (const std::exception& e) {
73 throw;
74}
LrIpcOut & lr_ipc_out_
Definition MainComponent.h:88
MidiReceiver & midi_receiver_
Definition MainComponent.h:89
CommandTableModel command_table_model_
Definition MainComponent.h:67
ProfileManager & profile_manager_
Definition MainComponent.h:92
Profile & profile_
Definition MainComponent.h:91
MidiSender & midi_sender_
Definition MainComponent.h:90
SettingsManager & settings_manager_
Definition MainComponent.h:93
void ExceptionResponse(gsl::czstring id, gsl::czstring fu, const std::exception &e) noexcept

◆ ~MainContentComponent()

MainContentComponent::~MainContentComponent ( )
77{
79}
juce::Label profile_name_label_
Definition MainComponent.h:71
void SetDefaultProfile(const juce::String &default_profile)
Definition SettingsManager.h:65

◆ MainContentComponent() [2/3]

MainContentComponent::MainContentComponent ( const MainContentComponent other)
delete

◆ MainContentComponent() [3/3]

MainContentComponent::MainContentComponent ( MainContentComponent &&  other)
delete

Member Function Documentation

◆ handleAsyncUpdate()

void MainContentComponent::handleAsyncUpdate ( )
overrideprivate
382{
383 try {
384 /* Update the last command label and set its color to green */
385 command_label_.setText(last_command_, juce::NotificationType::dontSendNotification);
386 command_label_.setColour(juce::Label::backgroundColourId, juce::Colours::greenyellow);
387 startTimer(1000);
388 /* Update the command table to add and/or select row corresponding to midi command */
389 command_table_.updateContent();
390 command_table_.selectRow(gsl::narrow_cast<int>(row_to_select_));
391 }
392 catch (const std::exception& e) {
394 throw;
395 }
396}
CommandTable command_table_
Definition MainComponent.h:66
juce::String last_command_
Definition MainComponent.h:80
juce::Label command_label_
Definition MainComponent.h:69
size_t row_to_select_
Definition MainComponent.h:94

◆ Init()

void MainContentComponent::Init ( )
97{
98 try {
102
103 /* Main title */
104 StandardLabelSettings(title_label_);
105 title_label_.setFont(juce::Font {36.F, juce::Font::bold});
106 title_label_.setComponentEffect(&title_shadow_);
107 title_label_.setBounds(kMainLeft, 10, kFullWidth, 30);
108 addToLayout(&title_label_, anchorMidLeft, anchorMidRight);
109 addAndMakeVisible(title_label_);
110
111 /* Version label */
112 StandardLabelSettings(version_label_);
113 version_label_.setBounds(kMainLeft, 40, kFullWidth, 10);
114 addToLayout(&version_label_, anchorMidLeft, anchorMidRight);
115 addAndMakeVisible(version_label_);
116
117 /* Connection status */
118 StandardLabelSettings(connection_label_);
119 connection_label_.setColour(juce::Label::textColourId, juce::Colours::black);
120 connection_label_.setColour(juce::Label::backgroundColourId, juce::Colours::red);
121 connection_label_.setJustificationType(juce::Justification::centred);
122 connection_label_.setBounds(200, 15, kConnectionLabelWidth, kStandardHeight);
123 addToLayout(&connection_label_, anchorMidLeft, anchorMidRight);
124 addAndMakeVisible(connection_label_);
125
126 /* Load button */
127 load_button_.setBounds(kFirstButtonX, kTopButtonY, kButtonWidth, kStandardHeight);
128 addToLayout(&load_button_, anchorMidLeft, anchorMidRight);
129 addAndMakeVisible(load_button_);
130 load_button_.onClick = [this] {
131 if (profile_.ProfileUnsaved()) {
132 if (juce::NativeMessageBox::showYesNoBox(juce::AlertWindow::WarningIcon,
133 juce::translate("MIDI2LR profiles"),
134 juce::translate("Profile changed. Do you want to save your changes? If you "
135 "continue without saving, your changes will be lost."))) {
136 SaveProfile();
137 }
138 }
139 juce::File profile_directory {settings_manager_.GetProfileDirectory()};
140 const auto directory_saved {profile_directory.exists()};
141 if (!directory_saved) [[unlikely]] {
142 profile_directory = juce::File::getSpecialLocation(juce::File::userDocumentsDirectory);
143 }
144 juce::FileChooser chooser {
145 juce::translate("Open profile"), profile_directory, "*.xml", true};
146 if (chooser.browseForFileToOpen()) {
147 if (const auto parsed {juce::parseXML(chooser.getResult())}) {
148 const auto new_profile {chooser.getResult()};
149 lr_ipc_out_.SendCommand(fmt::format(FMT_STRING("ChangedToFullPath {}\n"),
150 new_profile.getFullPathName().toStdString()));
151 profile_name_label_.setText(new_profile.getFileName(),
152 juce::NotificationType::dontSendNotification);
153 profile_.FromXml(parsed.get());
154 command_table_.updateContent();
155 command_table_.repaint();
156 if (!directory_saved) [[unlikely]] { /* haven't saved a directory yet */
158 new_profile.getParentDirectory().getFullPathName());
159 }
160 }
161 else {
162 rsj::Log(fmt::format(FMT_STRING("Unable to load profile {}."),
163 chooser.getResult().getFullPathName().toStdString()));
164 }
165 }
166 };
167
168 /* Save button */
169 save_button_.setBounds(kSecondButtonX, kTopButtonY, kButtonWidth, kStandardHeight);
170 addToLayout(&save_button_, anchorMidLeft, anchorMidRight);
171 addAndMakeVisible(save_button_);
172 save_button_.onClick = [this] { SaveProfile(); };
173
174 /* Settings button */
175 settings_button_.setBounds(kThirdButtonX, kTopButtonY, kButtonWidth, kStandardHeight);
176 addToLayout(&settings_button_, anchorMidLeft, anchorMidRight);
177 addAndMakeVisible(settings_button_);
178 settings_button_.onClick = [this] {
179 juce::DialogWindow::LaunchOptions dialog_options;
180 dialog_options.dialogTitle = juce::translate("Settings");
181 /* create new object */
182 auto component {std::make_unique<SettingsComponent>(settings_manager_)};
183 component->Init();
184 dialog_options.content.setOwned(component.release());
185 dialog_options.content->setSize(400, 300);
186 settings_dialog_.reset(dialog_options.create());
187 settings_dialog_->setVisible(true);
188 };
189
190 /* Command Table */
192 command_table_.setBounds(kMainLeft, kCommandTableY, kFullWidth, kCommandTableHeight);
193 addToLayout(&command_table_, anchorMidLeft, anchorMidRight);
194 addAndMakeVisible(command_table_);
195
196 /* Profile name label */
197 StandardLabelSettings(profile_name_label_);
198 profile_name_label_.setColour(juce::Label::textColourId, juce::Colours::black);
199 profile_name_label_.setBounds(kMainLeft, kProfileNameY, kLabelWidth, kStandardHeight);
200 profile_name_label_.setJustificationType(juce::Justification::centred);
201 addToLayout(&profile_name_label_, anchorMidLeft, anchorMidRight);
202 addAndMakeVisible(profile_name_label_);
203
204 /* Last MIDI command */
205 StandardLabelSettings(command_label_);
206 command_label_.setBounds(kCommandLabelX, kCommandLabelY, kLabelWidth, kStandardHeight);
207 addToLayout(&command_label_, anchorMidLeft, anchorMidRight);
208 addAndMakeVisible(command_label_);
209
210 /* Remove row button */
211 remove_row_button_.setBounds(kFirstButtonX, kBottomButtonY, kButtonWidth, kStandardHeight);
212 addToLayout(&remove_row_button_, anchorMidLeft, anchorMidRight);
213 addAndMakeVisible(remove_row_button_);
214 remove_row_button_.onClick = [this] {
215 if (command_table_.getNumRows() > 0) {
217 command_table_.updateContent();
218 }
219 };
220
221 /* Rescan MIDI button */
222 rescan_button_.setBounds(kSecondButtonX, kBottomButtonY, kButtonWidth, kStandardHeight);
223 addToLayout(&rescan_button_, anchorMidLeft, anchorMidRight);
224 addAndMakeVisible(rescan_button_);
225 rescan_button_.onClick = [this] {
226 /* Re-enumerate MIDI IN and OUT devices */
229 /* Send new CC parameters to MIDI Out devices */
230 lr_ipc_out_.SendCommand("FullRefresh 1\n");
231 };
232
233 /* Disconnect button */
234 disconnect_button_.setBounds(kThirdButtonX, kBottomButtonY, kButtonWidth, kStandardHeight);
235 disconnect_button_.setClickingTogglesState(true);
236 addToLayout(&disconnect_button_, anchorMidLeft, anchorMidRight);
237 addAndMakeVisible(disconnect_button_);
238 disconnect_button_.onClick = [this] {
239 if (disconnect_button_.getToggleState()) {
241 rsj::Log("Sending halted.");
242 }
243 else {
245 rsj::Log("Sending restarted.");
246 }
247 };
248
249 /* Delete unassigned rows */
250 remove_unassigned_button_.setBounds(kFirstButtonX, kBottomButtonY2, kButtonWidth,
251 kStandardHeight);
252 addToLayout(&remove_unassigned_button_, anchorMidLeft, anchorMidRight);
253 addAndMakeVisible(remove_unassigned_button_);
254 remove_unassigned_button_.onClick = [this] {
256 command_table_.updateContent();
257 };
258
259 /* Try to load a default.xml if the user has not set a profile directory */
260 if (settings_manager_.GetProfileDirectory().isEmpty()) {
261 const auto filename {rsj::AppDataFilePath(kDefaultsFile)};
262 const auto default_profile {juce::File(filename.data())};
263 if (const auto parsed {juce::parseXML(default_profile)}) {
264 profile_.FromXml(parsed.get());
265 command_table_.updateContent();
266 }
267 }
268 else {
269 const auto last_prof {settings_manager_.GetDefaultProfile()};
270 if (last_prof != juce::String()) { profile_manager_.SwitchToProfile(last_prof); }
271 else { /* otherwise use the last profile from the profile directory */
273 }
274 }
275
276 /* turn it on */
277 activateLayout();
278 }
279 catch (const std::exception& e) {
281 throw;
282 }
283}
void SendingRestart()
Definition LR_IPC_Out.cpp:77
void SendingStop()
Definition LR_IPC_Out.cpp:93
void AddCallback(_In_ T *const object, _In_ void(T::*const mf)(bool, bool))
Definition LR_IPC_Out.h:51
void SendCommand(std::string &&command) const
Definition LR_IPC_Out.cpp:67
juce::TextButton rescan_button_
Definition MainComponent.h:85
void SaveProfile() const
Definition MainComponent.cpp:285
void ProfileChanged(juce::XmlElement *xml_element, const juce::String &file_name)
Definition MainComponent.cpp:362
juce::Label version_label_
Definition MainComponent.h:74
juce::TextButton settings_button_
Definition MainComponent.h:87
juce::TextButton remove_row_button_
Definition MainComponent.h:83
std::unique_ptr< juce::DialogWindow > settings_dialog_
Definition MainComponent.h:95
juce::Label connection_label_
Definition MainComponent.h:70
void LrIpcOutCallback(bool, bool)
Definition MainComponent.cpp:340
juce::TextButton save_button_
Definition MainComponent.h:86
juce::DropShadowEffect title_shadow_
Definition MainComponent.h:68
juce::TextButton load_button_
Definition MainComponent.h:82
void MidiCmdCallback(rsj::MidiMessage mm)
Definition MainComponent.cpp:309
juce::Label title_label_
Definition MainComponent.h:72
juce::TextButton remove_unassigned_button_
Definition MainComponent.h:84
juce::TextButton disconnect_button_
Definition MainComponent.h:81
void AddCallback(_In_ T *const object, _In_ void(T::*const mf)(rsj::MidiMessage))
Definition MIDIReceiver.h:49
void RescanDevices()
Definition MIDIReceiver.cpp:66
void RescanDevices()
Definition MIDISender.cpp:114
bool ProfileUnsaved() const
Definition Profile.h:139
void RemoveUnassignedMessages()
Definition Profile.cpp:187
void RemoveAllRows()
Definition Profile.cpp:137
void FromXml(const juce::XmlElement *root)
Definition Profile.cpp:35
void SwitchToProfile(int profile_index)
Definition ProfileManager.cpp:57
void AddCallback(_In_ T *const object, _In_ void(T::*const mf)(juce::XmlElement *, const juce::String &))
Definition ProfileManager.h:49
juce::String GetDefaultProfile() const noexcept
Definition SettingsManager.h:42
juce::String GetProfileDirectory() const noexcept
Definition SettingsManager.h:57
void SetProfileDirectory(const juce::String &profile_directory)
Definition SettingsManager.h:81
std::string AppDataFilePath(const std::string &file_name)
void Log(const juce::String &info, const std::source_location &location=std::source_location::current()) noexcept
Definition Misc.cpp:131

◆ LrIpcOutCallback()

void MainContentComponent::LrIpcOutCallback ( bool  connected,
bool  sending_blocked 
)
private
341{
342 try {
343 const juce::MessageManagerLock mm_lock; /* as not called in message loop */
344 if (connected) {
345 if (sending_blocked) { UpdateConnectionLabel("Sending halted", juce::Colours::yellow); }
346 else {
347 UpdateConnectionLabel("Connected to Lightroom", juce::Colours::greenyellow);
348 }
349 }
350 else {
351 UpdateConnectionLabel("Not connected to Lightroom", juce::Colours::red);
352 }
353 }
354 catch (const std::exception& e) {
356 throw;
357 }
358}
void UpdateConnectionLabel(const char *text, juce::Colour colour)
Definition MainComponent.cpp:327

◆ MidiCmdCallback()

void MainContentComponent::MidiCmdCallback ( rsj::MidiMessage  mm)
private
310{
311 try {
312 /* Display the MIDI parameters and add/highlight row in table corresponding to the message msg
313 * is 1-based for channel, which display expects */
314 const rsj::MidiMessageId msg {mm};
315 last_command_ = fmt::format(FMT_STRING("{}: {}{} [{}]"), msg.channel, mm.message_type_byte,
316 msg.control_number, mm.value);
318 row_to_select_ = gsl::narrow_cast<size_t>(profile_.GetRowForMessage(msg));
319 triggerAsyncUpdate();
320 }
321 catch (const std::exception& e) {
323 throw;
324 }
325}
int GetRowForMessage(rsj::MidiMessageId message) const
Definition Profile.h:103
void InsertUnassigned(rsj::MidiMessageId message)
Definition Profile.cpp:121
MessageType message_type_byte
Definition MidiUtilities.h:124
int value
Definition MidiUtilities.h:127
Definition MidiUtilities.h:145

◆ operator=() [1/2]

MainContentComponent & MainContentComponent::operator= ( const MainContentComponent other)
delete

◆ operator=() [2/2]

MainContentComponent & MainContentComponent::operator= ( MainContentComponent &&  other)
delete

◆ paint()

void MainContentComponent::paint ( juce::Graphics &  g)
overrideprivate
299{ //-V2009 overridden method
300 try {
301 g.fillAll(juce::Colours::white);
302 }
303 catch (const std::exception& e) {
305 throw;
306 }
307}

◆ ProfileChanged()

void MainContentComponent::ProfileChanged ( juce::XmlElement *  xml_element,
const juce::String &  file_name 
)
private
364{ //-V2009 overridden method
365 try {
366 {
367 const juce::MessageManagerLock mm_lock;
368 profile_.FromXml(xml_element);
369 command_table_.updateContent();
370 profile_name_label_.setText(file_name, juce::NotificationType::dontSendNotification);
371 }
372 /* Send new CC parameters to MIDI Out devices */
373 lr_ipc_out_.SendCommand("FullRefresh 1\n");
374 }
375 catch (const std::exception& e) {
377 throw;
378 }
379}

◆ SaveProfile()

void MainContentComponent::SaveProfile ( ) const
286{
287 juce::File profile_directory {settings_manager_.GetProfileDirectory()};
288 if (!profile_directory.exists()) {
289 profile_directory = juce::File::getSpecialLocation(juce::File::userDocumentsDirectory);
290 }
291 juce::FileChooser chooser {juce::translate("Save profile"), profile_directory, "*.xml", true};
292 if (chooser.browseForFileToSave(true)) {
293 const auto selected_file {chooser.getResult().withFileExtension("xml")};
294 profile_.ToXmlFile(selected_file);
295 }
296}
void ToXmlFile(const juce::File &file)
Definition Profile.cpp:239

◆ timerCallback()

void MainContentComponent::timerCallback ( )
overrideprivate
399{
400 try {
401 /* reset the command label's background to white */
402 command_label_.setColour(juce::Label::backgroundColourId, juce::Colours::white);
403 juce::Timer::stopTimer();
404 }
405 catch (const std::exception& e) {
407 throw;
408 }
409}

◆ UpdateConnectionLabel()

void MainContentComponent::UpdateConnectionLabel ( const char *  text,
juce::Colour  colour 
)
private
328{
329 try {
330 connection_label_.setText(juce::translate(text),
331 juce::NotificationType::dontSendNotification);
332 connection_label_.setColour(juce::Label::backgroundColourId, colour);
333 }
334 catch (const std::exception& e) {
336 throw;
337 }
338}

Member Data Documentation

◆ command_label_

juce::Label MainContentComponent::command_label_ {"Command", ""}
private
69{"Command", ""};

◆ command_table_

CommandTable MainContentComponent::command_table_ {"Table", nullptr}
private
66{"Table", nullptr};

◆ command_table_model_

CommandTableModel MainContentComponent::command_table_model_
private

◆ connection_label_

juce::Label MainContentComponent::connection_label_ {"Connection", juce::translate("Not connected to Lightroom")}
private
70{"Connection", juce::translate("Not connected to Lightroom")};

◆ disconnect_button_

juce::TextButton MainContentComponent::disconnect_button_ {juce::translate("Halt sending to Lightroom")}
private
81{juce::translate("Halt sending to Lightroom")};

◆ last_command_

juce::String MainContentComponent::last_command_
private

◆ load_button_

juce::TextButton MainContentComponent::load_button_ {juce::translate("Load")}
private
82{juce::translate("Load")};

◆ lr_ipc_out_

LrIpcOut& MainContentComponent::lr_ipc_out_
private

◆ midi_receiver_

MidiReceiver& MainContentComponent::midi_receiver_
private

◆ midi_sender_

MidiSender& MainContentComponent::midi_sender_
private

◆ profile_

Profile& MainContentComponent::profile_
private

◆ profile_manager_

ProfileManager& MainContentComponent::profile_manager_
private

◆ profile_name_label_

juce::Label MainContentComponent::profile_name_label_ {"ProfileNameLabel", ""}
private
71{"ProfileNameLabel", ""};

◆ remove_row_button_

juce::TextButton MainContentComponent::remove_row_button_ {juce::translate("Clear ALL rows")}
private
83{juce::translate("Clear ALL rows")};

◆ remove_unassigned_button_

juce::TextButton MainContentComponent::remove_unassigned_button_ {juce::translate("Remove unassigned rows")}
private
84{juce::translate("Remove unassigned rows")};

◆ rescan_button_

juce::TextButton MainContentComponent::rescan_button_ {juce::translate("Rescan MIDI devices")}
private
85{juce::translate("Rescan MIDI devices")};

◆ row_to_select_

size_t MainContentComponent::row_to_select_ {0}
private
94{0};

◆ save_button_

juce::TextButton MainContentComponent::save_button_ {juce::translate("Save")}
private
86{juce::translate("Save")};

◆ settings_button_

juce::TextButton MainContentComponent::settings_button_ {juce::translate("Settings")}
private
87{juce::translate("Settings")};

◆ settings_dialog_

std::unique_ptr<juce::DialogWindow> MainContentComponent::settings_dialog_
private

◆ settings_manager_

SettingsManager& MainContentComponent::settings_manager_
private

◆ title_label_

juce::Label MainContentComponent::title_label_ {"Title", "MIDI2LR"}
private
72{"Title", "MIDI2LR"};

◆ title_shadow_

juce::DropShadowEffect MainContentComponent::title_shadow_
private

◆ version_label_

juce::Label MainContentComponent::version_label_
private
Initial value:
{
"Version", juce::translate("Version ") + juce::String {ProjectInfo::versionString}}
74 {
75 "Version", juce::translate("Version ") + juce::String {ProjectInfo::versionString}};

The documentation for this class was generated from the following files: