Быстрый способ упаковки содержимого папки в ZIP-архив

В wxWidgets есть такая отличная штука, как wxDirTraverser.
Что это и с чем его едят? Официальная документация говорит нам вот что:

wxDirTraverser is an abstract interface which must be implemented by objects passed to wxDir::Traverse() function.
Как по мне, довольно странное описание %), ничего толкового мы из него не узнаем. Тогда идем смотреть описание wxDir::Traverse():
Enumerate all files and directories under the given directory recursively calling the element of the provided wxDirTraverser object for each of them.
Уже лучше, оказывается, объект wxDirTraverser вызывается для каждого найденного объекта в указанном каталоге. Поиск выполняется рекурсивно для всех подкаталогов.
Итак, чем нам может помочь эта информация. Из документации видно, что у класса wxDirTraverser есть методы, которые вызываются для каждого файла или подкаталога (соответственно OnFile() и OnDir()), в которые передается путь найденного файла или каталога. Это значит что, получая имя каждого файла, мы можем сразу же добавить этот файл в архив без необходимости получения списка всех файлов и подкаталогов в массив (как мы бы сделали при использовании wxDir::GetAllFiles()).
Как это реализовано, смотрим ниже:

Dir Travaerser:

#ifndef _WX_DIR_TRAVERSER_ZIP_H
#define _WX_DIR_TRAVERSER_ZIP_H

#include 
#include 
#include 
#include 
#include 

class wxDirTraverserZIP : public wxDirTraverser
{
public:
	wxDirTraverserZIP(wxZipOutputStream & stream, const wxString & baseDir) 
		: m_Stream(stream), m_BaseDir(baseDir) { }

	virtual wxDirTraverseResult OnFile(const wxString& filename)
	{
		do
		{
			wxFileName newFileName(filename);
			newFileName.MakeRelativeTo(m_BaseDir);
			if(!m_Stream.PutNextEntry(newFileName.GetFullPath()))
			{
				break;
			}
			wxFileInputStream in(filename);
			if(!in.IsOk()) 
			{
				break;
			}
			m_Stream.Write(in);
			return wxDIR_CONTINUE;
		}
		while(false);
		return wxDIR_STOP;
	}

	virtual wxDirTraverseResult OnDir(const wxString& dirname)
	{
		return wxDIR_CONTINUE;
	}

private:
	wxZipOutputStream & m_Stream;
	wxString m_BaseDir;
};

#endif

Usage:

bool PackFolder(const wxString & srcFolderName, const wxString & zipFileName)
{
	do
	{
		wxFFileOutputStream out(zipFileName);
		if(!out.IsOk()) break;
		wxCSConv conv(wxT("cp-866"));
		wxZipOutputStream zip(out, 9, conv);
		if(!zip.IsOk()) break;
		wxDirTraverserZIP traverser(zip, srcFolderName);
		wxDir srcDir(srcFolderName);
		if(srcDir.Traverse(traverser, wxEmptyString) == (size_t)-1) break;
		return true;
	}
	while(false);
	return false;
}
...
// Pack the folder
if(!PackFolder(wxT("c:\\test"), wxT("c:\\some.zip")))
{
//Handle error here
}

Find & Replace для wxWidgets-приложений

Эх если бы вы знали сколько редакторов не могут корректно делать поиск и замену, особенно касательно неанглийского языка…

На прикручивания поиска&замены, для своего проекта на wxWidgets я убил пару дней. И вот теперь когда это кое-как работает, хочу поделится с вами. В open source проектах не удалось найти реализацию поиска для wxTextCtrl или wxRichTextCtrl, кое-какой код удалось найти на форуме, но его работоспособность оставляла желать очень и очень лучшего. Видимо все серьозные программисты для реальных проектов используют обертки над scintill‘ой. Так что пришлось писать практически с нуля.

Итак поехали.

