Page 1 of 1

Alternative set of picker controls

Posted: Sat Feb 09, 2008 10:13 am
by Troels
GTK provides a native set of picker widgets and it is the job of wxWidgets to provide wrappers (on GTK, and to provide similar widgets on Windows).
The GTK picker widgets are ok only if you like big buttons with clipped variable length text inside.

Here I'm providing an alternative set of simple pickers, named wx{}PickerButton instead of wx{}PickerCtrl, very simple implementations and yet more functional in many respects than the wx ones.

Image

Below is a list of problems with the wxWidgets pickers. Most of these problems are adressed with the simple alternative set of pickers provided here.

wxColourPickerCtrl:
This is almost ok, only annoying thing is that it is largish on GTK and do not take into account that colours normally goes in pairs: foreground + background. In my preferences dialogs I don't like to have two bulky GTK colour picker buttons together, displaying the foreground and background, plus yet another control with a (text) preview next to it. See screenshot above for a space preserving solution (utilizing wxColourPickerButton and wxBitmapComboBox)

wxFontPickerCtrl:
This will display the face name inside it (good if you like big buttons with variable length text inside) but it shows no preview text using the actual font on GTK or Windows, nor is it directly supporting such a preview text (next to it). Moreover, the interface is using wxFont only, not wxFontData: on Windows you'll miss the colour selection in wxFontData.

wxDirPickerCtrl:
This will display the file path inside it on GTK (good if you like big buttons with variable length text inside), you cannot turn this off on GTK, and the default button width makes the text clipped (unless you have very short paths such as "/"). Optionally you can make it control an adjacent text control - but not an adjacent combobox, which is often what you want.

wxFilePickerCtrl:
Same problems as with wxDirPickerCtrl above + the wildcard (filter) can only be set in the constructor which is a nuisance especially if created from XRC.

Notes:

- Naturally the interfaces of the wx pickers are reused, the same events fired.

- If you're using XRC simply create buttons - base class wxButton - and make the (derived) class wxColourPickerButton/wxFontPickerButton/wxDirPickerButton/wxFilePickerButton.
Adjusting the style of a file picker:

Code: Select all

wxFilePickerButton* file_picker = XRCCTRL(*this, "browse0", wxFilePickerButton);
wxModifyStyle(file_picker, 0, wxFLP_OPEN | wxFLP_FILE_MUST_EXIST);
wxModifyStyle(file_picker, 0, wxFLP_SAVE);
wxModifyStyle: See below.

- Call picker.AlignAndFit(adjacent) if you want the picker to
1) set the picker button width, fitting the button text ("..." or "Browse")
2) align picker button height with the adjacent/buddy control.

- The alternative pickers looks the same across platforms, unlike the wx ones, which may [not] be what you want.

EDIT: In wxFilePickerButton::CreateDialog(), use appropriate caption text depending on the wxFLP_SAVE flag

Code: Select all

inline void wxModifyStyle(wxWindow* wnd, long remove, long add)
{
   long dw = wnd->GetWindowStyleFlag();
   dw&=~remove;
   dw|=add;
   wnd->SetWindowStyleFlag(dw);
}

/////////////////////////////////////////////////////////////////////////////
// wxColourPickerButton

class wxColourData;
class wxColourPickerButton : public wxButton
{
   DECLARE_DYNAMIC_CLASS(wxColourPickerButton)
protected:
   wxColourData* m_data;
   wxString m_message;
public:   
   wxColourPickerButton();

   // get the colour chosen
   wxColour GetColour() const;

   // set currently displayed color
   void SetColour(const wxColour& col, bool background = true, bool tooltip = false);

   void SetMessage(const wxString& str) { m_message = str; }
   void AlignAndFit(wxWindow* alignwindow = NULL);
   wxColourData *GetColourData() { return m_data; }

   wxControl *GetPickerCtrl()
     { return this; }

   virtual ~wxColourPickerButton();
protected:
   void OnButtonClick(wxCommandEvent&);
};

/////////////////////////////////////////////////////////////////////////////
// wxFontPickerButton

class wxFontData;
class wxFontPickerButton : public wxButton
{
   DECLARE_DYNAMIC_CLASS(wxFontPickerButton)
protected:
   wxFontData* m_data;
   wxString m_message;
public:   
   wxFontPickerButton();

   // get the font chosen
   wxFont GetSelectedFont() const;

   // sets currently displayed font
   void SetSelectedFont(const wxFont&);

   void SetMessage(const wxString& str) { m_message = str; }
   void AlignAndFit(wxWindow* alignwindow = NULL);
   
   const wxFontData& GetFontData() const { return *m_data; }
   void              SetFontData(const wxFontData&);

   wxControl *GetPickerCtrl()
     { return this; }

