1 /*
2 * This file is part of Poedit (http://poedit.net)
3 *
4 * Copyright (C) 2014-2015 Vaclav Slavik
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
23 *
24 */
25
26 #include "sidebar.h"
27
28 #include "catalog.h"
29 #include "customcontrols.h"
30 #include "commentdlg.h"
31 #include "concurrency.h"
32 #include "errors.h"
33 #include "hidpi.h"
34
35 #include "tm/suggestions.h"
36 #include "tm/transmem.h"
37
38 #include <wx/app.h>
39 #include <wx/button.h>
40 #include <wx/config.h>
41 #include <wx/dcclient.h>
42 #include <wx/menu.h>
43 #include <wx/sizer.h>
44 #include <wx/statbmp.h>
45 #include <wx/stattext.h>
46 #include <wx/wupdlock.h>
47
48 #include <algorithm>
49
50 #define SIDEBAR_BACKGROUND wxColour("#EDF0F4")
51 #define GRAY_LINES_COLOR wxColour(220,220,220)
52 #define GRAY_LINES_COLOR_DARK wxColour(180,180,180)
53
54
55 class SidebarSeparator : public wxWindow
56 {
57 public:
SidebarSeparator(wxWindow * parent)58 SidebarSeparator(wxWindow *parent)
59 : wxWindow(parent, wxID_ANY),
60 m_sides(SIDEBAR_BACKGROUND),
61 m_center(GRAY_LINES_COLOR_DARK)
62 {
63 Bind(wxEVT_PAINT, &SidebarSeparator::OnPaint, this);
64 }
65
DoGetBestSize() const66 wxSize DoGetBestSize() const override
67 {
68 return wxSize(-1, 1);
69 }
70
71 private:
OnPaint(wxPaintEvent &)72 void OnPaint(wxPaintEvent&)
73 {
74 wxPaintDC dc(this);
75 auto w = dc.GetSize().x;
76 dc.GradientFillLinear(wxRect(0,0,PX(15),PX(1)), m_sides, m_center);
77 dc.GradientFillLinear(wxRect(PX(15),0,w,PX(1)), m_center, m_sides);
78 }
79
80 wxColour m_sides, m_center;
81 };
82
83
SidebarBlock(Sidebar * parent,const wxString & label,int flags)84 SidebarBlock::SidebarBlock(Sidebar *parent, const wxString& label, int flags)
85 {
86 m_parent = parent;
87 m_sizer = new wxBoxSizer(wxVERTICAL);
88 if (!(flags & NoUpperMargin))
89 m_sizer->AddSpacer(PX(15));
90 if (!label.empty())
91 {
92 if (!(flags & NoUpperMargin))
93 {
94 m_sizer->Add(new SidebarSeparator(parent),
95 wxSizerFlags().Expand().Border(wxBOTTOM|wxLEFT, PX(2)));
96 }
97 m_headerSizer = new wxBoxSizer(wxHORIZONTAL);
98 m_headerSizer->Add(new HeadingLabel(parent, label), wxSizerFlags().Expand().Center());
99 m_sizer->Add(m_headerSizer, wxSizerFlags().Expand().PXDoubleBorder(wxLEFT|wxRIGHT));
100 }
101 m_innerSizer = new wxBoxSizer(wxVERTICAL);
102 m_sizer->Add(m_innerSizer, wxSizerFlags(1).Expand().PXDoubleBorder(wxLEFT|wxRIGHT));
103 }
104
Show(bool show)105 void SidebarBlock::Show(bool show)
106 {
107 m_sizer->ShowItems(show);
108 }
109
SetItem(const CatalogItemPtr & item)110 void SidebarBlock::SetItem(const CatalogItemPtr& item)
111 {
112 if (!item)
113 {
114 Show(false);
115 return;
116 }
117
118 bool use = ShouldShowForItem(item);
119 if (use)
120 Update(item);
121 Show(use);
122 }
123
124
125 class OldMsgidSidebarBlock : public SidebarBlock
126 {
127 public:
OldMsgidSidebarBlock(Sidebar * parent)128 OldMsgidSidebarBlock(Sidebar *parent)
129 /// TRANSLATORS: "Previous" as in used in the past, now replaced with newer.
130 : SidebarBlock(parent, _("Previous source text:"))
131 {
132 m_innerSizer->AddSpacer(PX(2));
133 m_innerSizer->Add(new ExplanationLabel(parent, _("The old source text (before it changed during an update) that the fuzzy translation corresponds to.")),
134 wxSizerFlags().Expand());
135 m_innerSizer->AddSpacer(PX(5));
136 m_text = new SelectableAutoWrappingText(parent, "");
137 m_innerSizer->Add(m_text, wxSizerFlags().Expand());
138 }
139
ShouldShowForItem(const CatalogItemPtr & item) const140 bool ShouldShowForItem(const CatalogItemPtr& item) const override
141 {
142 return item->HasOldMsgid();
143 }
144
Update(const CatalogItemPtr & item)145 void Update(const CatalogItemPtr& item) override
146 {
147 auto txt = wxJoin(item->GetOldMsgid(), ' ', '\0');
148 m_text->SetAndWrapLabel(txt);
149 }
150
151 private:
152 SelectableAutoWrappingText *m_text;
153 };
154
155
156 class ExtractedCommentSidebarBlock : public SidebarBlock
157 {
158 public:
ExtractedCommentSidebarBlock(Sidebar * parent)159 ExtractedCommentSidebarBlock(Sidebar *parent)
160 : SidebarBlock(parent, _("Notes for translators:"))
161 {
162 m_innerSizer->AddSpacer(PX(5));
163 m_comment = new SelectableAutoWrappingText(parent, "");
164 m_innerSizer->Add(m_comment, wxSizerFlags().Expand());
165 }
166
ShouldShowForItem(const CatalogItemPtr & item) const167 bool ShouldShowForItem(const CatalogItemPtr& item) const override
168 {
169 return item->HasExtractedComments();
170 }
171
Update(const CatalogItemPtr & item)172 void Update(const CatalogItemPtr& item) override
173 {
174 auto comment = wxJoin(item->GetExtractedComments(), '\n', '\0');
175 if (comment.StartsWith("TRANSLATORS:") || comment.StartsWith("translators:"))
176 {
177 comment.Remove(0, 12);
178 if (!comment.empty() && comment[0] == ' ')
179 comment.Remove(0, 1);
180 }
181 m_comment->SetAndWrapLabel(comment);
182 }
183
184 private:
185 SelectableAutoWrappingText *m_comment;
186 };
187
188
189 class CommentSidebarBlock : public SidebarBlock
190 {
191 public:
CommentSidebarBlock(Sidebar * parent)192 CommentSidebarBlock(Sidebar *parent)
193 : SidebarBlock(parent, _("Comment:"))
194 {
195 m_innerSizer->AddSpacer(PX(5));
196 m_comment = new SelectableAutoWrappingText(parent, "");
197 m_innerSizer->Add(m_comment, wxSizerFlags().Expand());
198 }
199
ShouldShowForItem(const CatalogItemPtr & item) const200 bool ShouldShowForItem(const CatalogItemPtr& item) const override
201 {
202 return item->HasComment();
203 }
204
Update(const CatalogItemPtr & item)205 void Update(const CatalogItemPtr& item) override
206 {
207 auto text = CommentDialog::RemoveStartHash(item->GetComment());
208 text.Trim();
209 m_comment->SetAndWrapLabel(text);
210 }
211
212 private:
213 SelectableAutoWrappingText *m_comment;
214 };
215
216
217 class AddCommentSidebarBlock : public SidebarBlock
218 {
219 public:
AddCommentSidebarBlock(Sidebar * parent)220 AddCommentSidebarBlock(Sidebar *parent) : SidebarBlock(parent, "")
221 {
222 #ifdef __WXMSW__
223 auto label = _("Add comment");
224 #else
225 auto label = _("Add Comment");
226 #endif
227 m_btn = new wxButton(parent, XRCID("menu_comment"), _("Add Comment"));
228 m_innerSizer->AddStretchSpacer();
229 m_innerSizer->Add(m_btn, wxSizerFlags().Right());
230 }
231
IsGrowable() const232 bool IsGrowable() const override { return true; }
233
ShouldShowForItem(const CatalogItemPtr &) const234 bool ShouldShowForItem(const CatalogItemPtr&) const override
235 {
236 return m_parent->FileHasCapability(Catalog::Cap::UserComments);
237 }
238
Update(const CatalogItemPtr & item)239 void Update(const CatalogItemPtr& item) override
240 {
241 #ifdef __WXMSW__
242 auto add = _("Add comment");
243 auto edit = _("Edit comment");
244 #else
245 auto add = _("Add Comment");
246 auto edit = _("Edit Comment");
247 #endif
248 m_btn->SetLabel(item->HasComment() ? edit : add);
249 }
250
251 private:
252 wxButton *m_btn;
253 };
254
255
256 wxDEFINE_EVENT(EVT_SUGGESTION_SELECTED, wxCommandEvent);
257
258 class SuggestionWidget : public wxPanel
259 {
260 public:
SuggestionWidget(wxWindow * parent)261 SuggestionWidget(wxWindow *parent) : wxPanel(parent, wxID_ANY)
262 {
263 m_appIsRTL = (wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft);
264
265 m_icon = new wxStaticBitmap(this, wxID_ANY, wxNullBitmap);
266 m_text = new AutoWrappingText(this, "TEXT");
267 m_info = new InfoStaticText(this);
268
269 auto top = new wxBoxSizer(wxHORIZONTAL);
270 auto right = new wxBoxSizer(wxVERTICAL);
271 top->AddSpacer(PX(2));
272 top->Add(m_icon, wxSizerFlags().Top().PXBorder(wxTOP|wxBOTTOM));
273 top->Add(right, wxSizerFlags(1).Expand().PXBorder(wxLEFT));
274 right->Add(m_text, wxSizerFlags().Expand().Border(wxTOP, PX(4)));
275 right->Add(m_info, wxSizerFlags().Expand().Border(wxTOP|wxBOTTOM, PX(2)));
276 SetSizerAndFit(top);
277
278 // setup mouse hover highlighting:
279 m_bg = parent->GetBackgroundColour();
280 m_bgHighlight = m_bg.ChangeLightness(160);
281
282 wxWindow* parts [] = { this, m_icon, m_text, m_info };
283 for (auto w : parts)
284 {
285 w->Bind(wxEVT_MOTION, &SuggestionWidget::OnMouseMove, this);
286 w->Bind(wxEVT_LEAVE_WINDOW, &SuggestionWidget::OnMouseMove, this);
287 w->Bind(wxEVT_LEFT_UP, &SuggestionWidget::OnMouseClick, this);
288 }
289 }
290
SetValue(int index,const Suggestion & s,bool isRTL,const wxBitmap & icon,const wxString & tooltip)291 void SetValue(int index, const Suggestion& s, bool isRTL, const wxBitmap& icon, const wxString& tooltip)
292 {
293 m_value = s;
294
295 auto percent = wxString::Format("%d%%", int(100 * s.score));
296
297 index++;
298 if (index < 10)
299 {
300 #ifdef __WXOSX__
301 auto shortcut = wxString::Format(L"⌘%d", index);
302 #else
303 // TRANSLATORS: This is the key shortcut used in menus on Windows, some languages call them differently
304 auto shortcut = wxString::Format("%s%d", _("Ctrl+"), index);
305 #endif
306 m_info->SetLabel(wxString::Format(L"%s • %s", shortcut, percent));
307 }
308 else
309 {
310 m_info->SetLabel(percent);
311 }
312
313 m_icon->SetBitmap(icon);
314
315 auto text = wxControl::EscapeMnemonics(s.text);
316 if (isRTL)
317 text = L"\u202b" + text;
318 else
319 text = L"\u202a" + text;
320 m_text->SetAndWrapLabel(text);
321
322 // a quirk of wx API: if the current locale is RTL, the meaning of L and R is reversed
323 // for alignments
324 if (m_appIsRTL)
325 isRTL = !isRTL;
326
327 m_text->SetAlignment(isRTL ? wxALIGN_RIGHT : wxALIGN_LEFT);
328 m_text->SetAndWrapLabel(text);
329
330 #ifndef __WXOSX__
331 // FIXME: Causes weird issues on OS X: tooltips appearing on the main list control,
332 // over toolbar, where the mouse just was etc.
333 m_icon->SetToolTip(tooltip);
334 m_text->SetToolTip(tooltip);
335 #endif
336 (void)tooltip;
337
338 SetBackgroundColour(m_bg);
339 InvalidateBestSize();
340 SetMinSize(wxDefaultSize);
341 SetMinSize(GetBestSize());
342 }
343
344 private:
345 class InfoStaticText : public wxStaticText
346 {
347 public:
InfoStaticText(wxWindow * parent)348 InfoStaticText(wxWindow *parent) : wxStaticText(parent, wxID_ANY, "100%")
349 {
350 SetForegroundColour(ExplanationLabel::GetTextColor());
351 #ifdef __WXMSW__
352 SetFont(GetFont().Smaller());
353 #else
354 SetWindowVariant(wxWINDOW_VARIANT_SMALL);
355 #endif
356 }
357
DoEnable(bool)358 void DoEnable(bool) override {} // wxOSX's disabling would break color
359 };
360
OnMouseMove(wxMouseEvent & e)361 void OnMouseMove(wxMouseEvent& e)
362 {
363 auto rectWin = GetClientRect();
364 rectWin.Deflate(1); // work around off-by-one issue on OS X
365 auto evtWin = static_cast<wxWindow*>(e.GetEventObject());
366 auto mpos = e.GetPosition();
367 if (evtWin != this)
368 mpos += evtWin->GetPosition();
369 bool highlighted = rectWin.Contains(mpos);
370 SetBackgroundColour(highlighted ? m_bgHighlight : m_bg);
371 Refresh();
372 }
373
OnMouseClick(wxMouseEvent &)374 void OnMouseClick(wxMouseEvent&)
375 {
376 wxCommandEvent event(EVT_SUGGESTION_SELECTED);
377 event.SetEventObject(this);
378 event.SetString(m_value.text);
379 ProcessWindowEvent(event);
380 }
381
382 Suggestion m_value;
383 wxStaticBitmap *m_icon;
384 AutoWrappingText *m_text;
385 wxStaticText *m_info;
386 wxColour m_bg, m_bgHighlight;
387 bool m_appIsRTL;
388 };
389
390
SuggestionsSidebarBlock(Sidebar * parent,wxMenu * menu)391 SuggestionsSidebarBlock::SuggestionsSidebarBlock(Sidebar *parent, wxMenu *menu)
392 : SidebarBlock(parent, _("Translation suggestions:"), NoUpperMargin),
393 m_suggestionsMenu(menu),
394 m_msgPresent(false),
395 m_pendingQueries(0),
396 m_latestQueryId(0)
397 {
398 m_provider.reset(new SuggestionsProvider);
399
400 m_msgSizer = new wxBoxSizer(wxHORIZONTAL);
401 m_msgIcon = new wxStaticBitmap(parent, wxID_ANY, wxNullBitmap);
402 m_msgText = new ExplanationLabel(parent, "");
403 m_msgSizer->Add(m_msgIcon, wxSizerFlags().Center().PXBorderAll());
404 m_msgSizer->Add(m_msgText, wxSizerFlags(1).Center().PXBorder(wxTOP|wxBOTTOM));
405 m_innerSizer->Add(m_msgSizer, wxSizerFlags().Expand());
406
407 m_innerSizer->AddSpacer(PX(10));
408
409 m_suggestionsSizer = new wxBoxSizer(wxVERTICAL);
410 m_extrasSizer = new wxBoxSizer(wxVERTICAL);
411 m_innerSizer->Add(m_suggestionsSizer, wxSizerFlags().Expand());
412 m_innerSizer->Add(m_extrasSizer, wxSizerFlags().Expand());
413
414 m_iGotNothing = new wxStaticText(parent, wxID_ANY,
415 #ifdef __WXMSW__
416 // TRANSLATORS: This is shown when no translation suggestions can be found in the TM (Windows).
417 _("No matches found")
418 #else
419 // TRANSLATORS: This is shown when no translation suggestions can be found in the TM (OS X, Linux).
420 _("No Matches Found")
421 #endif
422 );
423 m_iGotNothing->SetForegroundColour(ExplanationLabel::GetTextColor().ChangeLightness(150));
424 m_iGotNothing->SetWindowVariant(wxWINDOW_VARIANT_NORMAL);
425 #ifdef __WXMSW__
426 m_iGotNothing->SetFont(m_iGotNothing->GetFont().Larger());
427 #endif
428 m_innerSizer->Add(m_iGotNothing, wxSizerFlags().Center().Border(wxTOP|wxBOTTOM, PX(100)));
429
430 BuildSuggestionsMenu();
431 }
432
~SuggestionsSidebarBlock()433 SuggestionsSidebarBlock::~SuggestionsSidebarBlock()
434 {
435 }
436
GetIconForSuggestion(const Suggestion &) const437 wxBitmap SuggestionsSidebarBlock::GetIconForSuggestion(const Suggestion&) const
438 {
439 return wxArtProvider::GetBitmap("SuggestionTM");
440 }
441
GetTooltipForSuggestion(const Suggestion &) const442 wxString SuggestionsSidebarBlock::GetTooltipForSuggestion(const Suggestion&) const
443 {
444 return _(L"This string was found in Poedit’s translation memory.");
445 }
446
ClearMessage()447 void SuggestionsSidebarBlock::ClearMessage()
448 {
449 m_msgPresent = false;
450 m_msgText->SetAndWrapLabel("");
451 UpdateVisibility();
452 m_parent->Layout();
453 }
454
SetMessage(const wxString & icon,const wxString & text)455 void SuggestionsSidebarBlock::SetMessage(const wxString& icon, const wxString& text)
456 {
457 m_msgPresent = true;
458 m_msgIcon->SetBitmap(wxArtProvider::GetBitmap(icon));
459 m_msgText->SetAndWrapLabel(text);
460 UpdateVisibility();
461 m_parent->Layout();
462 }
463
ReportError(SuggestionsBackend *,std::exception_ptr e)464 void SuggestionsSidebarBlock::ReportError(SuggestionsBackend*, std::exception_ptr e)
465 {
466 SetMessage("SuggestionError", DescribeException(e));
467 }
468
ClearSuggestions()469 void SuggestionsSidebarBlock::ClearSuggestions()
470 {
471 m_pendingQueries = 0;
472 m_suggestions.clear();
473 UpdateSuggestionsMenu();
474 UpdateVisibility();
475 }
476
UpdateSuggestions(const SuggestionsList & hits)477 void SuggestionsSidebarBlock::UpdateSuggestions(const SuggestionsList& hits)
478 {
479 wxWindowUpdateLocker lock(m_parent);
480
481 for (auto& h: hits)
482 {
483 // empty entries screw up menus (treated as stock items), don't use them:
484 if (!h.text.empty())
485 m_suggestions.push_back(h);
486 }
487
488 std::stable_sort(m_suggestions.begin(), m_suggestions.end());
489
490 // create any necessary controls:
491 while (m_suggestions.size() > m_suggestionsWidgets.size())
492 {
493 auto w = new SuggestionWidget(m_parent);
494 m_suggestionsSizer->Add(w, wxSizerFlags().Expand());
495 m_suggestionsWidgets.push_back(w);
496 }
497 m_innerSizer->Layout();
498
499 // update shown suggestions:
500 bool isRTL = m_parent->GetCurrentLanguage().IsRTL();
501 for (size_t i = 0; i < m_suggestions.size(); ++i)
502 {
503 auto s = m_suggestions[i];
504 m_suggestionsWidgets[i]->SetValue((int)i, s, isRTL, GetIconForSuggestion(s), GetTooltipForSuggestion(s));
505 }
506
507 m_innerSizer->Layout();
508 UpdateVisibility();
509 m_parent->Layout();
510
511 UpdateSuggestionsMenu();
512 }
513
BuildSuggestionsMenu(int count)514 void SuggestionsSidebarBlock::BuildSuggestionsMenu(int count)
515 {
516 m_suggestionMenuItems.reserve(SUGGESTIONS_MENU_ENTRIES);
517 auto menu = m_suggestionsMenu;
518 for (int i = 0; i < count; i++)
519 {
520 auto text = wxString::Format("(empty)\t%s%d", _("Ctrl+"), i+1);
521 auto item = new wxMenuItem(menu, wxID_ANY, text);
522 item->SetBitmap(wxArtProvider::GetBitmap("SuggestionTM"));
523
524 m_suggestionMenuItems.push_back(item);
525 menu->Append(item);
526
527 m_suggestionsMenu->Bind(wxEVT_MENU, [this,i,menu](wxCommandEvent&){
528 if (i >= (int)m_suggestions.size())
529 return;
530 wxCommandEvent event(EVT_SUGGESTION_SELECTED);
531 event.SetEventObject(menu);
532 event.SetString(m_suggestions[i].text);
533 menu->GetWindow()->ProcessWindowEvent(event);
534 }, item->GetId());
535 }
536 }
537
UpdateSuggestionsMenu()538 void SuggestionsSidebarBlock::UpdateSuggestionsMenu()
539 {
540 ClearSuggestionsMenu();
541
542 bool isRTL = m_parent->GetCurrentLanguage().IsRTL();
543 wxString formatMask;
544 if (isRTL)
545 formatMask = L"\u202b%s\u202c\t" + _("Ctrl+") + "%d";
546 else
547 formatMask = L"\u202a%s\u202c\t" + _("Ctrl+") + "%d";
548
549 int index = 0;
550 for (auto s: m_suggestions)
551 {
552 if (index >= SUGGESTIONS_MENU_ENTRIES)
553 break;
554
555 auto text = wxString::Format(formatMask, s.text, index+1);
556
557 auto item = m_suggestionMenuItems[index];
558 m_suggestionsMenu->Append(item);
559
560 item->SetItemLabel(text);
561 item->SetBitmap(GetIconForSuggestion(s));
562
563 index++;
564 }
565 }
566
ClearSuggestionsMenu()567 void SuggestionsSidebarBlock::ClearSuggestionsMenu()
568 {
569 auto m = m_suggestionsMenu;
570
571 auto menuItems = m->GetMenuItems();
572 for (auto i: menuItems)
573 {
574 if (std::find(m_suggestionMenuItems.begin(), m_suggestionMenuItems.end(), i) != m_suggestionMenuItems.end())
575 m->Remove(i);
576 }
577 }
578
579
OnQueriesFinished()580 void SuggestionsSidebarBlock::OnQueriesFinished()
581 {
582 if (m_suggestions.empty())
583 {
584 m_innerSizer->Show(m_iGotNothing);
585 m_parent->Layout();
586 }
587 }
588
UpdateVisibility()589 void SuggestionsSidebarBlock::UpdateVisibility()
590 {
591 m_msgSizer->ShowItems(m_msgPresent);
592 m_innerSizer->Show(m_iGotNothing, m_suggestions.empty() && !m_pendingQueries);
593
594 int heightRemaining = m_innerSizer->GetSize().y;
595 size_t w = 0;
596 for (w = 0; w < m_suggestions.size(); w++)
597 {
598 heightRemaining -= m_suggestionsWidgets[w]->GetSize().y;
599 if (heightRemaining < 20)
600 break;
601 m_suggestionsSizer->Show(m_suggestionsWidgets[w]);
602 }
603
604 for (; w < m_suggestionsWidgets.size(); w++)
605 m_suggestionsSizer->Hide(m_suggestionsWidgets[w]);
606
607 }
608
Show(bool show)609 void SuggestionsSidebarBlock::Show(bool show)
610 {
611 SidebarBlock::Show(show);
612 if (show)
613 {
614 UpdateVisibility();
615 }
616 else
617 {
618 ClearSuggestionsMenu();
619 }
620 }
621
ShouldShowForItem(const CatalogItemPtr &) const622 bool SuggestionsSidebarBlock::ShouldShowForItem(const CatalogItemPtr&) const
623 {
624 return m_parent->FileHasCapability(Catalog::Cap::Translations) &&
625 wxConfig::Get()->ReadBool("use_tm", true);
626 }
627
Update(const CatalogItemPtr & item)628 void SuggestionsSidebarBlock::Update(const CatalogItemPtr& item)
629 {
630 // FIXME: Cancel previous pending async operation if any
631
632 ClearMessage();
633 ClearSuggestions();
634
635 auto srclang = m_parent->GetCurrentSourceLanguage();
636 auto lang = m_parent->GetCurrentLanguage();
637 if (!srclang.IsValid() || !lang.IsValid() || srclang == lang)
638 {
639 OnQueriesFinished();
640 return;
641 }
642
643 QueryAllProviders(item);
644 }
645
QueryAllProviders(const CatalogItemPtr & item)646 void SuggestionsSidebarBlock::QueryAllProviders(const CatalogItemPtr& item)
647 {
648 auto thisQueryId = ++m_latestQueryId;
649 QueryProvider(TranslationMemory::Get(), item, thisQueryId);
650 }
651
QueryProvider(SuggestionsBackend & backend,const CatalogItemPtr & item,uint64_t queryId)652 void SuggestionsSidebarBlock::QueryProvider(SuggestionsBackend& backend, const CatalogItemPtr& item, uint64_t queryId)
653 {
654 m_pendingQueries++;
655
656 // we need something to talk to GUI thread through that is guaranteed
657 // to exist, and the app object is a good choice:
658 auto backendPtr = &backend;
659 std::weak_ptr<SuggestionsSidebarBlock> weakSelf = std::dynamic_pointer_cast<SuggestionsSidebarBlock>(shared_from_this());
660
661 m_provider->SuggestTranslation
662 (
663 backend,
664 m_parent->GetCurrentSourceLanguage(),
665 m_parent->GetCurrentLanguage(),
666 item->GetString().ToStdWstring(),
667
668 // when receiving data
669 [=](const SuggestionsList& hits){
670 call_on_main_thread([weakSelf,queryId,hits]{
671 auto self = weakSelf.lock();
672 // maybe this call is already out of date:
673 if (!self || self->m_latestQueryId != queryId)
674 return;
675 self->UpdateSuggestions(hits);
676 if (--self->m_pendingQueries == 0)
677 self->OnQueriesFinished();
678 });
679 },
680
681 // on error:
682 [=](std::exception_ptr e){
683 call_on_main_thread([weakSelf,queryId,backendPtr,e]{
684 auto self = weakSelf.lock();
685 // maybe this call is already out of date:
686 if (!self || self->m_latestQueryId != queryId)
687 return;
688 self->ReportError(backendPtr, e);
689 if (--self->m_pendingQueries == 0)
690 self->OnQueriesFinished();
691 });
692 }
693 );
694 }
695
696
697
Sidebar(wxWindow * parent,wxMenu * suggestionsMenu)698 Sidebar::Sidebar(wxWindow *parent, wxMenu *suggestionsMenu)
699 : wxPanel(parent, wxID_ANY),
700 m_catalog(nullptr),
701 m_selectedItem(nullptr)
702 {
703 SetBackgroundColour(SIDEBAR_BACKGROUND);
704 Bind(wxEVT_PAINT, &Sidebar::OnPaint, this);
705 #ifdef __WXOSX__
706 SetWindowVariant(wxWINDOW_VARIANT_SMALL);
707 #endif
708
709 auto *topSizer = new wxBoxSizer(wxVERTICAL);
710 topSizer->SetMinSize(wxSize(PX(300), -1));
711
712 m_blocksSizer = new wxBoxSizer(wxVERTICAL);
713 topSizer->Add(m_blocksSizer, wxSizerFlags(1).Expand().PXDoubleBorder(wxTOP|wxBOTTOM));
714
715 m_topBlocksSizer = new wxBoxSizer(wxVERTICAL);
716 m_bottomBlocksSizer = new wxBoxSizer(wxVERTICAL);
717
718 m_blocksSizer->Add(m_topBlocksSizer, wxSizerFlags(1).Expand().ReserveSpaceEvenIfHidden());
719 m_blocksSizer->Add(m_bottomBlocksSizer, wxSizerFlags().Expand());
720
721 AddBlock(new SuggestionsSidebarBlock(this, suggestionsMenu), Top);
722 AddBlock(new OldMsgidSidebarBlock(this), Bottom);
723 AddBlock(new ExtractedCommentSidebarBlock(this), Bottom);
724 AddBlock(new CommentSidebarBlock(this), Bottom);
725 AddBlock(new AddCommentSidebarBlock(this), Bottom);
726
727 SetSizerAndFit(topSizer);
728
729 SetSelectedItem(nullptr, nullptr);
730 }
731
732
AddBlock(SidebarBlock * block,BlockPos pos)733 void Sidebar::AddBlock(SidebarBlock *block, BlockPos pos)
734 {
735 m_blocks.emplace_back(block);
736
737 auto sizer = (pos == Top) ? m_topBlocksSizer : m_bottomBlocksSizer;
738 auto grow = (block->IsGrowable()) ? 1 : 0;
739 sizer->Add(block->GetSizer(), wxSizerFlags(grow).Expand());
740 }
741
742
~Sidebar()743 Sidebar::~Sidebar()
744 {
745 }
746
747
SetSelectedItem(const CatalogPtr & catalog,const CatalogItemPtr & item)748 void Sidebar::SetSelectedItem(const CatalogPtr& catalog, const CatalogItemPtr& item)
749 {
750 m_catalog = catalog;
751 m_selectedItem = item;
752 RefreshContent();
753 }
754
SetMultipleSelection()755 void Sidebar::SetMultipleSelection()
756 {
757 SetSelectedItem(nullptr, nullptr);
758 }
759
GetCurrentLanguage() const760 Language Sidebar::GetCurrentLanguage() const
761 {
762 if (!m_catalog)
763 return Language();
764 return m_catalog->GetLanguage();
765 }
766
GetCurrentSourceLanguage() const767 Language Sidebar::GetCurrentSourceLanguage() const
768 {
769 if (!m_catalog)
770 return Language::English();
771 return m_catalog->GetSourceLanguage();
772 }
773
FileHasCapability(Catalog::Cap cap) const774 bool Sidebar::FileHasCapability(Catalog::Cap cap) const
775 {
776 return m_catalog && m_catalog->HasCapability(cap);
777 }
778
RefreshContent()779 void Sidebar::RefreshContent()
780 {
781 if (!IsShown())
782 return;
783
784 auto item = m_selectedItem;
785 if (!IsThisEnabled())
786 item = nullptr;
787
788 wxWindowUpdateLocker lock(this);
789 for (auto& b: m_blocks)
790 b->SetItem(item);
791 Layout();
792 }
793
SetUpperHeight(int size)794 void Sidebar::SetUpperHeight(int size)
795 {
796 wxWindowUpdateLocker lock(this);
797
798 int pos = GetSize().y - size;
799 #ifdef __WXOSX__
800 pos += 4;
801 #else
802 pos += 6;
803 #endif
804 m_bottomBlocksSizer->SetMinSize(wxSize(-1, pos));
805 Layout();
806 }
807
DoEnable(bool)808 void Sidebar::DoEnable(bool)
809 {
810 RefreshContent();
811 }
812
OnPaint(wxPaintEvent &)813 void Sidebar::OnPaint(wxPaintEvent&)
814 {
815 wxPaintDC dc(this);
816
817 dc.SetPen(wxPen(GRAY_LINES_COLOR));
818 #ifndef __WXMSW__
819 dc.DrawLine(0, 0, 0, dc.GetSize().y-1);
820 #endif
821 #ifndef __WXOSX__
822 dc.DrawLine(0, 0, dc.GetSize().x - 1, 0);
823 #endif
824 }
825