В меню добавляем пункты Find, Find Next, Replace и соответвующие обработчики в нашу EVENT_TABLE

  // Сообщения меню
  EVT_MENU( wxID_FIND, FlashnoteFrame::OnFindClick )
  EVT_MENU( ID_FindNext, FlashnoteFrame::OnFindNextClick )
  EVT_MENU( wxID_REPLACE, FlashnoteFrame::OnReplaceClick )

  // Сообщения специфические для стандартного диалога поиска и замены
  EVT_FIND( wxID_ANY, FlashnoteFrame::OnFind )
  EVT_FIND_NEXT( wxID_ANY, FlashnoteFrame::OnFind )
  EVT_FIND_CLOSE( wxID_ANY, FlashnoteFrame::OnFindClose )
  EVT_FIND_REPLACE( wxID_ANY, FlashnoteFrame::OnReplace )
  EVT_FIND_REPLACE_ALL( wxID_ANY, FlashnoteFrame::OnReplaceAll )

В заголовочный файл добавляем такой блок


//********* FIND AND REPLACE *****************************************

  void OnFindClick( wxCommandEvent& event );
  void OnFindNextClick( wxCommandEvent& event );
  void OnReplaceClick( wxCommandEvent& event );

  void OnFind( wxFindDialogEvent& event );
  bool OnFind( bool restart=false);
  void OnFindClose( wxFindDialogEvent& event );

  void OnReplace(wxFindDialogEvent& event);
  bool OnReplace();
  void OnReplaceAll(wxFindDialogEvent& event);

  wxFindReplaceData m_findData;
  wxFindReplaceDialog * m_findDialog;
  // Specify what kind of wxFindReplaceDialog we have for search or for replace
  bool IsReplaceDlg;
  // Don't show MessageBox when can't found text when user want 
  // to ReplaceAll if at least one replace was made И не идем по кругу!
  int IsReplaceAll;

Ну и теперь пишем собственно код реализующий данную функциональность

void FlashnoteFrame::OnFindClick( wxCommandEvent& event )
{
	if ( NULL == m_findDialog )
	{
		m_findDialog = new wxFindReplaceDialog( this, &m_findData, 
			_("Find"), wxFR_NOWHOLEWORD );
		m_findDialog->Centre( wxCENTRE_ON_SCREEN | wxBOTH );
		IsReplaceDlg=false;
	}

	m_Note->SetFocus();
	m_findDialog->Show( true );
}

void FlashnoteFrame::OnReplaceClick( wxCommandEvent& event )
{
	if ( NULL == m_findDialog )
	{
		m_findDialog = new wxFindReplaceDialog( this, &m_findData, 
			_("Find & Replace"), wxFR_REPLACEDIALOG | wxFR_NOWHOLEWORD);
		m_findDialog->Centre( wxCENTRE_ON_SCREEN | wxBOTH );
		IsReplaceDlg=true;
	}
	m_Note->SetFocus();
	m_findDialog->Show( true );
}

void FlashnoteFrame::OnFindClose( wxFindDialogEvent& event )
{
	m_findDialog->Destroy();
	m_findDialog = NULL;
}

void FlashnoteFrame::OnFindNextClick( wxCommandEvent& event )
{	
	if(m_findData.GetFindString().IsEmpty())
	{
		wxCommandEvent t;
		OnFindClick(t);
	}
	else  OnFind();   
}

void FlashnoteFrame::OnFind( wxFindDialogEvent& event ) 
{ 	
	OnFind();  
}

