summaryrefslogtreecommitdiff
path: root/ui/exec_finished_box.cpp
blob: b063ca59e7c02829960212106a31d0044f1180e1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
// **************************************************************************
// * This file is part of the FreeFileSync project. It is distributed under *
// * GNU General Public License: http://www.gnu.org/licenses/gpl.html       *
// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved        *
// **************************************************************************

#include "exec_finished_box.h"
#include <deque>
#include <zen/i18n.h>
#include <algorithm>
#include <zen/stl_tools.h>
#ifdef FFS_WIN
#include <zen/win_ver.h>
#endif

using namespace zen;


namespace
{
const std::wstring cmdTxtCloseProgressDlg = L"Close progress dialog"; //special command //mark for extraction: _("Close progress dialog")

const std::wstring separationLine(L"---------------------------------------------------------------------------------------------------------------");

std::vector<std::pair<std::wstring, std::wstring>> getDefaultCommands() //(gui name/command) pairs
{
    std::vector<std::pair<std::wstring, std::wstring>> output;

    auto addEntry = [&](const std::wstring& name, const std::wstring& value) { output.push_back(std::make_pair(name, value)); };

#ifdef FFS_WIN
    if (zen::vistaOrLater())
    {
        addEntry(_("Standby"  ), L"rundll32.exe powrprof.dll,SetSuspendState Sleep"); //suspend/Suspend to RAM/sleep
        addEntry(_("Log off"  ), L"shutdown /l");
        addEntry(_("Shut down"), L"shutdown /s /t 60");
        //addEntry(_("Hibernate"), L"shutdown /h"); //Suspend to disk -> Standby is better anyway
    }
    else //XP
    {
        addEntry(_("Standby"), L"rundll32.exe powrprof.dll,SetSuspendState"); //this triggers standby OR hibernate, depending on whether hibernate setting is active!
        addEntry(_("Log off"  ), L"shutdown -l");
        addEntry(_("Shut down"), L"shutdown -s -t 60");
        //no suspend on XP?
    }

#elif defined FFS_LINUX
    addEntry(_("Standby"  ), L"sudo pm-suspend");
    addEntry(_("Log off"  ), L"gnome-session-quit"); //alternative requiring admin: sudo killall Xorg
    addEntry(_("Shut down"), L"dbus-send --print-reply --dest=org.gnome.SessionManager /org/gnome/SessionManager org.gnome.SessionManager.RequestShutdown");
    //alternative requiring admin: sudo shutdown -h 1
    //addEntry(_("Hibernate"), L"sudo pm-hibernate");
    //alternative: "pmi action suspend" and "pmi action hibernate", require "sudo apt-get install powermanagement-interaface"

#elif defined FFS_MAC
    addEntry(_("Standby"  ), L"osascript -e \'tell application \"System Events\" to sleep\'");
    addEntry(_("Log off"  ), L"osascript -e \'tell application \"System Events\" to log out\'");
    addEntry(_("Shut down"), L"osascript -e \'tell application \"System Events\" to shut down\'");
#endif
    return output;
}

const wxEventType wxEVT_VALIDATE_USER_SELECTION = wxNewEventType();
}


bool isCloseProgressDlgCommand(const std::wstring& value)
{
    std::wstring tmp = value;
    trim(tmp);
    return tmp == cmdTxtCloseProgressDlg;
}


ExecFinishedBox::ExecFinishedBox(wxWindow* parent,
                                 wxWindowID id,
                                 const wxString& value,
                                 const wxPoint& pos,
                                 const wxSize& size,
                                 int n,
                                 const wxString choices[],
                                 long style,
                                 const wxValidator& validator,
                                 const wxString& name) :
    wxComboBox(parent, id, value, pos, size, n, choices, style, validator, name),
    history_(nullptr),
    historyMax_(0),
    defaultCommands(getDefaultCommands())
{
    //#####################################
    /*##*/ SetMinSize(wxSize(150, -1)); //## workaround yet another wxWidgets bug: default minimum size is much too large for a wxComboBox
    //#####################################

    Connect(wxEVT_KEY_DOWN,                  wxKeyEventHandler    (ExecFinishedBox::OnKeyEvent  ), nullptr, this);
    Connect(wxEVT_LEFT_DOWN,                 wxEventHandler       (ExecFinishedBox::OnUpdateList), nullptr, this);
    Connect(wxEVT_COMMAND_COMBOBOX_SELECTED, wxCommandEventHandler(ExecFinishedBox::OnSelection ), nullptr, this);
    Connect(wxEVT_MOUSEWHEEL,                wxMouseEventHandler  (ExecFinishedBox::OnMouseWheel), nullptr, this);

    Connect(wxEVT_VALIDATE_USER_SELECTION, wxCommandEventHandler(ExecFinishedBox::OnValidateSelection), nullptr, this);
}