   virtual ~wxFontPickerButton();
protected:
   void OnButtonClick(wxCommandEvent&);
};

/////////////////////////////////////////////////////////////////////////////
// wxFilePickerButton

class wxFileDialog;
class wxFilePickerButton : public wxButton
{
   DECLARE_DYNAMIC_CLASS(wxFilePickerButton)
protected:
   wxString m_message;
   wxString m_path;
   wxString m_wildcard; // file filter
public:
#if (wxVERSION_NUMBER >= 2900)
   wxTextEntry* m_text;
#else
   wxComboBox* m_combo;
   wxTextCtrl* m_edit;
#endif
public:   
   wxFilePickerButton();

   void SetMessage(const wxString& str) { m_message = str; }
   void AlignAndFit(wxWindow* alignwindow = NULL);
   
   wxString GetPath() const { return m_path; }
   void SetPath(const wxString &str) { m_path = str; }

   // "wildcard" aka. "file name filter"
   wxString GetWildcard() const { return m_wildcard; }
   void SetWildcard(const wxString &str) { m_wildcard = str; }

   wxControl *GetPickerCtrl()
     { return this; }

   wxFileDialog* CreateDialog();
   void UpdatePickerFromTextCtrl();
   void UpdateTextCtrlFromPicker();

   long GetDialogStyle() const;

   bool DisconnectButtonClick()
   {
      // call if you want to handle EVT_BUTTON in your dialog
      return Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(wxFilePickerButton::OnButtonClick));
   }
   
   virtual ~wxFilePickerButton();
protected:
   void OnButtonClick(wxCommandEvent&);
};

/////////////////////////////////////////////////////////////////////////////
// wxDirPickerButton

class wxDirDialog;
class wxDirPickerButton : public wxButton
{
   DECLARE_DYNAMIC_CLASS(wxDirPickerButton)
protected:
   wxString m_message;
   wxString m_path;
public:
#if (wxVERSION_NUMBER >= 2900)
   wxTextEntry* m_text;
#else
   wxComboBox* m_combo;
   wxTextCtrl* m_edit;
#endif

public:   
   wxDirPickerButton();
  
   void SetMessage(const wxString& str) { m_message = str; }
   void AlignAndFit(wxWindow* alignwindow = NULL);
   
   wxString GetPath() const { return m_path; }
   void SetPath(const wxString &str) { m_path = str; }

   wxControl *GetPickerCtrl()
     { return this; }
   wxDirDialog* CreateDialog();
   void UpdatePickerFromTextCtrl();
   void UpdateTextCtrlFromPicker();

   long GetDialogStyle() const;

   bool DisconnectButtonClick()
   {
      // call if you want to handle EVT_BUTTON in your dialog
      return Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(wxDirPickerButton::OnButtonClick));
   }

   virtual ~wxDirPickerButton();
protected:
   void OnButtonClick(wxCommandEvent&);
};

Code: Select all

#include <wx/wx.h>
#include <wx/settings.h>

#include <wx/filepicker.h> // wxDIRP_
#include <wx/clrpicker.h> // wxColourPickerEvent
#include <wx/fontpicker.h> // wxFontPickerEvent

/////////////////////////////////////////////////////////////////////////////
// wxPickerButton_SetInitialSize

static void wxPickerButton_SetInitialSize(wxWindow* wnd, wxWindow* edit)
{
   // Can't get wnd->SetClientSize() to do it

   wxSize size;
   wnd->GetTextExtent(wnd->GetLabel(), &size.x, &size.y);

#ifdef __WXMSW__
   size.x+=4+2*wxSystemSettings::GetMetric(wxSYS_EDGE_X, wnd) + 2*wxSystemSettings::GetMetric(wxSYS_BORDER_X, wnd);
#elif __WXGTK__
   size.x+=14;
#endif
   size.y = edit ? (edit->GetSize().y-0) : wnd->GetSize().y;

#ifdef __WXMSW__
#elif __WXGTK__
   size.y-=2;
#endif
   wnd->SetInitialSize(size);
}

/////////////////////////////////////////////////////////////////////////////
// wxColourPickerButton

IMPLEMENT_DYNAMIC_CLASS(wxColourPickerButton, wxButton)

wxColourPickerButton::wxColourPickerButton() : wxButton(), m_data(new wxColourData)
{
   m_data->SetChooseFull(true);
   Connect(wxEVT_COMMAND_BUTTON_CLICKED,
         wxCommandEventHandler(wxColourPickerButton::OnButtonClick),
         NULL, this);
}

wxColourPickerButton::~wxColourPickerButton()
{
   wxDELETE(m_data)
}

wxColour wxColourPickerButton::GetColour() const
{
   return m_data->GetColour();
}

