Skip to content

Commit 71492ea

Browse files
authored
Fix object context menu and other small things (#503)
- Fix object context menu after gtkmm 4 changes - Fix crash when object is moved to folder where a same named object already exists - Fix layout of fixture types window - Make sure random selection doesn't select the same output twice - Make sure timer effect doesn't initiate fade-out on first activation
1 parent 94313de commit 71492ea

File tree

8 files changed

+133
-87
lines changed

8 files changed

+133
-87
lines changed

gui/components/objectlist.cpp

Lines changed: 44 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22

33
#include <sigc++/signal.h>
44

5+
#include <gtkmm/applicationwindow.h>
56
#include <gtkmm/gestureclick.h>
67
#include <gtkmm/icontheme.h>
78

89
#include "gui/eventtransmitter.h"
910
#include "gui/instance.h"
11+
#include "gui/menufunctions.h"
1012

1113
#include "theatre/chase.h"
1214
#include "theatre/effect.h"
@@ -233,22 +235,19 @@ void ObjectList::constructContextMenu() {
233235

234236
FolderObject *obj = SelectedObject().Get();
235237
if (obj != &Instance::Management().RootFolder()) {
236-
std::shared_ptr<Gio::SimpleActionGroup> actions =
237-
Gio::SimpleActionGroup::create();
238+
Gio::ActionMap &actions = Instance::MainWindow();
238239
// Move up & down
239-
menu->append("Move up", "move_object_up");
240-
actions->add_action("move_object_up",
241-
sigc::mem_fun(*this, &ObjectList::onMoveUpSelected));
240+
AddMenuItem(actions, menu, "Move up", "move_object_up",
241+
sigc::mem_fun(*this, &ObjectList::onMoveUpSelected));
242242

243-
menu->append("Move down", "move_object_down");
244-
actions->add_action("move_object_down",
245-
sigc::mem_fun(*this, &ObjectList::onMoveDownSelected));
243+
AddMenuItem(actions, menu, "Move down", "move_object_down",
244+
sigc::mem_fun(*this, &ObjectList::onMoveDownSelected));
246245

247246
// Move-to submenu
248247
std::shared_ptr<Gio::Menu> move_to_menu = Gio::Menu::create();
249248

250249
int folder_counter = 0;
251-
constructFolderMenu(*move_to_menu, *actions,
250+
constructFolderMenu(move_to_menu, actions,
252251
Instance::Management().RootFolder(), folder_counter);
253252
menu->append_submenu("Move to", move_to_menu);
254253
}
@@ -257,33 +256,34 @@ void ObjectList::constructContextMenu() {
257256
_contextMenu.set_parent(_listView);
258257
}
259258

260-
void ObjectList::constructFolderMenu(Gio::Menu &menu,
261-
Gio::SimpleActionGroup &actions,
262-
Folder &folder, int &counter) {
263-
if (folder.Children().empty()) {
259+
void ObjectList::constructFolderMenu(const std::shared_ptr<Gio::Menu> &menu,
260+
Gio::ActionMap &actions, Folder &folder,
261+
int &counter) {
262+
std::shared_ptr<Gio::Menu> sub_menu;
263+
for (const ObservingPtr<FolderObject> &object : folder.Children()) {
264+
Folder *subFolder = dynamic_cast<Folder *>(object.Get());
265+
if (subFolder) {
266+
if (!sub_menu) {
267+
sub_menu = Gio::Menu::create();
268+
const std::string name = "moveto_" + std::to_string(counter);
269+
++counter;
270+
AddMenuItem(actions, sub_menu, ".", name,
271+
[&]() { onMoveSelected(&folder); });
272+
}
273+
constructFolderMenu(sub_menu, actions, *subFolder, counter);
274+
}
275+
}
276+
277+
if (sub_menu) {
278+
menu->append_submenu(folder.Name(), sub_menu);
279+
} else {
280+
// If the folder is empty, we create a single menu item
264281
const std::string name = "moveto_" + std::to_string(counter);
265282
++counter;
266-
menu.append(folder.Name(), name);
267-
std::shared_ptr<Gio::SimpleAction> parent =
268-
actions.add_action(name, [&]() { onMoveSelected(&folder); });
283+
std::shared_ptr<Gio::SimpleAction> item = AddMenuItem(
284+
actions, menu, folder.Name(), name, [&]() { onMoveSelected(&folder); });
269285

270-
if (&folder == SelectedObject()) parent->set_enabled(false);
271-
} else {
272-
std::shared_ptr<Gio::Menu> sub_menu;
273-
for (const ObservingPtr<FolderObject> &object : folder.Children()) {
274-
Folder *subFolder = dynamic_cast<Folder *>(object.Get());
275-
if (subFolder) {
276-
if (!sub_menu) {
277-
sub_menu = Gio::Menu::create();
278-
const std::string name = "moveto_" + std::to_string(counter);
279-
++counter;
280-
menu.append(".", name);
281-
actions.add_action(name, [&]() { onMoveSelected(&folder); });
282-
}
283-
constructFolderMenu(*sub_menu, actions, *subFolder, counter);
284-
}
285-
}
286-
menu.append_submenu(folder.Name(), sub_menu);
286+
if (&folder == SelectedObject()) item->set_enabled(false);
287287
}
288288
}
289289

@@ -304,8 +304,17 @@ void ObjectList::TreeViewWithMenu::OnButtonPress(double x, double y) {
304304
}
305305

306306
void ObjectList::onMoveSelected(Folder *destination) {
307-
Folder::Move(SelectedObject(), *destination);
308-
Instance::Events().EmitUpdate();
307+
try {
308+
Folder::Move(SelectedObject(), *destination);
309+
Instance::Events().EmitUpdate();
310+
} catch (std::exception &e) {
311+
dialog_ = std::make_unique<Gtk::MessageDialog>(
312+
Instance::MainWindow(),
313+
"Could not move item to " + destination->Name() + ": " + e.what(),
314+
false, Gtk::MessageType::ERROR, Gtk::ButtonsType::OK, true);
315+
dialog_->signal_response().connect([this](int) { dialog_.reset(); });
316+
dialog_->show();
317+
}
309318
}
310319

311320
void ObjectList::onMoveUpSelected() {

gui/components/objectlist.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <gtkmm/iconpaintable.h>
1010
#include <gtkmm/image.h>
1111
#include <gtkmm/liststore.h>
12+
#include <gtkmm/messagedialog.h>
1213
#include <gtkmm/popovermenu.h>
1314
#include <gtkmm/scrolledwindow.h>
1415
#include <gtkmm/treeview.h>
@@ -114,8 +115,9 @@ class ObjectList : public Gtk::ScrolledWindow {
114115
bool selectObject(const theatre::FolderObject &object,
115116
const Gtk::TreeModel::Children &children);
116117
void constructContextMenu();
117-
void constructFolderMenu(Gio::Menu &menu, Gio::SimpleActionGroup &actions,
118-
theatre::Folder &folder, int &counter);
118+
void constructFolderMenu(const std::shared_ptr<Gio::Menu> &menu,
119+
Gio::ActionMap &actions, theatre::Folder &folder,
120+
int &counter);
119121
void onMoveSelected(theatre::Folder *destination);
120122
void onMoveUpSelected();
121123
void onMoveDownSelected();
@@ -126,6 +128,7 @@ class ObjectList : public Gtk::ScrolledWindow {
126128

127129
RecursionLock _avoidRecursion;
128130
Gtk::PopoverMenu _contextMenu;
131+
std::unique_ptr<Gtk::MessageDialog> dialog_;
129132
};
130133

131134
} // namespace glight::gui

gui/instance.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33

44
#include <memory>
55

6+
namespace Gtk {
7+
class ApplicationWindow;
8+
}
9+
610
namespace glight {
711
namespace theatre {
812
class Management;
@@ -51,13 +55,19 @@ class Instance {
5155

5256
static system::Settings& Settings() { return *Get().settings_; }
5357

58+
static Gtk::ApplicationWindow& MainWindow() { return *Get().main_window_; }
59+
void SetMainWindow(Gtk::ApplicationWindow& main_window) {
60+
main_window_ = &main_window;
61+
}
62+
5463
private:
5564
// Because this class is used in many places, all members are pointers
5665
// so that extra includes can be avoided.
5766
theatre::Management* management_;
5867
EventTransmitter* events_;
5968
FixtureSelection* selection_;
6069
GUIState* state_;
70+
Gtk::ApplicationWindow* main_window_;
6171
std::unique_ptr<system::Settings> settings_;
6272
};
6373

gui/mainwindow/mainwindow.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ MainWindow::MainWindow() : main_menu_(*this) {
5050
Instance::Get().SetState(_state);
5151
Instance::Get().SetSelection(_fixtureSelection);
5252
Instance::Get().SetEvents(*this);
53+
Instance::Get().SetMainWindow(*this);
5354
Instance::Settings() = system::LoadSettings();
5455
_management = std::make_unique<theatre::Management>(Instance::Settings());
5556
Instance::Get().SetManagement(*_management);

gui/menufunctions.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@
88
namespace glight::gui {
99

1010
inline std::shared_ptr<Gio::SimpleAction> AddMenuItem(
11-
Gio::ActionMap& actions, std::shared_ptr<Gio::Menu>& menu,
11+
Gio::ActionMap& actions, const std::shared_ptr<Gio::Menu>& menu,
1212
const Glib::ustring& label, const Glib::ustring& action_name,
1313
const sigc::slot<void()>& slot) {
1414
menu->append(label, "win." + action_name);
1515
return actions.add_action(action_name, slot);
1616
};
1717

1818
inline std::shared_ptr<Gio::SimpleAction> AddToggleMenuItem(
19-
Gio::ActionMap& actions, std::shared_ptr<Gio::Menu>& menu,
19+
Gio::ActionMap& actions, const std::shared_ptr<Gio::Menu>& menu,
2020
const Glib::ustring& label, const Glib::ustring& action_name,
2121
bool initial_value, const sigc::slot<void(bool)>& slot) {
2222
auto action_toggle =

gui/windows/fixturetypeswindow.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ FixtureTypesWindow::FixtureTypesWindow() : functions_frame_(*this) {
4040
tree_view_.append_column("Functions", list_columns_.functions_);
4141
tree_view_.get_selection()->signal_changed().connect(
4242
[&]() { onSelectionChanged(); });
43+
tree_view_.set_expand(true);
4344
fillList();
4445
type_scrollbars_.set_child(tree_view_);
4546

@@ -48,8 +49,7 @@ FixtureTypesWindow::FixtureTypesWindow() : functions_frame_(*this) {
4849
left_box_.append(type_scrollbars_);
4950

5051
paned_.set_start_child(left_box_);
51-
left_box_.set_hexpand(true);
52-
left_box_.set_vexpand(true);
52+
left_box_.set_expand(true);
5353

5454
// Right part
5555
right_grid_.attach(name_label_, 0, 0);

theatre/effects/randomselecteffect.h

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,9 @@ namespace glight::theatre {
1010

1111
class RandomSelectEffect final : public Effect {
1212
public:
13-
RandomSelectEffect()
14-
: Effect(1),
15-
_active{false, false},
16-
_startTime{0.0, 0.0},
17-
_delay(10000.0),
18-
_count(1){};
13+
RandomSelectEffect() : Effect(1){};
1914

20-
virtual EffectType GetType() const override {
21-
return EffectType::RandomSelect;
22-
}
15+
virtual EffectType GetType() const final { return EffectType::RandomSelect; }
2316

2417
double Delay() const { return _delay; }
2518
void SetDelay(double delay) { _delay = delay; }
@@ -29,26 +22,23 @@ class RandomSelectEffect final : public Effect {
2922

3023
private:
3124
virtual void MixImplementation(const ControlValue *values,
32-
const Timing &timing,
33-
bool primary) final override {
34-
size_t count = std::min(_count, Connections().size());
35-
if (values[0] && count != 0) {
25+
const Timing &timing, bool primary) final {
26+
size_t n_active = std::min(_count, Connections().size());
27+
if (values[0] && n_active != 0) {
3628
std::vector<size_t> &activeConnections = _activeConnections[primary];
3729
if (Connections().size() != activeConnections.size()) {
3830
activeConnections.resize(Connections().size());
3931
for (size_t i = 0; i != activeConnections.size(); ++i)
4032
activeConnections[i] = i;
41-
std::shuffle(activeConnections.begin(), activeConnections.end(),
42-
timing.RNG());
33+
Shuffle(activeConnections, timing, false);
4334
}
4435
if (!_active[primary] ||
4536
timing.TimeInMS() - _startTime[primary] > _delay) {
4637
_active[primary] = true;
4738
_startTime[primary] = timing.TimeInMS();
48-
std::shuffle(activeConnections.begin(), activeConnections.end(),
49-
timing.RNG());
39+
Shuffle(activeConnections, timing, true);
5040
}
51-
for (size_t i = 0; i != count; ++i) {
41+
for (size_t i = 0; i != n_active; ++i) {
5242
if (activeConnections[i] < Connections().size()) {
5343
const std::pair<Controllable *, size_t> &connection =
5444
Connections()[activeConnections[i]];
@@ -60,12 +50,25 @@ class RandomSelectEffect final : public Effect {
6050
}
6151
}
6252

63-
bool _active[2];
53+
void Shuffle(std::vector<size_t> &list, const Timing &timing,
54+
bool is_restart) {
55+
if (!list.empty()) {
56+
// If only one output is active, require the output to change
57+
// when reshuffling if it is a restart.
58+
bool must_change = _count == 1 && list.size() > 1 && is_restart;
59+
size_t first_active = list.front();
60+
do {
61+
std::shuffle(list.begin(), list.end(), timing.RNG());
62+
} while (must_change && first_active == list.front());
63+
}
64+
}
65+
66+
bool _active[2] = {false, false};
6467
std::array<std::vector<size_t>, 2> _activeConnections;
65-
double _startTime[2];
68+
double _startTime[2] = {0.0, 0.0};
6669

67-
double _delay;
68-
size_t _count;
70+
double _delay = 10000.0;
71+
size_t _count = 1;
6972
};
7073

7174
} // namespace glight::theatre

theatre/effects/timereffect.h

Lines changed: 44 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include "../effect.h"
55
#include "../transition.h"
66

7+
#include "system/optionalnumber.h"
78
#include "system/timepattern.h"
89

910
namespace glight::theatre {
@@ -35,40 +36,59 @@ class TimerEffect final : public Effect {
3536
const Timing& timing, bool primary) final {
3637
if (values[0]) {
3738
if (NowInRange(start_, end_)) {
38-
if (!is_on_) {
39-
is_on_ = true;
40-
transition_start_ = timing.TimeInMS();
41-
}
42-
if (timing.TimeInMS() - transition_start_ >
43-
transition_in_.LengthInMs()) {
44-
setAllOutputs(values[0]);
45-
} else {
46-
ControlValue multiplier = transition_in_.InValue(
47-
timing.TimeInMS() - transition_start_, timing);
48-
setAllOutputs(values[0] * multiplier);
49-
}
39+
MixOn(values[0], timing, primary);
5040
} else {
51-
if (is_on_) {
52-
is_on_ = false;
53-
transition_start_ = timing.TimeInMS();
54-
}
55-
if (timing.TimeInMS() - transition_start_ <=
56-
transition_out_.LengthInMs()) {
57-
ControlValue multiplier = transition_out_.OutValue(
58-
timing.TimeInMS() - transition_start_, timing);
59-
setAllOutputs(values[0] * multiplier);
60-
}
41+
MixOff(values[0], timing, primary);
6142
}
6243
}
6344
}
6445

6546
private:
47+
void MixOn(const ControlValue& input, const Timing& timing, bool primary) {
48+
if (!is_on_[primary]) {
49+
is_on_[primary] = true;
50+
transition_start_[primary] = timing.TimeInMS();
51+
}
52+
if (transition_start_[primary]) {
53+
const int start = *transition_start_[primary];
54+
if (timing.TimeInMS() - start < transition_in_.LengthInMs()) {
55+
const ControlValue multiplier =
56+
transition_in_.InValue(timing.TimeInMS() - start, timing);
57+
setAllOutputs(input * multiplier);
58+
} else {
59+
transition_start_[primary].Reset();
60+
}
61+
}
62+
if (!transition_start_[primary]) {
63+
setAllOutputs(input);
64+
}
65+
}
66+
67+
void MixOff(const ControlValue& input, const Timing& timing, bool primary) {
68+
if (is_on_[primary]) {
69+
is_on_[primary] = false;
70+
transition_start_[primary] = timing.TimeInMS();
71+
}
72+
if (transition_start_[primary]) {
73+
const int start = *transition_start_[primary];
74+
if (timing.TimeInMS() - start < transition_out_.LengthInMs()) {
75+
const ControlValue multiplier =
76+
transition_out_.OutValue(timing.TimeInMS() - start, timing);
77+
setAllOutputs(input * multiplier);
78+
} else {
79+
transition_start_[primary].Reset();
80+
}
81+
}
82+
}
83+
6684
Transition transition_in_ = Transition(300, TransitionType::Fade);
6785
Transition transition_out_ = Transition(300, TransitionType::Fade);
6886
system::TimePattern start_;
6987
system::TimePattern end_;
70-
int transition_start_ = 0;
71-
bool is_on_ = false;
88+
/// If unset, no transition is ongoing. If set, it is the transition start
89+
/// time.
90+
system::OptionalNumber<int> transition_start_[2];
91+
bool is_on_[2] = {false, false};
7292
};
7393

7494
} // namespace glight::theatre

0 commit comments

Comments
 (0)