Technical/Command: command-system.cpp

File command-system.cpp, 10.5 KB (added by nielsm, 2 years ago)

Prototype of how a command system could be designed

Line 
1/******* command.h *******/
2
3class CommandCallParameters {
4public:
5        // The actual type of this doesn't matter but of course needs to be
6        // decided on at some point. It's probably worth looking towards
7        // Avisynth's filter parameter design.
8        typename CommandCallParameterValue;
9       
10        CommandCallParameterValue GetValue(const wxString &name) const;
11       
12        void SetValue(const wxString &name, const CommandCallParameterValue &value);
13};
14
15
16// Should probably derive from another more specific exception base class
17class CommandInvalidParametersException : public Aegisub::Exception;
18
19
20class Command {
21public:
22        /// @brief Invoke the command in interactive mode
23        /// @param project    The subtitle project to perform the command on
24        /// @param main_frame The main window the command was invoked in, usable
25        ///                   for parenting dialogue boxes and obtaining various
26        ///                   other GUI state.
27        virtual void InvokeInteractive(
28                SubtitleProject *project,
29                FrameMain *main_frame
30                ) const = 0;
31       
32        /// @brief Invoke the command in scripted mode
33        /// @param project    The subtitle project to perform the command on
34        /// @param parameters The parameters to the command
35        ///
36        /// This method should throw CommandInvalidParametersException if the
37        /// passed parameters are insufficient or invalid for the command.
38        virtual void InvokeScripted(
39                SubtitleProject *project,
40                const CommandCallParameters &parameters
41                ) const = 0;
42       
43        /// @brief Determine whether the command can meaningfully be invoked in
44        ///        interactive mode
45        /// @param project    The subtitle project to test whether the command can
46        ///                   be invoked on
47        /// @param main_frame The main window to test whether the command can be
48        ///                   invoked on
49        /// @return False if the command can't successfully complete in the given
50        ///         state, true if all conditions for the command to successfully
51        ///         complete are satisfied
52        ///
53        /// Takes the same parameters as InvokeInteractive() and should check
54        /// whether the user should be allowed to run the command in the current
55        /// combination of project and UI state. In practice this will control
56        /// whether the command's menu item and toolbar button will apppear grayed
57        /// out or clickable.
58        ///
59        /// @todo Possibly commands should react to certain UI events like changing
60        ///       selection so this can be updated in a sensible way without
61        ///       polling or other ugly hacks.
62        virtual bool CanInvokeInteractive(
63                const SubtitleProject *project,
64                const FrameMain *main_frame
65                ) const = 0;
66       
67       
68        /// @brief Get the internal name for the command
69        /// @return A static store null-terminated character string without spaces
70        ///         uniquely identifying the command
71        virtual const char * GetName() const = 0;
72       
73        /// @brief Get the translated menu name for the command
74        /// @return The name for the command, suitable for being displayed on
75        ///         a menu item. The name should be translated into the user's
76        ///         UI language and have an appropriate character prepended by
77        //          an ampersand to denote the access key.
78        virtual wxString GetMenuName() const = 0;
79       
80        /// @brief Get the translated display name for the command
81        /// @return The name for the command, suitable for display to the user in
82        ///         all other cases than being on a menu. The name must not
83        ///         contain an ampersand denoting access key or any formatting
84        ///         relating to access keys, but should be translated into the
85        ///         user's UI language.
86        virtual wxString GetDisplayName() const = 0;
87       
88        /// @brief Get the translated help string for the command
89        /// @return The help string concisely describing the purpose of the command
90        ///         to the user, translated into the UI language.
91        virtual wxString GetHelpString() const = 0;
92       
93       
94        /// @brief Get the default hotkeys assigned to the command
95        /// @return A vector of hotkey keystrokes assigned to the command by
96        ///         default. The first hotkey will be displayed in menu items and
97        ///         on toolbar tooltips.
98        ///
99        /// The default hotkeys are only used if the command hasn't been seen
100        /// before and there is no known hotkey list associated with it. It may
101        /// also be used if the user requests resetting the hotkeys for the
102        /// command. If a default hotkey keystroke has already been registered for
103        /// another command, the second command to claim the keystroke will not
104        /// get it assigned.
105        virtual std::vector<HotkeyKeystroke> GetDefaultHotkeys() const = 0;
106       
107        /// @todo Some way of specifying default menu and toolbar layouts and
108        ///       the behaviour if a new command appears after the menu and toolbar
109        ///       layout has been initially built, how the command can specify
110        ///       where it wants to sit.
111};
112
113
114class CommandRepository {
115public:
116        /// @brief Register a new command
117        /// @param cmd The command object to register
118        ///
119        /// The registration will be ignored if there is already a command with
120        /// the same internal name as the new command.
121        ///
122        /// If the command hasn't been seen before its default hotkeys and default
123        /// menu and toolbar positions are registered and the UI is updated.
124        ///
125        /// The command object must have been allocated with operator new and will
126        /// be owned by the command repository after having been registered. One
127        /// command object can only belong to one command repository at a time.
128        void RegisterCommand(Command *cmd);
129       
130        /// @brief Find a command by internal name
131        /// @param name Internal name of the command to locate
132        /// @return A const pointer to the command requested if it exists,
133        ///         otherwise 0.
134        const Command * FindCommand(const char *name) const;
135};
136
137
138/******* timing_commands.h *******/
139
140class CommandRepository;
141
142void RegisterTimingCommands(CommandRepository *repository);
143
144
145/******* timing_commands.cpp *******/
146
147#include "timing_commands.h"
148#include "command.h"
149
150
151namespace {
152
153        class MakeContinuous : public Command {
154                void DoWork(std::vector<AssDialogue*> &lines) const;
155       
156        public:
157                void InvokeInteractive(SubtitleProject *project, FrameMain *main_frame) const;
158                void InvokeScripted(SubtitleProject *project, const CommandCallParameters &parameters) const;
159                bool CanInvokeInteractive(const SubtitleProject *project, const FrameMain *main_frame) const;
160                const char * GetName() const { return "timing_make_continuous"; }
161                wxString GetMenuName() const { return _("Make &continuous"); }
162                wxString GetDisplayName() const { return _("Make continuous"); }
163                wxString GetHelpString() const { return _("Adjust the timings of the selected lines so there are no gaps between them"); }
164                std::vector<HotkeyKeystroke> GetDefaultHotkeys() const { return std::vector<HotkeyKeystroke>(); }
165        };
166       
167       
168        class ShiftTimes : public Command {
169                void ShiftByTime(std::vector<AssDialogue*> &lines, int amount_ms, bool adjust_start, bool adjust_end) const;
170                void ShiftByFrames(std::vector<AssDialogue*> &lines, int amount_frames, const FramerateData &vfr, bool adjust_start, bool adjust_end) const;
171               
172        public:
173                void InvokeInteractive(SubtitleProject *project, FrameMain *main_frame) const;
174                void InvokeScripted(SubtitleProject *project, const CommandCallParameters &parameters) const;
175                bool CanInvokeInteractive(const SubtitleProject *project, const FrameMain *main_frame) const;
176                const char * GetName() const { return "timing_shift"; }
177                wxString GetMenuName() const { return _("&Shift times"); }
178                wxString GetDisplayName() const { return _("Shift times"); }
179                wxString GetHelpString() const { return _("Change the timings of many lines by a fixed amount at once"); }
180                std::vector<HotkeyKeystroke> GetDefaultHotkeys() const { return std::vector<HotkeyKeystroke>(); }
181        };
182       
183};
184
185
186void MakeContinuous::DoWork(std::vector<AssDialogue*> &lines) const
187{
188        // Implement the algorithm here
189}
190
191void MakeContinuous::InvokeInteractive(SubtitleProject *project, FrameMain *main_frame) const
192{
193        // Should probably do some sanity checks here first
194        std::vector<AssDialogue*> lines;
195        main_frame->subs_grid->GetSelected(lines);
196        DoWork(lines);
197}
198
199void MakeContinuous::InvokeScripted(SubtitleProject *project, const CommandCallParameters &parameters) const
200{
201        std::vector<AssDialogue*> lines;
202        int first_line = parameters.GetValue(_T("from"));
203        int last_line = parameters.GetValue(_T("to"));
204        for (int i = first_line; i <= last_line; ++i)
205                lines.push_back(project->subtitles->GetDialogueByIndex(i));
206        DoWork(lines);
207}
208
209bool MakeContinuous::CanInvokeInteractive(const SubtitleProject *project, const FrameMain *main_frame) const
210{
211        bool multiple_lines_selected_and_adjacent;
212        // Do some checks here
213        return multiple_lines_selected_and_adjacent;
214}
215
216
217void ShiftTimes::ShiftByTime(std::vector<AssDialogue*> &lines, int amount_ms, bool adjust_start, bool adjust_end) const
218{
219        // Do work
220}
221
222void ShiftTimes::ShiftByFrames(std::vector<AssDialogue*> &lines, int amount_frames, const FramerateData &vfr, bool adjust_start, bool adjust_end) const
223{
224        // Do work
225}
226
227void ShiftTimes::InvokeInteractive(SubtitleProject *project, FrameMain *main_frame) const
228{
229        bool by_frames, adjust_start, adjust_end;
230        int amount_ms, amount_frames;
231        std::vector<AssDialogue*> lines;
232       
233        ShiftTimesDialogue dlg(main_frame, project);
234        if (dlg.ShowModal() == ID_OK)
235        {
236                // fill variables from dialogue
237                // invoke command (also see below for scripted version)
238        }
239}
240
241void ShiftTimes::InvokeScripted(SubtitleProject *project, const CommandCallParameters &parameters) const
242{
243        wxString mode = parameters.GetValue(_T("mode"));
244        bool start = parameters.GetValue(_T("start"));
245        bool end = parameters.GetValue(_T("end"));
246        std::vector<AssDialogue*> lines;
247       
248        // Do some more stuff to fill in lines, probably involving more parameters
249       
250        if (!mode || mode == _T("time"))
251        {
252                int amount_ms = parameters.GetValue(_T("ms"));
253                ShiftByTime(lines, ms, start, end);
254        }
255        else if (mode == _T("frames"))
256        {
257                int amount_frames = parameters.GetValue(_T("frames"));
258                ShiftByFrames(lines, frames, project->vfr, start, end);
259        }
260        else
261        {
262                throw CommandInvalidParametersException(GetName(), _T("mode"), _T("Value must be \"time\" or \"frames\", or empty to default to \"time\""));
263        }
264}
265
266bool ShiftTimes::CanInvokeInteractive(const SubtitleProject *project, const FrameMain *main_frame) const
267{
268        return true;
269}
270
271
272void RegisterTimingCommands(CommandRepository *repository)
273{
274        repository.RegisterCommand(new MakeContinuous);
275        repository.RegisterCommand(new ShiftTimes);
276}