void wxColourPickerButton::SetColour(const wxColour& col, bool background, bool tooltip)
{
   m_data->SetColour(col);
   if (background) SetBackgroundColour(col);
   if (tooltip ) SetToolTip(col.GetAsString());
}

void wxColourPickerButton::AlignAndFit(wxWindow* buddy)
{
   if (GetLabel().IsEmpty()) SetLabel(wxT("..."));
   ::wxPickerButton_SetInitialSize(this, buddy);
}

void wxColourPickerButton::OnButtonClick(wxCommandEvent&)
{
    // create the colour dialog and display it
    wxColourDialog dlg(GetParent(), m_data);
    if (m_message.Length()) dlg.SetTitle(m_message);
    if (dlg.ShowModal() == wxID_OK)
    {
        m_data->operator=(dlg.GetColourData());

        // fire an event
        wxColourPickerEvent event(this, GetId(), m_data->GetColour());
        GetEventHandler()->ProcessEvent(event);
    }
}

/////////////////////////////////////////////////////////////////////////////
// wxFontPickerButton

IMPLEMENT_DYNAMIC_CLASS(wxFontPickerButton, wxButton)

wxFontPickerButton::wxFontPickerButton() : wxButton(), m_data(new wxFontData)
{
   Connect(wxEVT_COMMAND_BUTTON_CLICKED,
         wxCommandEventHandler(wxFontPickerButton::OnButtonClick),
         NULL, this);
}

wxFontPickerButton::~wxFontPickerButton()
{
   wxDELETE(m_data)
}

void wxFontPickerButton::AlignAndFit(wxWindow* buddy)
{
   if (GetLabel().IsEmpty()) SetLabel(wxT("..."));
   ::wxPickerButton_SetInitialSize(this, buddy);
}

// get the font chosen
wxFont wxFontPickerButton::GetSelectedFont() const
{
   return m_data->GetChosenFont();
}

// sets currently displayed font
void wxFontPickerButton::SetSelectedFont(const wxFont& font)
{
   m_data->SetChosenFont(font);
}

void wxFontPickerButton::SetFontData(const wxFontData& data)
{
   m_data->operator=(data);
}

void wxFontPickerButton::OnButtonClick(wxCommandEvent&)
{
    // create the font dialog and display it
    wxFontDialog dlg(GetParent(), *m_data);
    if (m_message.Length()) dlg.SetTitle(m_message);
    if (dlg.ShowModal() == wxID_OK)
    {
        m_data->operator=(dlg.GetFontData());

        // fire an event
        wxFontPickerEvent event(this, GetId(), m_data->GetChosenFont());
        GetEventHandler()->ProcessEvent(event);
    }
}

/////////////////////////////////////////////////////////////////////////////
// wxFilePickerButton

IMPLEMENT_DYNAMIC_CLASS(wxFilePickerButton, wxButton)

wxFilePickerButton::wxFilePickerButton() : wxButton(),
   m_wildcard(wxFileSelectorDefaultWildcardStr)
{
#if (wxVERSION_NUMBER >= 2900)
   m_text = NULL;
#else
   m_combo = NULL;
   m_edit  = NULL;
#endif
   //m_message = wxFileSelectorPromptStr;
   Connect(wxEVT_COMMAND_BUTTON_CLICKED,
         wxCommandEventHandler(wxFilePickerButton::OnButtonClick),
         NULL, this);
}

wxFilePickerButton::~wxFilePickerButton()
{
}

void wxFilePickerButton::AlignAndFit(wxWindow* buddy)
{
   if (GetLabel().IsEmpty()) SetLabel(wxT("..."));
#if (wxVERSION_NUMBER >= 2900)
   // if (buddy == NULL) buddy = m_text->GetWindow(); no such method :(
#else
   if (buddy == NULL) buddy = m_edit ? (wxWindow*)m_edit : (wxWindow*)m_combo;
#endif
   ::wxPickerButton_SetInitialSize(this, buddy);
}

long wxFilePickerButton::GetDialogStyle() const
{
   long filedlgstyle = 0;

   if (this->HasFlag(wxFLP_OPEN))
      filedlgstyle |= wxFD_OPEN;
   if (this->HasFlag(wxFLP_SAVE))
      filedlgstyle |= wxFD_SAVE;
   if (this->HasFlag(wxFLP_OVERWRITE_PROMPT))
      filedlgstyle |= wxFD_OVERWRITE_PROMPT;
   if (this->HasFlag(wxFLP_FILE_MUST_EXIST))
      filedlgstyle |= wxFD_FILE_MUST_EXIST;
   if (this->HasFlag(wxFLP_CHANGE_DIR))
      filedlgstyle |= wxFD_CHANGE_DIR;

   return filedlgstyle;
}