// Return true if we found text - need for ReplaceALL
bool FlashnoteFrame::OnFind(bool restart)
{
	if (!IsReplaceDlg)
	{	// Only for FIND dlg
		bool HideFindDialogAfterFirstFing=true; 
		if (HideFindDialogAfterFirstFing && m_findDialog)  
		{  // only if user want and our dlg is alive
			wxFindDialogEvent t; 
			OnFindClose(t); 
			m_Note->SetFocus();
		}
	}

	wxString find = m_findData.GetFindString();

	int flags = m_findData.GetFlags();
	bool forward(flags & wxFR_DOWN);

	int LastPosition=m_Note->GetLastPosition();
	long m_foundPos, StartFromBack, StartFromForward;

	if (restart)
	{
		StartFromForward=0; StartFromBack=LastPosition;
	}
	else 
	{ // First time
		m_Note->GetSelection(&StartFromBack, &StartFromForward);

		// If we have not selection, wxTextCtrl return 
		// StartFromBack=StartFromForward=Current_Insertion_Point, 
		// but wxRichTextCtrl return -2,-2 
		if (StartFromBack<0) StartFromBack=StartFromForward=m_Note->GetInsertionPoint();

		// Some optimization 
		if (IsReplaceAll==0)
		{
			if (forward && (StartFromForward==LastPosition)) StartFromForward=0;
			if (!forward && (StartFromBack==0)) StartFromBack=LastPosition;

			// если первый раз ищем вперед с первой позиции или назад с 
			// последней - то незачем второй раз проходить
			if ( (forward && (StartFromForward==0)) || 
				( !forward && (StartFromBack==LastPosition)) ) restart=true; 
		}
		// End of Some optimization 
	}

	wxString string = forward ? 
		m_Note->GetRange(StartFromForward, LastPosition) : 
	m_Note->GetRange(0, StartFromBack);

	if (flags & wxFR_MATCHCASE) 
		m_foundPos = forward ? string.find(find) : string.rfind(find);
	else	
	{		
		wxString string_lwr(string), find_lwr(find);
		BegUtils::StrMakeLowerUniversal(string_lwr);
		BegUtils::StrMakeLowerUniversal(find_lwr);
		m_foundPos = forward ? 
			string_lwr.find(find_lwr) : 
		string_lwr.rfind(find_lwr);

		//m_foundPos = forward ? 
		//string.Lower().find(find.Lower()) : 
		//string.Lower().rfind(find.Lower());
	}

	// Начинаем с начала (конца) если не нашли и сюда зашли первый раз 
	// (restart==false) И это не заменить все
	if (m_foundPos==-1)
	{
		if (restart || (IsReplaceAll>0))
		{  // уже по второму разу прошли или зашли из ReplaceAll
			if (IsReplaceAll<2) 
				wxMessageBox(wxString::Format(
				_("Cannot find \"%s\""), find), wxT("Flashnote"), 
				wxOK|wxICON_INFORMATION, m_findDialog);
			return false; //Not found
		}
		else  
			return OnFind(true);  // Ищем с начала и возвращяем нашли или нет
	}

	long startPos = forward ? StartFromForward + m_foundPos : m_foundPos;
	long endPos = startPos + find.length();

	m_Note->SetSelection(startPos, endPos);
	return true;
}

void FlashnoteFrame::OnReplace(wxFindDialogEvent& event)
{
	OnReplace(); 
} 
bool FlashnoteFrame::OnReplace()
{

	wxString FindText(m_findData.GetFindString());
	wxString SelText(m_Note->GetStringSelection());

	if (!SelText.IsEmpty())
	{ // We have selected text, if is it equal our FindText - replace it

		//		bool IsTextToReplace = m_findData.GetFlags() & 
		//			wxFR_MATCHCASE ? SelText==FindText : 
		//			SelText.Lower()==FindText.Lower();
		//		if (IsTextToReplace) m_Note->WriteText(m_findData.GetReplaceString());

		if (! (m_findData.GetFlags() & wxFR_MATCHCASE)) 
		{
			BegUtils::StrMakeLowerUniversal(SelText);
			BegUtils::StrMakeLowerUniversal(FindText);
		}

		if (SelText==FindText)
		{	
			// this line is needed only for wxRichTextCtrl and needed not for wxTextCtrl
			m_Note->DeleteSelection();
			m_Note->WriteText(m_findData.GetReplaceString());
		}
	}
	return OnFind();

}
void FlashnoteFrame::OnReplaceAll(wxFindDialogEvent& event)
{
	//Note: We can't just use wxString::Replace() for 
	//replace, if we have handle wxFR_MATCHCASE flag
	IsReplaceAll=1; // start ReplaceAll
	while (OnReplace()) ++IsReplaceAll; 

	if (IsReplaceAll>1)  // we have replaced something
		wxMessageBox(wxString::Format(_("Total Replacement: %i"), 
		IsReplaceAll-1), wxT("Flashnote"), 
		wxOK|wxICON_INFORMATION, m_findDialog);

	IsReplaceAll=0; // stop ReplaceAll
}

