NCBI C++ ToolKit
selection_panel.cpp
Go to the documentation of this file.

Go to the SVN repository for this file.

1 /* $Id: selection_panel.cpp 46555 2021-07-07 15:43:24Z shkeda $
2  * ===========================================================================
3  *
4  * PUBLIC DOMAIN NOTICE
5  * National Center for Biotechnology Information
6  *
7  * This software/database is a "United States Government Work" under the
8  * terms of the United States Copyright Act. It was written as part of
9  * the author's official duties as a United States Government employee and
10  * thus cannot be copyrighted. This software/database is freely available
11  * to the public for use. The National Library of Medicine and the U.S.
12  * Government have not placed any restriction on its use or reproduction.
13  *
14  * Although all reasonable efforts have been taken to ensure the accuracy
15  * and reliability of the software and data, the NLM and the U.S.
16  * Government do not and cannot warrant the performance or results that
17  * may be obtained by using this software or data. The NLM and the U.S.
18  * Government disclaim all warranties, express or implied, including
19  * warranties of performance, merchantability or fitness for any particular
20  * purpose.
21  *
22  * Please cite the author in any work or product based on this material.
23  *
24  * ===========================================================================
25  *
26  * Authors: Andrey Yazhuk
27  *
28  * File Description:
29  *
30  */
31 
32 #include <ncbi_pch.hpp>
33 
34 #include "selection_panel.hpp"
35 
37 
39 
41 
43 
46 
48 
49 #include <wx/event.h>
50 #include <wx/sizer.h>
51 #include <wx/panel.h>
52 #include <wx/menu.h>
53 #include <wx/toolbar.h>
54 #include <wx/statline.h>
55 
57 
58 ///////////////////////////////////////////////////////////////////////////////
59 /// CSelectionTable
60 
61 class CSelectionViewEventHandler : public wxEvtHandler
62 {
63 public:
65  : m_Panel( panel )
66  {}
67 
68  void OnContextMenu( wxContextMenuEvent& anEvent );
69 
70 private:
72 
73  DECLARE_EVENT_TABLE()
74 };
75 
76 BEGIN_EVENT_TABLE( CSelectionViewEventHandler, wxEvtHandler )
79 
80 void CSelectionViewEventHandler::OnContextMenu( wxContextMenuEvent& anEvent )
81 {
82  unique_ptr<wxMenu> menu( CreateContextMenuBackbone() );
83 
84  void* data = anEvent.GetClientData();
85  if( data ){
86  Merge( *menu.get(), *(wxMenu*)data );
87 
88  anEvent.SetClientData( NULL );
89  delete (wxMenu*)data;
90  }
91 
92  CleanupSeparators( *menu );
93 
94 // wxGenericListCtrl with GTK for some reason can't have focus from the point of view of wxWidgets.
95 // This doesn't allow wxGenericListCtrl to handle commands via CCommandToFocusHandler.
96 // Here is a fix for the Selection View. JIRA: GB-2792
97  m_Panel->GetCurrentCtrl()->PopupMenu( menu.get() );
98 }
99 
100 
101 #define ACTIVE_VIEW_TARGET_IX 0
102 #define ALL_VIEWS_TARGET_IX 1
103 
104 #define ID_TABLE 10001
105 #define ID_TEXT 10002
106 #define ID_TOOLBAR 10003
107 #define ID_COMBOBOX 10004
108 
109 BEGIN_EVENT_TABLE( CSelectionPanel, wxPanel )
110  EVT_CONTEXT_MENU( CSelectionPanel::OnContextMenu )
112  EVT_TOOL_RANGE( scm_ModeCmd+eTable, scm_ModeCmd+eText, CSelectionPanel::OnModeChanged )
114 
115 
117  : m_SelView(view)
118  , m_Service(NULL)
119  , m_TargetChoice(NULL)
120  , m_DisplayMode(eInvalidDisplayMode)
121  , m_ListWidget(NULL)
122  , m_TextWidget(NULL)
123 {
124  Init();
125 }
126 
127 void CSelectionPanel::Create( wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size )
128 {
129 #ifdef __WXOSX_COCOA__ // GB-8581
130  SetBackgroundStyle(wxBG_STYLE_COLOUR);
131  SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_FRAMEBK));
132 #endif
133 
134  wxPanel::Create( parent, id, pos, size );
135  CreateControls();
136 
137  PushEventHandler( new CCommandToFocusHandler( this ) );
138  PushEventHandler( new CSelectionViewEventHandler( this ) );
139 }
140 
142 {
143  PopEventHandler( true );
144  PopEventHandler( true );
145 
146  return wxPanel::Destroy();
147 }
148 
150 {
151  m_Service = NULL;
153  m_ListWidget = NULL;
154  m_TextWidget = NULL;
155 
156  static bool s_RegisteredIcons = false;
157 
158  // register icons only once
159  if( ! s_RegisteredIcons ){
161 
162  provider->RegisterFileAlias( wxT("menu::insp_table_mode"), wxT("insp_table_mode.png") );
163  provider->RegisterFileAlias( wxT("menu::insp_brief_text_mode"), wxT("insp_brief_text_mode.png") );
164  provider->RegisterFileAlias( wxT("menu::insp_text_mode"), wxT("insp_text_mode.png") );
165 
166  s_RegisteredIcons = true;
167  }
168 }
169 
171 {
172  wxSizer* sizer = new wxBoxSizer( wxVERTICAL );
173  SetSizer( sizer );
174 
175  // Create tool bar
176  wxToolBar* toolBar = new wxToolBar(
177  this, ID_TOOLBAR, wxDefaultPosition, wxDefaultSize,
178  wxTB_FLAT | wxTB_HORIZONTAL | wxTB_TEXT | wxTB_HORZ_LAYOUT
179  );
180 
181  toolBar->AddRadioTool(scm_ModeCmd + eTable, wxT("Table"),
182  wxArtProvider::GetBitmap(wxT("menu::insp_table_mode")),
183  wxNullBitmap, wxT("Table mode"));
184  toolBar->AddRadioTool(scm_ModeCmd + eBriefText, wxT("Brief"),
185  wxArtProvider::GetBitmap(wxT("menu::insp_brief_text_mode")),
186  wxNullBitmap, wxT("Brief Text mode"));
187  toolBar->AddRadioTool(scm_ModeCmd + eText, wxT("Full"),
188  wxArtProvider::GetBitmap( wxT("menu::insp_text_mode") ),
189  wxNullBitmap, wxT("Full Text mode"));
190  toolBar->AddSeparator();
191 
192  // Had used a combo box here but that has a strange error on macos carbon - it
193  // becomes disabled when certain other windows (like project view) are selected
194  // and can't be easily re-enabled (no standard wx functions, enable, setfocus, raise, etc
195  // had any effect, but selecting certain Other views, e.g. tree view, did
196  // re-enable combo-boxes on this panel...).
197  m_TargetChoice = new wxChoice(toolBar, ID_COMBOBOX, wxDefaultPosition);
198  toolBar->AddControl( m_TargetChoice );
199 
200  toolBar->Realize();
201  sizer->Add( toolBar, 0, wxEXPAND );
202 
203  // separation line before the table
204  wxStaticLine* line = new wxStaticLine(
205  this, wxID_STATIC, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL
206  );
207  sizer->Add( line, 0, wxEXPAND );
208 
209  // Create list widget
210  m_ListWidget = new CObjectListWidget( this, ID_TABLE, wxDefaultPosition, wxSize(100, 100), wxBORDER_NONE );
212  sizer->Add( m_ListWidget, 1, wxEXPAND );
213 
214  // create text widget
215  m_TextWidget = new CTextItemPanel( this, ID_TEXT );
216  m_TextWidget->Hide();
217  m_TextWidget->SetBackgroundColour( wxColor(wxT("white")) );
218  m_TextWidget->SetFont(wxFont(wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_MODERN, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL));
219 
220  sizer->Add(m_TextWidget, 1, wxEXPAND);
221 
223 
224  Update();
225 }
226 
228 {
229  if (eTable == m_DisplayMode)
230  return m_ListWidget;
231  else
232  return m_TextWidget;
233 }
234 
236 {
237  m_Service = service;
238  if( !m_Service ){
240  }
241 
242  Update();
243 }
244 
245 void CSelectionPanel::SetRegistryPath( const string& path )
246 {
247  m_RegPath = path; // store for later use
248 }
249 
251 {
252  if( ! m_RegPath.empty() ){
254  CRegistryWriteView view = gui_reg.GetWriteView(m_RegPath);
255 
257 
259  }
260 }
261 
263 {
264  if( ! m_RegPath.empty() ){
266  CRegistryReadView view = gui_reg.GetReadView(m_RegPath);
267 
269 
271  }
272 }
273 
275 {
276  if( m_DisplayMode == eTable ){
278  } else {
280  if (context)
281  context->GetSelectedObjects( buf );
282  }
283 }
284 
285 void CSelectionPanel::OnContextMenu( wxContextMenuEvent& anEvent )
286 {
287  wxMenu* menu;
288 
289  void* data = anEvent.GetClientData();
290  menu = data ? (wxMenu*)data : new wxMenu();
291 
292  if( /*ShouldPropagate()*/ true ){
293  anEvent.SetClientData( menu );
294  anEvent.Skip();
295  return;
296  }
297 
298  anEvent.SetClientData( NULL );
299 
300  CleanupSeparators( *menu );
301  PopupMenu( menu );
302 
303  delete menu;
304 }
305 
306 void CSelectionPanel::OnTargetChanged( wxCommandEvent& /*event*/ )
307 {
309 }
310 
311 void CSelectionPanel::OnModeChanged( wxCommandEvent& event )
312 {
313  EDisplayMode new_mode = EDisplayMode(event.GetId() - scm_ModeCmd);
314 
315  if( new_mode != m_DisplayMode ){
316  m_DisplayMode = new_mode;
317 
318  if( new_mode == eTable ){
319  m_TextWidget->Hide();
320  m_ListWidget->Show();
321  } else {
322  m_ListWidget->Hide();
323  m_TextWidget->Show();
325  }
326 
328  }
329 }
330 
332 {
333  if( m_DisplayMode == eTable ){
335 
336  } else {
337  if( !( m_TextWidget->GetContext() && m_TextWidget->Selecting() ) ){
339  }
340  }
341 }
342 
344 {
345  switch( update.GetID() ){
348  break;
349 
351  {{
353  CIRef<ISelectionClient> updateClient = update.GetClient();
354 
355  if (wic.GetPointerOrNull() == updateClient) {
357  Update();
358  } else {
360  }
361  }}
362  break;
363 
365  {{
367  CIRef<ISelectionClient> updateClient = update.GetClient();
368  if (updateClient != wic.GetPointerOrNull() && updateClient != m_SelView) {
369  if (updateClient == NULL || (m_Service && m_Service->IsAttached(updateClient))) {
370  m_CurrentClientWeakPtr = updateClient;
371  }
372 
373  Update();
374  }
375  }}
376  break;
377 
380  break;
381 
382  default:
383  _ASSERT(false);
384  }
385 }
386 
388 {
390 
392 }
393 
395 {
396  ISelectionClient* cur_target = NULL;
397  int target_ix = m_TargetChoice->GetSelection();
398 
399  if( target_ix == wxNOT_FOUND ){
400  target_ix = ACTIVE_VIEW_TARGET_IX;
401 
402  } else if ( target_ix > ALL_VIEWS_TARGET_IX ) {
403  cur_target = (ISelectionClient*)m_TargetChoice->GetClientData(target_ix);
404  }
405 
406  m_TargetChoice->Clear();
407 
408  string s_active = "Active View";
409  {{
411  ISelectionClient* current_client = wic.GetPointerOrNull();
412 
413  // add "Active Client" line
414  if( current_client ){
415  s_active += " - " + current_client->GetSelClientLabel();
416  }
417  }}
418 
419  m_TargetChoice->Append( ToWxString(s_active), (void*)nullptr );
420 
421  /// add "All Views" line
422  m_TargetChoice->Append( wxT("All Views"), (void*)nullptr);
423 
425 
426  if( m_Service ){
427  m_Service->GetClients( clients );
428  }
429 
430  if( clients.empty() ){
431  m_TargetChoice->SetSelection( ACTIVE_VIEW_TARGET_IX );
432  return;
433  }
434 
435  if (target_ix > ALL_VIEWS_TARGET_IX)
436  target_ix = ACTIVE_VIEW_TARGET_IX;
437  /// Add lines for all existing clients
438  ITERATE( CSelectionService::TClients, it, clients ){
439  ISelectionClient* client = *it;
440  if( client == m_SelView ){ // skip myself
441  continue;
442  }
443 
444  string s = client->GetSelClientLabel();
445  int index = m_TargetChoice->Append( ToWxString(s), (void*)client );
446 
447  if( client == cur_target ){
448  target_ix = index;
449  }
450  }
451  m_TargetChoice->SetSelection( target_ix );
452 
453  m_TargetChoice->SetMinSize(wxDefaultSize);
454  wxSize size = m_TargetChoice->GetBestSize();
455  m_TargetChoice->SetMinSize(size);
456  m_TargetChoice->SetSize(size);
457 
458  wxToolBar* toolbar = (wxToolBar*)m_TargetChoice->GetParent();
459  toolbar->Realize();
460 }
461 
463 {
464  int target_ix = m_TargetChoice->GetSelection();
465 
466  if( !m_Service || target_ix == wxNOT_FOUND ){
467  return false;
468  }
469 
470  TConstScopedObjects tmp_sel_objs;
471  if( target_ix == ALL_VIEWS_TARGET_IX ){ // All Views
473  m_Service->GetClients( clients );
474 
475  ITERATE( CSelectionService::TClients, it, clients ){
476  ISelectionClient* client = *it;
477  if( client != m_SelView ){
478  client->GetActiveObjects( tmp_sel_objs );
479  }
480  }
481  } else {
482  ISelectionClient* client = nullptr;
483  if( target_ix == ACTIVE_VIEW_TARGET_IX ){ // Active View
485  ISelectionClient* current_client = wic.GetPointerOrNull();
486  client = current_client;
487  } else {
488  client = (ISelectionClient*)m_TargetChoice->GetClientData(target_ix);
489  // the functions can be called after the client was removed
490  // and before m_TargetChoice is updated
491  // so we need to check if m_TargetChoice's client still exists
492  CSelectionService::TClients active_clients;
493  m_Service->GetClients(active_clients);
494  if (find(active_clients.begin(), active_clients.end(), client) == active_clients.end()) {
495  client = nullptr;
496  }
497  }
498 
499  if( client ){
500  client->GetActiveObjects( tmp_sel_objs );
501  }
502  }
503 
504  // Remove empty locs (Alignment span view braodcasts a pair of locs and one of teh pair might be empty)
505  tmp_sel_objs.erase(remove_if(tmp_sel_objs.begin(), tmp_sel_objs.end(), [](const SConstScopedObject& obj) {
506  auto loc = dynamic_cast<const objects::CSeq_loc*>(&*obj.object);
507  return loc && loc->GetTotalRange().GetLength() <= 0;
508  }), tmp_sel_objs.end());
509 
510  bool matched = false;
511  if (m_SelObjects.size() == tmp_sel_objs.size()) {
512  matched = true;
513  set<const CObject*> objs;
515  objs.insert(iter->object.GetPointerOrNull());
516  }
517 
518  ITERATE (TConstScopedObjects, iter, tmp_sel_objs) {
519  if (objs.count(iter->object.GetPointerOrNull()) == 0) {
520  matched = false;
521  break;
522  }
523  }
524  }
525 
526  if (matched) {
527  // no selection changed
528  return false;
529  }
530 
531  m_SelObjects.swap(tmp_sel_objs);
532  return true;
533 }
534 
536 {
537  if (x_ResetSelObjects()) {
539  }
540 
541  Layout();
542  Refresh();
543 }
544 
545 namespace {
546  class CCancelGuard
547  {
548  public:
549  CCancelGuard(ICanceled* canceled, CTextPanelContext& context) : m_Context(context)
550  {
551  m_SaveCanceled = m_Context.SetCanceled(canceled);
552  }
553  ~CCancelGuard()
554  {
555  m_Context.SetCanceled(m_SaveCanceled);
556  }
557  private:
560  };
561 }
562 
564 {
566 
567  typedef tuple<unique_ptr<CCompositeTextItem>, unique_ptr<CTextPanelContext> > TResult;
568 
569  m_Future.reset(job_async(
571  fontDesc = m_TextWidget->GetFont().GetNativeFontInfoDesc(),
573  {
574  TResult result;
575 
576  auto rootItem = std::make_unique<CCompositeTextItem>();
577  auto context = std::make_unique<CTextPanelContext>(1, nullptr, nullptr);
578 
579  context->SetFontDesc(fontDesc);
580 
581  string logMsg("CTextJob: ");
582 
583  try {
584  NON_CONST_ITERATE(TConstScopedObjects, it, objects) {
585  if (canceled.IsCanceled())
586  return result;
587 
588  if (rootItem->GetItemCount() > 0) {
589  CPlainTextItem* itemDiv = new CPlainTextItem();
590  itemDiv->AddLine(" ");
591  rootItem->AddItem(itemDiv, false);
592  }
593  rootItem->AddItem(CObjectTextItem::CreateTextItem(*it, mode, &canceled), false);
594  }
595  }
596  catch (CException& e) {
597  NCBI_REPORT_EXCEPTION(logMsg, e);
598  throw;
599  }
600  catch (std::exception& e) {
601  CNcbiDiag() << Error << "[" << logMsg << "] Exception: " << e.what();
602  throw;
603  }
604 
605  if (canceled.IsCanceled())
606  return result;
607 
608  CCancelGuard guard(&canceled, *context);
609  wxMemoryDC dc;
610  wxFont font(context->GetFontDesc());
611  dc.SetFont(font);
612  context->CalcWSize(dc);
613  rootItem->CalcSize(dc, context.get());
614 
615  if (canceled.IsCanceled())
616  return result;
617 
618  std::get<0>(result) = std::move(rootItem);
619  std::get<1>(result) = std::move(context);
620 
621  return result;
622  },
623  [textWidget = m_TextWidget](job_future<TResult>& fn)
624  {
625  string errMsg = "Unknown fatal error";
626  try {
627  auto& result = fn();
628 
629  unique_ptr<CCompositeTextItem> rootItem(std::move(std::get<0>(result)));
630  unique_ptr<CTextPanelContext> context(std::move(std::get<1>(result)));
631 
632  if (!rootItem || !context) {
633  textWidget->ReportError("No data");
634  }
635  else {
636  textWidget->SetMainItem(rootItem.release(), context.release());
637  textWidget->Layout();
638  textWidget->Refresh();
639  }
640  return;
641  }
642  catch (const CException& e) {
643  errMsg = e.GetMsg();
644  }
645  catch (const std::exception& e) {
646  errMsg = e.what();
647  }
648 
649  textWidget->ReportError("Failed: " + errMsg);
650  }
651  , "Generate Selection Text"));
652 }
653 
void remove_if(Container &c, Predicate *__pred)
Definition: chainer.hpp:69
ICanceled * m_SaveCanceled
CCancelGuard(ICanceled *canceled, CTextPanelContext &context)
CTextPanelContext & m_Context
CRegistryWriteView GetWriteView(const string &section)
get a read-write view at a particular level.
Definition: registry.cpp:462
static CGuiRegistry & GetInstance()
access the application-wide singleton
Definition: registry.cpp:400
CRegistryReadView GetReadView(const string &section) const
get a read-only view at a particular level.
Definition: registry.cpp:428
CNcbiDiag –.
Definition: ncbidiag.hpp:924
CObjectListWidget - mediator widget.
void SetObjects(TConstScopedObjects &objects)
void GetSelection(TConstScopedObjects &objects)
ISelection-style API.
class CRegistryReadView provides a nested hierarchical view at a particular key.
Definition: reg_view.hpp:58
CSelectionPanel.
virtual void LoadSettings()
static const int scm_ModeCmd
virtual void Create(wxWindow *parent, wxWindowID id=wxID_ANY, const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxDefaultSize)
virtual void SetRegistryPath(const string &reg_path)
void SetSelectionService(CSelectionService *service)
void Init()
Initialises member variables.
ISelectionClient * m_SelView
void OnModeChanged(wxCommandEvent &event)
CTextItemPanel * m_TextWidget
EDisplayMode m_DisplayMode
virtual bool Destroy()
wxWindow * GetCurrentCtrl()
virtual void SaveSettings() const
std::unique_ptr< async_job > m_Future
void OnContextMenu(wxContextMenuEvent &event)
wxChoice * m_TargetChoice
void OnSelServiceStateChange(CSelectionServiceEvent &update)
CSelectionService * m_Service
void OnTargetChanged(wxCommandEvent &event)
void GetSelection(TConstScopedObjects &buf) const
CObjectListWidget * m_ListWidget
CWeakIRef< ISelectionClient > m_CurrentClientWeakPtr
void CreateControls()
Creates the controls and sizers.
TConstScopedObjects m_SelObjects
CIRef< ISelectionClient > GetClient()
void GetClients(TClients &clients)
vector< ISelectionClient * > TClients
virtual bool IsAttached(ISelectionClient *client) const
CSelectionViewEventHandler(CSelectionPanel *panel)
void OnContextMenu(wxContextMenuEvent &anEvent)
bool Selecting() const
CTextPanelContext * GetContext()
virtual bool SetFont(const wxFont &font)
ICanceled * SetCanceled(ICanceled *canceled)
void SetContextMenuEventPropagation(bool flag)
void LoadTableSettings(const CRegistryReadView &view, bool byName=false)
void SaveTableSettings(CRegistryWriteView &view, bool saveSorting=true) const
Interface for testing cancellation request in a long lasting operation.
Definition: icanceled.hpp:51
ISelectionClient - represents an object that support a notion of selection.
virtual string GetSelClientLabel()=0
returns a string identifying the client in UI
Definition: set.hpp:45
iterator_bool insert(const value_type &val)
Definition: set.hpp:149
virtual void RegisterFileAlias(const wxArtID &anId, const wxArtClient &aClient, const wxSize &aSize, const wxString &aName, long aType=wxBITMAP_TYPE_ANY, int anIndex=-1)
static void Init(void)
Definition: cursor6.c:76
char data[12]
Definition: iconv.c:80
#define ITERATE(Type, Var, Cont)
ITERATE macro to sequence through container elements.
Definition: ncbimisc.hpp:815
#define NULL
Definition: ncbistd.hpp:225
void Error(CExceptionArgs_Base &args)
Definition: ncbiexpt.hpp:1197
const string & GetMsg(void) const
Get message string.
Definition: ncbiexpt.cpp:461
#define NCBI_REPORT_EXCEPTION(title, ex)
Generate a report on the exception.
Definition: ncbiexpt.hpp:755
virtual const char * what(void) const noexcept
Standard report (includes full backlog).
Definition: ncbiexpt.cpp:342
const TEventID GetID(void) const
Inline Implementation.
Definition: event.hpp:164
vector< SConstScopedObject > TConstScopedObjects
Definition: objects.hpp:65
TRefType Lock(void) const
Lock the object and return reference to it.
Definition: ncbiobj.hpp:2864
TObjectType * GetPointerOrNull(void) THROWS_NONE
Get pointer value.
Definition: ncbiobj.hpp:986
#define END_NCBI_SCOPE
End previously defined NCBI scope.
Definition: ncbistl.hpp:103
#define BEGIN_NCBI_SCOPE
Define ncbi namespace.
Definition: ncbistl.hpp:100
virtual bool IsCanceled(void) const =0
job_function_traits< _Fty >::future job_async(const _Fty &_Fnarg, const string &descr)
Definition: job_future.hpp:428
END_EVENT_TABLE()
char * buf
@ eText
Definition: map_control.hpp:50
#define wxT(x)
Definition: muParser.cpp:41
mdb_mode_t mode
Definition: lmdb++.h:38
const struct ncbi::grid::netcache::search::fields::SIZE size
wxMenu * CreateContextMenuBackbone()
#define ALL_VIEWS_TARGET_IX
#define ID_TEXT
#define ACTIVE_VIEW_TARGET_IX
#define ID_TABLE
#define ID_TOOLBAR
#define ID_COMBOBOX
static CNamedPipeClient * client
#define _ASSERT
else result
Definition: token2.c:20
static CS_CONTEXT * context
Definition: will_convert.c:21
wxFileArtProvider * GetDefaultFileArtProvider()
Definition: wx_utils.cpp:334
void Merge(wxMenu &menu_1, const wxMenu &menu_2)
merges all items form menu_2 into menu_1, preserving the structure if possible
Definition: wx_utils.cpp:579
wxString ToWxString(const string &s)
Definition: wx_utils.hpp:173
void CleanupSeparators(wxMenu &menu)
Removes extra separators (in the begining or at the end of the menu, ot those that precede other sepa...
Definition: wx_utils.cpp:668
Modified on Fri Sep 20 14:57:39 2024 by modify_doxy.py rev. 669887