wxFileDialog* wxFilePickerButton::CreateDialog()
{
   UpdatePickerFromTextCtrl();
   wxString caption = m_message;
   if (caption.IsEmpty())
   {
      caption = wxGetStockLabel(this->HasFlag(wxFLP_SAVE) ? wxID_SAVEAS : wxID_OPEN, wxSTOCK_WITHOUT_ELLIPSIS);
   }
   wxFileDialog *p = new wxFileDialog(GetParent(), caption, wxEmptyString, wxEmptyString, m_wildcard, GetDialogStyle());
   p->SetPath(m_path);
   return p;
}

void wxFilePickerButton::OnButtonClick(wxCommandEvent&)
{
    wxFileDialog *p = CreateDialog();
    if (p->ShowModal() == wxID_OK)
    {
        m_path = p->GetPath();
        UpdateTextCtrlFromPicker();

        // fire an event
        wxFileDirPickerEvent event(wxEVT_COMMAND_FILEPICKER_CHANGED, this, GetId(), m_path);
        GetEventHandler()->ProcessEvent(event);
    }
    wxDELETE(p)
}

void wxFilePickerButton::UpdatePickerFromTextCtrl()
{
#if (wxVERSION_NUMBER >= 2900)
   m_path = m_text->GetValue();
#else
        if (m_edit ) m_path = m_edit ->GetValue();
   else if (m_combo) m_path = m_combo->GetValue();
#endif
}

void wxFilePickerButton::UpdateTextCtrlFromPicker()
{
#if (wxVERSION_NUMBER >= 2900)
   m_text->SetValue(m_path);
#else
        if (m_edit ) m_edit ->SetValue(m_path);
   else if (m_combo) m_combo->SetValue(m_path);
#endif
}

/////////////////////////////////////////////////////////////////////////////
// wxDirPickerButton

IMPLEMENT_DYNAMIC_CLASS(wxDirPickerButton, wxButton)

wxDirPickerButton::wxDirPickerButton() : wxButton()
{
#if (wxVERSION_NUMBER >= 2900)
   m_text = NULL;
#else
   m_combo = NULL;
   m_edit  = NULL;
#endif
   m_message = wxDirSelectorPromptStr;
   Connect(wxEVT_COMMAND_BUTTON_CLICKED,
         wxCommandEventHandler(wxDirPickerButton::OnButtonClick),
         NULL, this);
}

wxDirPickerButton::~wxDirPickerButton()
{
}

void wxDirPickerButton::AlignAndFit(wxWindow* buddy)
{
   if (GetLabel().IsEmpty()) SetLabel(wxT("..."));
#if (wxVERSION_NUMBER >= 2900)
   // if (buddy == NULL) buddy = m_text->GetWindow(); no such method :(
#else
   if (buddy == NULL) buddy = m_edit ? (wxWindow*)m_edit : (wxWindow*)m_combo;
#endif
   ::wxPickerButton_SetInitialSize(this, buddy);
}

long wxDirPickerButton::GetDialogStyle() const
{
   long dirdlgstyle = wxDD_DEFAULT_STYLE;

   if (this->HasFlag(wxDIRP_DIR_MUST_EXIST))
      dirdlgstyle |= wxDD_DIR_MUST_EXIST;
   if (this->HasFlag(wxDIRP_CHANGE_DIR))
      dirdlgstyle |= wxDD_CHANGE_DIR;

   return dirdlgstyle;
}

wxDirDialog* wxDirPickerButton::CreateDialog()
{
   UpdatePickerFromTextCtrl();
   return new wxDirDialog(GetParent(), m_message, m_path, GetDialogStyle());
}

void wxDirPickerButton::OnButtonClick(wxCommandEvent&)
{
    wxDirDialog *p = CreateDialog();
    if (p->ShowModal() == wxID_OK)
    {
        m_path = p->GetPath();
        UpdateTextCtrlFromPicker();

        // fire an event
        wxFileDirPickerEvent event(wxEVT_COMMAND_DIRPICKER_CHANGED, this, GetId(), m_path);
        GetEventHandler()->ProcessEvent(event);
    }
    wxDELETE(p)
}

void wxDirPickerButton::UpdatePickerFromTextCtrl()
{
#if (wxVERSION_NUMBER >= 2900)
   m_path = m_text->GetValue();
#else
        if (m_edit ) m_path = m_edit ->GetValue();
   else if (m_combo) m_path = m_combo->GetValue();
#endif
}

void wxDirPickerButton::UpdateTextCtrlFromPicker()
{
#if (wxVERSION_NUMBER >= 2900)
   m_text->SetValue(m_path);
#else
        if (m_edit ) m_edit ->SetValue(m_path);
   else if (m_combo) m_combo->SetValue(m_path);
#endif
}