Основная работа идет в функции OnFind(bool restart/*=false*/) и OnReplace(), все остальное фактически обвязка вокруг этих двух функций.

Код взят из реального приложения – программы для быстрых заметок (будет использоватся с v3.0) и отражает мое субьективное представление как должен вести себя диалог вставки замены. Из плюсов – неплохо оттестирован, с учетом мелочей, но если найдете проблемы – велком в комментарии.

Особенности:

  • Поиск идет циклически по кругу, то есть по достижению конца (начала) документа, автоматически переходит на начало (конец) и продолжает поиск.
  • Замена в ручном режиме ведет себя точно также, в режиме Replace ALL документ просматривается только до конца.
  • После нахождения первого вхождения искомой строки диалог поиска скрывается, дальнейший поиск идет с использованием F3 (шоткат для меню Find Next). Изменить это можно изменив строчку bool HideFindDialogAfterFirstFing=true; в функции OnFind(bool restart/*=false*/)
  • Для приведения строки к нижнему регистру используется самописная функция StrMakeLowerUniversal() (см. ниже). При использовании стандартной Lower() регистронезависимый поиск будет работать только для латиницы.

Функция StrMakeLowerUniversal(wxString & data) преобразует строку к нижнему регистру, работая со строками на любом языке, в отличии от Lower() которая понимает только латиницу. Из минусов – поддержка только MSW.

void BegUtils::StrMakeLowerUniversal(wxString & data)
{
#ifdef __WIN32__
int len=data.length()+1;
wxChar * buf = new wxChar[len];
wxStrcpy(buf, data.c_str());
CharLower(buf);
data=buf;
delete [] buf;
#else
data.MakeLower();
#endif

Успехов в написании безглючных приложений:)

Об авторе: Тюшков Николай, автор программы для мгновенной вставки текста и ряда других программ. Так же я веду несколько авторских блогов, в частности блог мысли про wx-widgets по-русски.

Собираем сторонние компоненты wxWidgets в Code::Blocks

Сегодня я расскажу о том, как создать проект Code::Blocks, использующий в своей работе сторонние библиотеки. В качестве сторонней библиотеки мы возьмем библиотеку wxPropertyGrid, предоставляющую возможность встроить редактор свойств в приложение.

Для того, чтобы проверить как все работает, нам необходимо создать тестовое приложение, которое будет использовать в своей работе дополнительную библиотеку. Тестовое приложение я собирал с настройками, указанными ниже:

Исходный код тестового приложения:

#include 
#include 

class wxTestApp : public wxApp
{
    virtual bool OnInit();
};

DECLARE_APP(wxTestApp)

class wxTestMainFrame : public wxFrame
{
    void CreateControls()
    {
        wxBoxSizer * sizer = new wxBoxSizer(wxVERTICAL);
        SetSizer(sizer);
        wxPropertyGrid * propgrid = new wxPropertyGrid(this, wxNewId(), 
wxDefaultPosition, wxDefaultSize, 0);
        sizer->Add(propgrid, 1, wxEXPAND);
    }
public:
    wxTestMainFrame()
    {
        Create(NULL);
    }
    bool Create(wxWindow * parent, wxWindowID id = wxID_ANY, const wxString & title = wxT("wxTest"))
    {
        bool res = wxFrame::Create(parent, id, title);
        if(res)
        {
            CreateControls();
        }
        return res;
    }
};

bool wxTestApp::OnInit()
{
    wxTestMainFrame * frame = new wxTestMainFrame();
    SetTopWindow(frame);
    frame->Centre();
    frame->Show();
    return true;
}