void ExecFinishedBox::addItemHistory()
{
    if (history_)
    {
        std::wstring command = getValue();
        trim(command);

        bool skipCmd = command == separationLine         || //do not add sep. line
                       command == cmdTxtCloseProgressDlg || //do not add special command
                       command.empty();

        //do not add built-in commands to history
        if (!skipCmd)
        {
            for (auto it = defaultCommands.begin(); it != defaultCommands.end(); ++it)
                if (command == it->first ||
                    command == it->second)
                {
                    skipCmd = true;
                    break;
                }
        }

        if (!skipCmd)
            history_->insert(history_->begin(), command);

        if (history_->size() > historyMax_)
            history_->resize(historyMax_);
    }
}


std::wstring ExecFinishedBox::getValue() const
{
    const std::wstring value = zen::copyStringTo<std::wstring>(GetValue());

    {
        std::wstring tmp = value;
        trim(tmp);
        if (tmp == implementation::translate(cmdTxtCloseProgressDlg)) //have this symbolic constant translated properly
            return cmdTxtCloseProgressDlg;
    }

    return value;
}


void ExecFinishedBox::setValue(const std::wstring& value)
{
    std::wstring tmp = value;
    trim(tmp);

    if (tmp == cmdTxtCloseProgressDlg)
        setValueAndUpdateList(implementation::translate(cmdTxtCloseProgressDlg)); //have this symbolic constant translated properly
    else
        setValueAndUpdateList(value);
}

//set value and update list are technically entangled: see potential bug description below
void ExecFinishedBox::setValueAndUpdateList(const std::wstring& value)
{
    //it may be a little lame to update the list on each mouse-button click, but it should be working and we dont't have to manipulate wxComboBox internals

    std::deque<std::wstring> items;

    //1. special command
    items.push_back(implementation::translate(cmdTxtCloseProgressDlg));

    //2. built in commands
    for (auto it = defaultCommands.begin(); it != defaultCommands.end(); ++it)
        items.push_back(it->first);

    //3. history elements
    if (history_ && !history_->empty())
    {
        items.push_back(separationLine);
        items.insert(items.end(), history_->begin(), history_->end());
        std::sort(items.end() - history_->size(), items.end());
    }

    //attention: if the target value is not part of the dropdown list, SetValue() will look for a string that *starts with* this value:
    //e.g. if the dropdown list contains "222" SetValue("22") will erroneously set and select "222" instead, while "111" would be set correctly!
    // -> by design on Windows!
    if (std::find(items.begin(), items.end(), value) == items.end())
    {
        if (!value.empty())
            items.push_front(separationLine);
        items.push_front(value);
    }

    Clear();
    std::for_each(items.begin(), items.end(), [&](const std::wstring& item) { this->Append(item); });
    //this->SetSelection(wxNOT_FOUND); //don't select anything
    SetValue(value);          //preserve main text!
}


void ExecFinishedBox::OnSelection(wxCommandEvent& event)
{
    wxCommandEvent dummy2(wxEVT_VALIDATE_USER_SELECTION); //we cannot replace built-in commands at this position in call stack, so defer to a later time!
    if (auto handler = GetEventHandler())
        handler->AddPendingEvent(dummy2);

    event.Skip();
}


void ExecFinishedBox::OnValidateSelection(wxCommandEvent& event)
{
    const auto& value = getValue();

    if (value == separationLine)
        setValueAndUpdateList(std::wstring());
    else
        for (auto it = defaultCommands.begin(); it != defaultCommands.end(); ++it)
            if (it->first == value)
                return setValueAndUpdateList(it->second); //replace GUI name by actual command string
}


void ExecFinishedBox::OnUpdateList(wxEvent& event)
{
    setValue(getValue());
    event.Skip();
}


void ExecFinishedBox::OnKeyEvent(wxKeyEvent& event)
{
    switch (event.GetKeyCode())
    {
        case WXK_DELETE:
        case WXK_NUMPAD_DELETE:
        {
            //try to delete the currently selected config history item
            int pos = this->GetCurrentSelection();
            if (0 <= pos && pos < static_cast<int>(this->GetCount()) &&
                //what a mess...:
                (GetValue() != GetString(pos) || //avoid problems when a character shall be deleted instead of list item
                 GetValue() == wxEmptyString)) //exception: always allow removing empty entry
            {
                const std::wstring selValue = copyStringTo<std::wstring>(GetString(pos));

                if (history_ && std::find(history_->begin(), history_->end(), selValue) != history_->end()) //only history elements may be deleted
                {
                    //save old (selected) value: deletion seems to have influence on this
                    const wxString currentVal = this->GetValue();
                    //this->SetSelection(wxNOT_FOUND);

                    //delete selected row
                    vector_remove_if(*history_, [&](const std::wstring& item) { return item == selValue; });

                    SetString(pos, wxString()); //in contrast to Delete(), this one does not kill the drop-down list and gives a nice visual feedback!
                    //Delete(pos);

                    //(re-)set value
                    SetValue(currentVal);
                }

                //eat up key event
                return;
            }
        }
        break;

        case WXK_UP:
        case WXK_NUMPAD_UP:
        case WXK_DOWN:
        case WXK_NUMPAD_DOWN:
        case WXK_PAGEUP:
        case WXK_NUMPAD_PAGEUP:
        case WXK_PAGEDOWN:
        case WXK_NUMPAD_PAGEDOWN:
            return; //swallow -> using these keys gives a weird effect due to this weird control
    }
    event.Skip();
}
bgstack15