IMPLEMENT_APP(wxTestApp)

После того, как мы создали тестовое приложение, нам необходимо добавить библиотеку wxPropertyGrid в workspace. wxPropertyGrid распространяется в виде архива с исходным кодом, который мы распакуем в подкаталог propgrid. Далее в Code::Blocks создаем новый проект статической библиотеки:


Нам необходимо чтобы файл проекта был создан в папке propgrid/build, поэтому этот параметр необходимо подправить вручную:


Изменяем параметры таким образом чтобы наша бибилиотека создавалась в каталоге propgrid/lib (чего-то мне показалось, что параметр Output dir в текущей версии Code::Blocks не работает):


Далее идем в настройки проекта и видим, что в Output filename у нас записано немного не то, что мы указывали при создании проекта, а именно, файл библиотеки с текущими параметрами будет создаваться в каталоге propgrid/build (в каталоге с проектом). Изменяем параметр Output filename и указываем новый путь вывода статической библиотеки:


Теперь нам необходимо указать, в какой папке наш проект статической библиотеки будет искать заголовочные файлы. Пути к заголовочным файлам необходимо указать в настройках общих для обеих сборок (Debug и Release):


Для того, чтобы проект библиотеки был собран с такими же настройками как и наше тестовое приложение (это требование обязательно), нам необходимо скопировать список директив компилятора из проекта приложения в проект библиотеки:


Далее проставляем зависимости:


Добавляем в список каталогов для поиска заголовочных файлов каталог с заголовочными файлами wxPopertyGrid:


В список каталогов для поиска библиотек добавляем путь к папке со статической библиотекой wxPropertyGrid:


В настройках линкера в список зависимостей добавляем статическую библиотеку wxPropertyGrid:


Вот и все, можно собирать проект. После сборки получим приблизительно такой результат:

Скачать пример

Прозрачное журналирование с wxLog

В wxWidgets есть отличный механизм журналирования wxLog.
С его помощью можно решить проблему ведения лога одновременно в файл и, например, в текстовый контрол.

Если необходимо выводить сообщения в файл не только в ANSI, а и в Unicode,
то очень удобно использовать логгер wxLogStream.
Для его использования, потребуется собрать (если еще не собрано) wxWidgets с выставленной поддержкой std потоков:
Continue reading…

wxJSON Tutorial – Part IV – Using Comment Lines in wxJSON

Preface

Here is a fourth part of wxJSON tutorial provided by Luciano Cattani, author and maintainer of wxJSON library.

Using Comment Lines in wxJSON

Comments are not supported by the JSON syntax specifications but many JSON implementations do recognize and store comment lines in the JSON value objects. Starting by version 0.2, the wxJSON library do recognize and store C/C++ comment lines in the JSON input text and can also write comments to the JSON output text.
Continue reading…

wxJSON Tutorial – Part III – Describing a Table

Preface

Here is a third part of wxJSON tutorial provided by Luciano Cattani, author and maintainer of wxJSON library.

Describing a Table with wxJSON

How many times did you use a table in your application? I know the answer: many times. So the best thing would be to write a general-purpose panel window that is capable to show every possible table and table’s format.
Continue reading…

wxJSON Tutorial – Part II – Сonfiguration File

Preface

Here is a second part of wxJSON tutorial provided by Luciano Cattani, author and maintainer of wxJSON library.

Creating a Configuration File with wxJSON

We start by using JSON for an application’s configuration file. There are many formats for storing application’s configuration data. I remember when there was MS-DOS: each application used its own, unreadable and proprietary format (it was a nightmare). Next came Windows 3: it had a better way for storing application’s configuration data; they were kept in an .INI file which contains simple ASCII text. This was a good thing because it was easier for humans to fine-tuning application’s behaviour.

In this example we use JSON to store the configuration data of a simple web server application. If you take a look at the Apache config file you will notice that our example looks very similar (but much more human readable).
Continue reading…