View Issue Details

IDProjectCategoryView StatusLast Update
0014567ScribusStory Editor / Text Framespublic2020-05-07 07:37
ReporterFahad Assigned Toale  
PrioritynormalSeverityfeatureReproducibilityalways
Status assignedResolutionopen 
Product Version 
Target VersionFixed in Version 
Summary0014567: [PATCH] Search \ replace all text frames
DescriptionThe scenario:
I am designing a book that has more than 50 text frames and then I discover spelling error in one word or I want to replace it with another word. It is very boring to select all 50 text frames one by one and open Search\replace and write the word and its replacement.


Search \ replace dialog should work on all text frames by default.
Tagssearch
PatchYes

Relationships

has duplicate 0010477 closedale Find and Replace function only on one page 
related to 0013483 new Ability to see where in the scribus whole document one style is used 

Activities

Fahad

2018-01-18 08:18

developer   ~0044857

Here is a patch will implement this feature. The work is done by @Majda.

The current status now:
- every thing is working fine, you can search and replace into all text frames.
- the logic behind search for all text frames is explained as follow:
 The search algorithm is to start from selected text frame, if there is no
text frame selected select the first text frame in the current page, if
not, find the first text frame from next pages, if reaching the end of
the document start searching from the first page.

Enhancement may be added in the future:
 - make search and replace dialog is modeless, so you can search and interact with text directly.

searchAll.patch (12,916 bytes)
Index: scribus/appmodehelper.cpp
===================================================================
--- scribus/appmodehelper.cpp	(revision 22354)
+++ scribus/appmodehelper.cpp	(working copy)
@@ -215,11 +215,20 @@
 		case modeNormal:
 			{
 				bool editSearchReplace = false;
-				if (currItem != 0)
+				//enable search if at least one text frame is in the document
+				if (doc->Items->count() != 0)
 				{
-					editSearchReplace |= currItem->isTextFrame();
-					editSearchReplace |= (currItem->itemText.length() > 0);
-					editSearchReplace |= (doc->m_Selection->count() == 1);
+					for (int i = 0; i < doc->Items->count(); i++)
+					{
+						if (doc->Items->at(i)->isTextFrame())
+						{
+							if (doc->Items->at(i)->itemText.length() > 0)
+							{
+								editSearchReplace = true;
+								break;
+							}
+						}
+					}
 				}
 				(*a_scrActions)["editSearchReplace"]->setEnabled(editSearchReplace);
 
@@ -526,7 +535,23 @@
 			(*a_scrActions)["editCut"]->setEnabled(false);
 			(*a_scrActions)["editCopy"]->setEnabled(false);
 			(*a_scrActions)["editCopyContents"]->setEnabled(false);
-			(*a_scrActions)["editSearchReplace"]->setEnabled(false);
+			if (doc->Items->count() != 0)
+			{
+				for (int i = 0; i < doc->Items->count(); i++)
+				{
+					if (doc->Items->at(i)->isTextFrame())
+					{
+						if (doc->Items->at(i)->itemText.length() > 0)
+						{
+							(*a_scrActions)["editSearchReplace"]->setEnabled(true);
+							break;
+						}
+					}
+				}
+			}
+			else
+				(*a_scrActions)["editSearchReplace"]->setEnabled(false);
+
 			(*a_scrActions)["extrasHyphenateText"]->setEnabled(false);
 			(*a_scrActions)["extrasDeHyphenateText"]->setEnabled(false);
 
Index: scribus/scribus.cpp
===================================================================
--- scribus/scribus.cpp	(revision 22354)
+++ scribus/scribus.cpp	(working copy)
@@ -8502,10 +8502,74 @@
 
 void ScribusMainWindow::SearchText()
 {
-	PageItem *currItem = doc->m_Selection->itemAt(0);
+	PageItem *currItem = NULL;
+	bool SearchCur = false;
+	bool flag = false;
+
+	/*
+	 * The currItem is selected based on:
+	 * 1- first text frame selected.
+	 * if not
+	 *	select first text frame in current page.
+	 * if not
+	 *	got to next page and select the first text frame
+	 * if you reach the end of the document
+	 *	start from the first page.
+	 */
+	if (doc->Items->count() == 0)
+		return;
+	else if (doc->m_Selection->count() > 0)
+	{
+		//start searching from the selected text frame
+		for (int i = 0 ; i < doc->m_Selection->count(); i++)
+			if (doc->m_Selection->itemAt(i)->isTextFrame() && doc->m_Selection->itemAt(i)->itemText.length() > 0 )
+			{
+				flag = true;
+				currItem = doc->m_Selection->itemAt(i);
+				if (doc->m_Selection->count() == 1)
+					SearchCur = true;
+				break;
+			}
+	}
+	// if no text frame selected
+	if (!flag)
+	{
+		int i = 0;
+		int j = 0;
+		//find current page index
+		for (i = 0; i < doc->Items->count(); i++)
+		{
+			if (doc->Items->at(i)->OwnPage >= doc->currentPageNumber())
+				break;
+		}
+		j = i;
+		//start from current page  till the end of the document
+		for (i = j; i < doc->Items->count(); i++)
+		{
+			if (doc->Items->at(i)->isTextFrame() && doc->Items->at(i)->itemText.length() > 0)
+			{
+				flag= true;
+				currItem = doc->Items->at(i);
+				break;
+			}
+		}
+		//if reached the end of the document start from beggining
+		if (!flag)
+			for (i = 0; i < j; i++)
+			{
+				if (doc->Items->at(i)->isTextFrame() && doc->Items->at(i)->itemText.length() > 0)
+				{
+					currItem = doc->Items->at(i);
+					break;
+				}
+			}
+	}
+
+	doc->m_Selection->addItem(currItem);
+	//PageItem *currItem = doc->m_Selection->itemAt(0);
 	view->requestMode(modeEdit);
-	currItem->itemText.setCursorPosition(0);
-	SearchReplace* dia = new SearchReplace(this, doc, currItem);
+	currItem->itemText.setCursorPosition(currItem->firstInFrame());
+	SearchReplace* dia = new SearchReplace(this, doc, currItem, true , SearchCur);
 	connect(dia, SIGNAL(NewFont(const QString&)), this, SLOT(SetNewFont(const QString&)));
 	connect(dia, SIGNAL(NewAbs(int)), this, SLOT(setAlignmentValue(int)));
 	dia->exec();
===================================================================
--- scribus/ui/search.cpp	(revision 22354)
+++ scribus/ui/search.cpp	(working copy)
@@ -30,7 +30,9 @@
 #include "prefsmanager.h"
 #include "scpage.h"
 #include "scribus.h"
+#include "scribusview.h"
 #include "scrspinbox.h"
+#include "selection.h"
 #include "shadebutton.h"
 #include "styleselect.h"
 #include "ui/storyeditor.h"
@@ -37,7 +39,7 @@
 #include "util.h"
 #include "util_text.h"
 
-SearchReplace::SearchReplace( QWidget* parent, ScribusDoc *doc, PageItem* ite, bool mode )
+SearchReplace::SearchReplace( QWidget* parent, ScribusDoc *doc, PageItem* ite, bool mode, bool SearchCur )
 	: QDialog( parent ),
 	matchesFound(0)
 {
@@ -46,7 +48,33 @@
 	m_notFound = false;
 	m_itemMode = mode;
 	m_firstMatchPosition = -1;
+	m_SearchCurrent = SearchCur;
 
+	// Mapping between page number and text frames
+	QList<PageItem*> TFItems; //list of text frames
+	PageItem* tempPageItem; //temporary page item
+	for (int i = 0; i < m_doc->DocPages.count(); i++)
+	{
+		for (int j = 0; j < m_doc->Items->count(); j++)
+		{
+			tempPageItem = m_doc->Items->at(j);
+			//check if item is text frame and in the same page, add it to the map
+			if (tempPageItem->isTextFrame() && i == tempPageItem->OwnPage)
+			{
+				tempPageItem->itemText.deselectAll();
+				tempPageItem->HasSel = false;
+				TFItems.append(tempPageItem);
+			}
+		}
+		if (!TFItems.empty())
+			ItemsPageNum.insert(i,TFItems);
+
+		TFItems.clear();
+	}
+	//for chain text start form the first letter in the chain
+	if (m_item->itemText.cursorPosition() == 0)
+		m_item->itemText.setCursorPosition(m_item->firstInFrame());
+
 	setModal(true);
 	setWindowTitle( tr( "Search/Replace" ) );
 	setWindowIcon(IconManager::instance()->loadIcon("AppIcon.png"));
@@ -261,6 +289,10 @@
 	if (mode)
 		Word->setEnabled(false);
 	OptsLayout->addWidget( Word );
+	SearchCurrent = new QCheckBox ( tr ("Current Selected Text Frame") , this);
+		OptsLayout -> addWidget(SearchCurrent);
+		if (mode)
+			SearchCurrent->setEnabled(false);
 	CaseIgnore = new QCheckBox( tr( "&Ignore Case, Diacritics and Kashida" ), this );
 	if (mode)
 		CaseIgnore->setEnabled(false);
@@ -377,14 +409,10 @@
 
 void SearchReplace::slotDoSearch()
 {
-	int maxChar = m_item->itemText.length() - 1;
+	int maxChar = m_item->maxCharsInFrame() - 1;
 	DoReplace->setEnabled(false);
 	AllReplace->setEnabled(false);
-	if (m_itemMode)
-	{
-		m_item->itemText.deselectAll();
-		m_item->HasSel = false;
-	}
+
 	QString fCol = "";
 	QString sCol = "";
 	QString sFont = "";
@@ -437,11 +465,18 @@
 	int a, textLen(0);
 	if (m_itemMode)
 	{
+		m_item->itemText.deselectAll();
+		m_item->HasSel = false;
+		m_doc->m_Selection->clear();
+		m_item->setSelected(true);
+		maxChar = m_item->maxCharsInFrame() - 1;
+		as = m_item->itemText.cursorPosition();
+		m_replStart = as;
 		Qt::CaseSensitivity cs = Qt::CaseSensitive;
 		if (CaseIgnore->isChecked())
 			cs = Qt::CaseInsensitive;
 
-		for (a = as; a < m_item->itemText.length(); ++a)
+		for (a = as; a < m_item->maxCharsInFrame(); ++a)
 		{
 			found = true;
 			if (SText->isChecked())
@@ -510,6 +545,17 @@
 			{
 				m_item->itemText.select(a, textLen);
 				m_item->HasSel = true;
+				m_doc->DoDrawing = true;
+				m_item->update();
+
+				//move scroll bar to the selected element
+				QTransform itemTrans = m_item->getTransform();
+				double xOffset=0.0, yOffset=0.0;
+				xOffset = m_item->width() / 2.0;
+				yOffset = m_item->height() / 2.0;
+				QPointF point = itemTrans.map(QPointF(xOffset, yOffset));
+				m_doc->view()->SetCCPo(point.x(), point.y());
+
 				if (rep)
 				{
 					DoReplace->setEnabled(true);
@@ -533,15 +579,68 @@
 				}
 			}
 		}
-		if ((!found) || (a == m_item->itemText.length()))
+		if ((!found) || (a >= maxChar+1))
 		{
 			m_doc->DoDrawing = true;
 			m_item->update();
+			m_item->setSelected(false);
 			DoReplace->setEnabled(false);
 			AllReplace->setEnabled(false);
-			ScMessageBox::information(this, tr("Search/Replace"), tr("Search finished"));
-			m_item->itemText.setCursorPosition(0);
 			m_notFound = false;
+
+			if (SearchCurrent->isChecked())
+			{
+				ScMessageBox::information(this, tr("Search/Replace"), tr("Search finished"));
+				m_item->itemText.setCursorPosition(m_item->firstInFrame());
+			}
+			else
+			{
+				//update m_item to the next text frame
+				QList<int> pageKeys = ItemsPageNum.keys();
+				int pageKeysIndex = pageKeys.indexOf(m_item->OwnPage); //index for pages
+
+				for (int i = 0; i < ItemsPageNum.value(pageKeys.at(pageKeysIndex)).count(); i++)
+				{
+					if (m_item == ItemsPageNum.value(pageKeys.at(pageKeysIndex)).at(i))
+					{
+						//go to next item in the same page
+						if (!ItemsPageNum.value(pageKeys.at(pageKeysIndex)).endsWith(m_item))
+						{
+							m_item = ItemsPageNum.value(pageKeys.at(pageKeysIndex)).at(i + 1);
+							m_item->itemText.setCursorPosition(m_item->firstInFrame());
+							slotDoSearch();
+							break;
+						}
+						else // if the text frame was the last item in the page
+						{
+							pageKeysIndex ++;//increase index
+							if (pageKeysIndex >= pageKeys.count()) //if the last page search finished
+							{
+								if (ScMessageBox::question(this, tr( "Search/Replace" ),
+														   tr("You reached the end of the document.\nDo you want to start from beginning?"),
+														   QMessageBox::Ok | QMessageBox::Cancel,
+														   QMessageBox::NoButton,	// GUI default
+														   QMessageBox::Ok)	// batch default
+										== QMessageBox::Ok)
+								{
+									m_item = ItemsPageNum.value(pageKeys.first()).first();
+									m_item->itemText.setCursorPosition(m_item->firstInFrame());
+									slotDoSearch();
+								}
+								break;
+							}
+							else
+							{
+								m_item = ItemsPageNum.value(pageKeys.at(pageKeysIndex)).first();
+								m_item->itemText.setCursorPosition(m_item->firstInFrame());
+								slotDoSearch();
+								break;
+							}
+						}
+					}
+				}
+				SearchCurrent->setEnabled(false);
+			}
 		}
 	}
 	else if (m_doc->scMW()->CurrStED != NULL)
@@ -686,6 +785,8 @@
 {
 //	if (m_itemMode)
 //		m_doc->view()->slotDoCurs(false);
+	m_doc->m_Selection->clear();
+	m_doc->m_Selection->addItem(m_item);
 	slotDoReplace();
 	if (m_itemMode)
 	{
@@ -700,12 +801,11 @@
 	{
 		QString repl, sear;
 		int cs, cx;
-		int textLen = 0;
+		int textLen = m_item->itemText.lengthOfSelection();
 		if (RText->isChecked())
 		{
 			repl = RTextVal->text();
 			sear = STextVal->text();
-			textLen = m_item->itemText.lengthOfSelection();
 			if (textLen == repl.length())
 			{
 				for (cs = 0; cs < textLen; ++cs)
@@ -727,12 +827,12 @@
 					m_item->itemText.removeChars(m_replStart+cs, textLen - cs);
 				}
 			}
+		}
+		if (repl.length() > 0)
+		{
 			m_item->itemText.deselectAll();
-			if (repl.length() > 0)
-			{
-				m_item->itemText.select(m_replStart, repl.length());
-				m_item->itemText.setCursorPosition(m_replStart + repl.length());
-			}
+			m_item->itemText.select(m_replStart, repl.length());
+			m_item->itemText.setCursorPosition(m_replStart + repl.length());
 		}
 		if (RStyle->isChecked())
 		{
@@ -867,6 +967,11 @@
 	bool setter = SText->isChecked();
 	STextVal->setEnabled(setter);
 	Word->setEnabled(setter);
+	if (m_doc->m_Selection->count() == 1 && m_SearchCurrent)
+			SearchCurrent->setEnabled(setter);
+		else
+			SearchCurrent->setEnabled(false);
+
 	CaseIgnore->setEnabled(setter);
 	if (setter)
 		STextVal->setFocus();
Index: scribus/ui/search.h
===================================================================
--- scribus/ui/search.h	(revision 22354)
+++ scribus/ui/search.h	(working copy)
@@ -8,6 +8,7 @@
 #define SEARCHREPLACE_H
 
 #include <QDialog>
+#include <QMap>
 class QVBoxLayout;
 class QHBoxLayout;
 class QGridLayout;
@@ -33,7 +34,7 @@
 	Q_OBJECT
 
 public:
-	SearchReplace( QWidget* parent, ScribusDoc *doc, PageItem* ite, bool mode = true );
+	SearchReplace( QWidget* parent, ScribusDoc *doc, PageItem* ite, bool mode = true, bool CurSelected = true );
 	~SearchReplace() {};
 	virtual void slotDoSearch();
 	virtual void slotDoReplace();
@@ -84,6 +85,7 @@
 	StyleSelect* SEffVal;
 	StyleSelect* REffVal;
 	QCheckBox* Word;
+	QCheckBox* SearchCurrent;
 	QCheckBox* CaseIgnore;
 	QPushButton* DoSearch;
 	QPushButton* DoReplace;
@@ -133,6 +135,7 @@
 	bool m_notFound;
 	bool m_itemMode;
 
+	bool m_SearchCurrent; // for search current selected text frame
 	QVBoxLayout* SearchReplaceLayout;
 	QHBoxLayout* SelLayout;
 	QGridLayout* SearchLayout;
@@ -145,7 +148,8 @@
 	/// Number of matches found thus far in a search
 	int matchesFound;
 	int m_firstMatchPosition;
-
+	// to map each item to a page number
+	QMap<int, QList<PageItem*>> ItemsPageNum;
 };
 
 #endif // SEARCHREPLACE_H
searchAll.patch (12,916 bytes)

PeterBenedek

2018-01-19 17:05

developer   ~0044860

This is excellent feature. :-)

Fahad

2018-03-17 06:41

developer   ~0045046

Does anybody test the patch? any feedback?

ale

2018-03-17 13:15

manager   ~0045047

hi fahad,
do you also have a git branch?
that could help for testing...

personally, i'm not too hot in spending time on the search and replace because
a/ i don't use it much in scribus (i wonder if i have ever needed)
b/ the dialog is overwhelming
c/ important features are missing (your patch will introduce one of those features).

if @majda is still working on the search and replace, i'd like to propose a refactoring of the search and dialog... mostly based on the dialog from libreoffice. i attach a png and the "pencil" file.

search-and-replace.epgz (19,627 bytes)

ale

2018-03-17 13:17

manager   ~0045048

... and the png...

search-and-replace.png (52,978 bytes)
search-and-replace.png (52,978 bytes)

ale

2018-03-17 13:18

manager   ~0045050

in the mockup above, if you choose a formatting, it's shown as a text below the search or replace box (as it is done in libreoffice)

ale

2018-03-17 14:34

manager   ~0045051

i can do some more work on the mockup if
- somebody likes it
- somebody wants to work on it

Fahad

2018-03-18 03:54

developer   ~0045053

Hi @ale, Unfortunately Majda has finished her internship and I am here to finish her work if needed. Refactoring of the search and dialog is good but out of my scope here. Let's first test the functionality and see if it works as expected.

Fahad

2018-03-22 08:09

developer   ~0045073

Hi @ale, I have setup HOST repository again and I applied the patch for testing. The repository here

https://github.com/HOST-Oman/scribus

ale

2018-05-15 13:38

manager   ~0045247

@fahad

i've finally tested. it looks neat!

i've found one small issue: if i click "replace all", it tells me that it has reached the end of the document and if it should start from the beginning.
this is a bit odd.
i would expect to simply replace all occurrences.
if you click on "yes", it will ask you again the same question.
not a huge things, but i really do not expect it...

personally, i would expect it to do a one pass replacement.
as an example replacing "text" in "tetextxt" should leave "text" when finished with replacing with the replacing all.

all in all: the simple example i've tested did work.

Fahad

2019-12-05 18:00

developer   ~0047204

@jghali could you please have a look to this bug since you fixed 0011369?

ale

2019-12-06 13:13

manager   ~0047206

i'm working on a new version of the patch that applies to the current code...

ale

2019-12-14 09:09

manager   ~0047268

it's taking some time, but i'm progressing...

ale

2019-12-20 13:29

manager   ~0047300

Last edited: 2019-12-20 17:28

View 2 revisions

voilĂ :

https://gitlab.com/scribus/scribus/merge_requests/21
https://gitlab.com/a.l.e/scribus/-/jobs/385792805/artifacts/file/Scribus-nightly-x86_64.AppImage (for the next week)

some comments:

- this is a complete rework of the search and replace dialog.
- the main goal was to allow search and replace to act on one multiple items (the current selection, the whole document).
- during the rewrite i've fixed several bugs and implemented a few feature requests. often following suggestions in tickets here in mantis.
- there is now a .ui file and several other features will now be much easier to implement: i've opened a request at opensourcedesign, asking a ui/ux expert to work on it. this will take some time: further improvements are to be expected AFTER this patch has been reviewed / accepted.

if possible, please review this patch on gitlab and leave there (or here) comments on what i should change and -- at the end of the process -- commit the merge request as is.
i'd like to continue my work on this feature and it's important that i know how the code works AND learn from the errors i did.

in the merge request, i've added a list of known issues (the patch can probably be committed without fixing them... but it would be nice to get them fixed before the code is in scribus).

p.s.: there are chances that with the time the version on gitlab will diverge from the patch posted here (actually, it's already different...)



search-and-replace.diff (121,047 bytes)
diff --git a/Scribus.pro b/Scribus.pro
index 6918634c5b233dc1a3e69503c15d0a4a4eab308b..2fe7062e223a094f5efdc2704e124e4399d2ca67 100644
--- a/Scribus.pro
+++ b/Scribus.pro
@@ -1124,6 +1124,7 @@ FORMS += scribus/ui/aboutplugins.ui \
          scribus/ui/resourcemanagerbase.ui \
          scribus/ui/resourcemanagerlicensebase.ui \
          scribus/ui/rotationsetter.ui \
+         scribus/ui/searchbase.ui \
          scribus/ui/selectobjects.ui \
          scribus/ui/shortcutwidget.ui \
          scribus/ui/smcellstylewidget.ui \
diff --git a/scribus/CMakeLists.txt b/scribus/CMakeLists.txt
index 0fb1968990eb4b01c001167df5188429d4a0f03d..f0bfe93bbe90836492524174558c65d7de10c28f 100644
--- a/scribus/CMakeLists.txt
+++ b/scribus/CMakeLists.txt
@@ -182,6 +182,7 @@ set(SCRIBUS_UI_SRC
 	ui/replaceonecolor.ui
 	ui/resourcemanagerbase.ui
 	ui/resourcemanagerlicensebase.ui
+	ui/searchbase.ui
 	ui/selectobjects.ui
 	ui/shortcutwidget.ui
 	ui/smcellstylewidget.ui
diff --git a/scribus/appmodehelper.cpp b/scribus/appmodehelper.cpp
index 64fd09f8e5c373919037e7f213e20df1424c3cc4..80a7614f7b169514e41d72bd25c4b34a00653f6b 100644
--- a/scribus/appmodehelper.cpp
+++ b/scribus/appmodehelper.cpp
@@ -205,14 +205,7 @@ void AppModeHelper::setApplicationMode(ScribusMainWindow* scmw, ScribusDoc* doc,
 	{
 		case modeNormal:
 			{
-				bool editSearchReplace = false;
-				if (currItem != nullptr)
-				{
-					editSearchReplace |= currItem->isTextFrame();
-					editSearchReplace |= (currItem->itemText.length() > 0);
-					editSearchReplace |= (doc->m_Selection->count() == 1);
-				}
-				(*a_scrActions)["editSearchReplace"]->setEnabled(editSearchReplace);
+				(*a_scrActions)["editSearchReplace"]->setEnabled(true);
 
 				(*a_scrActions)["editCut"]->setEnabled(currItem != nullptr);
 				(*a_scrActions)["editCopy"]->setEnabled(currItem != nullptr);
@@ -287,20 +280,21 @@ void AppModeHelper::setApplicationMode(ScribusMainWindow* scmw, ScribusDoc* doc,
 					enableTextActions(true, currItem->currentCharStyle().font().scName());
 					currItem->asTextFrame()->toggleEditModeActions();
 				}
+				bool isTextFrame = ((currItem != nullptr) && (currItem->asTextFrame()));
 				if (ScMimeData::clipboardHasScribusData())
 				{
-					bool textFrameEditMode = ((currItem != nullptr) && (currItem->asTextFrame()));
-					(*a_scrActions)["editPaste"]->setEnabled( textFrameEditMode || (currItem == nullptr) );
+					(*a_scrActions)["editPaste"]->setEnabled( isTextFrame || (currItem == nullptr) );
 				}
 				setTextEditMode(true);
 
+				(*a_scrActions)["editSearchReplace"]->setEnabled(isTextFrame);
+
 				if (currItem != nullptr)
 				{
 					(*a_scrActions)["editCut"]->setEnabled(currItem->HasSel);
 					(*a_scrActions)["editCopy"]->setEnabled(currItem->HasSel);
 					(*a_scrActions)["editClearContents"]->setEnabled(currItem->HasSel);
 					(*a_scrActions)["editTruncateContents"]->setEnabled(currItem->HasSel && currItem->isTextFrame());
-					(*a_scrActions)["editSearchReplace"]->setEnabled(true);
 				}
 			}
 			break;
@@ -461,6 +455,7 @@ void AppModeHelper::enableActionsForSelection(ScribusMainWindow* scmw, ScribusDo
 	(*a_scrActions)["editEditWithImageEditor"]->setEnabled(isImageFrame && currItem->imageIsAvailable && currItem->isRaster);
 	(*a_scrActions)["editEditRenderSource"]->setEnabled(isImageFrame && currItem && (currItem->asLatexFrame() || currItem->asOSGFrame()));
 	(*a_scrActions)["itemAdjustFrameHeightToText"]->setEnabled(SelectedType==PageItem::TextFrame && currItem->itemText.length() >0);
+
 	if (!isImageFrame)
 	{
 		(*a_scrActions)["itemImageIsVisible"]->setChecked(false);
@@ -513,7 +508,6 @@ void AppModeHelper::enableActionsForSelection(ScribusMainWindow* scmw, ScribusDo
 			(*a_scrActions)["editCopyContents"]->setEnabled(false);
 			(*a_scrActions)["editClearContents"]->setEnabled(false);
 			(*a_scrActions)["editTruncateContents"]->setEnabled(false);
-			(*a_scrActions)["editSearchReplace"]->setEnabled(false);
 			(*a_scrActions)["extrasHyphenateText"]->setEnabled(false);
 			(*a_scrActions)["extrasDeHyphenateText"]->setEnabled(false);
 
@@ -538,7 +532,6 @@ void AppModeHelper::enableActionsForSelection(ScribusMainWindow* scmw, ScribusDo
 			(*a_scrActions)["editCopy"]->setEnabled(!inAnEditMode);
 			(*a_scrActions)["editClearContents"]->setEnabled(true);
 			(*a_scrActions)["editTruncateContents"]->setEnabled(false);
-			(*a_scrActions)["editSearchReplace"]->setEnabled(false);
 			(*a_scrActions)["extrasHyphenateText"]->setEnabled(false);
 			(*a_scrActions)["extrasDeHyphenateText"]->setEnabled(false);
 			(*a_scrActions)["itemDuplicate"]->setEnabled(true);
@@ -586,7 +579,6 @@ void AppModeHelper::enableActionsForSelection(ScribusMainWindow* scmw, ScribusDo
 			//scrMenuMgr->setMenuEnabled("EditContents", true);
 			(*a_scrActions)["editClearContents"]->setEnabled(true);
 			(*a_scrActions)["editTruncateContents"]->setEnabled(true);
-			(*a_scrActions)["editSearchReplace"]->setEnabled(currItem->itemText.length() != 0);
 			(*a_scrActions)["extrasHyphenateText"]->setEnabled(true);
 			(*a_scrActions)["extrasDeHyphenateText"]->setEnabled(true);
 			//		scrMenuMgr->setMenuEnabled("Item", true);
@@ -687,7 +679,6 @@ void AppModeHelper::enableActionsForSelection(ScribusMainWindow* scmw, ScribusDo
 			(*a_scrActions)["editCopy"]->setEnabled(!inAnEditMode);
 			(*a_scrActions)["editClearContents"]->setEnabled(false);
 			(*a_scrActions)["editTruncateContents"]->setEnabled(false);
-			(*a_scrActions)["editSearchReplace"]->setEnabled(false);
 			(*a_scrActions)["extrasHyphenateText"]->setEnabled(false);
 			(*a_scrActions)["extrasDeHyphenateText"]->setEnabled(false);
 			//		scrMenuMgr->setMenuEnabled("Item", true);
@@ -734,7 +725,6 @@ void AppModeHelper::enableActionsForSelection(ScribusMainWindow* scmw, ScribusDo
 			(*a_scrActions)["editCopy"]->setEnabled(!inAnEditMode);
 			(*a_scrActions)["editClearContents"]->setEnabled(false);
 			(*a_scrActions)["editTruncateContents"]->setEnabled(false);
-			(*a_scrActions)["editSearchReplace"]->setEnabled(false);
 
 			(*a_scrActions)["extrasHyphenateText"]->setEnabled(false);
 			(*a_scrActions)["extrasDeHyphenateText"]->setEnabled(false);
@@ -827,7 +817,6 @@ void AppModeHelper::enableActionsForSelection(ScribusMainWindow* scmw, ScribusDo
 			(*a_scrActions)["itemConvertToTextFrame"]->setEnabled(false);
 			(*a_scrActions)["itemConvertToSymbolFrame"]->setEnabled(false);
 		}
-		(*a_scrActions)["editSearchReplace"]->setEnabled(false);
 
 		bool hPoly = true;
 		for (int i = 0; i < docSelectionCount; ++i)
diff --git a/scribus/scribus.cpp b/scribus/scribus.cpp
index e71a3c3dc2237049130e61096863176c39fc0ec4..030d77f68d7b3598dca317e2c4c58b1b40c40855 100644
--- a/scribus/scribus.cpp
+++ b/scribus/scribus.cpp
@@ -8453,26 +8453,14 @@ void ScribusMainWindow::EditTabs()
 
 void ScribusMainWindow::SearchText()
 {
-	bool wasModeEdit = (doc->appMode == modeEdit);
-
-	PageItem *currItem = doc->m_Selection->itemAt(0);
-	if (!wasModeEdit)
-	{
-		view->requestMode(modeEdit);
-		currItem->itemText.setCursorPosition(0);
-	}
-	
-	SearchReplace* dia = new SearchReplace(this, doc, currItem);
-	if (wasModeEdit)
+	SearchReplace dia(this, doc);
+	if (doc->appMode == modeEdit)
 	{
-		QString selText = currItem->itemText.selectedText();
-		if (!selText.isEmpty())
-			dia->setSearchedText(selText);
+		PageItem *currItem = doc->m_Selection->itemAt(0);
+		dia.processCurrentSelection(currItem->itemText.selectedText());
 	}
-	dia->exec();
-	dia->disconnect();
-	delete dia;
-	//slotSelect();
+	dia.exec();
+	dia.disconnect();
 }
 
 /* call gimp and wait upon completion */
diff --git a/scribus/text/storytext.cpp b/scribus/text/storytext.cpp
index e59d3845e3d15dad5af527d62c615cf0ab94973a..2b35536cd6089cf848f21d54dd3e3ad64c1848b1 100644
--- a/scribus/text/storytext.cpp
+++ b/scribus/text/storytext.cpp
@@ -750,24 +750,19 @@ void StoryText::replaceSelection(const QString& newText)
 	int selLength = selectionLength();
 
 	int lengthDiff = newText.length() - selLength;
-	if (lengthDiff == 0)
-	{
-		for (int i = 0; i < selLength; ++i)
-			replaceChar(selStart + i, newText[i]);
-	}
-	else if (lengthDiff > 0)
+
+	if (lengthDiff > 0)
 	{
-		for (int i = 0; i < selLength; ++i)
-			replaceChar(selStart + i, newText[i]);
-		for (int i = selLength; i < newText.length(); ++i)
-			insertChars(selStart + i, newText.mid(i, 1), true);
+		// extend the selected string with its last char until the lenghts match
+		auto lastChar = selectedText().mid(selLength -1, 1);
+		insertChars(selStart + selLength -1, lastChar.repeated(lengthDiff), true);
 	}
-	else
-	{
-		for (int i = 0; i < newText.length(); ++i)
-			replaceChar(selStart + i, newText[i]);
+
+	for (int i = 0; i < newText.length(); ++i)
+		replaceChar(selStart + i, newText[i]);
+
+	if (lengthDiff < 0)
 		removeChars(selStart + newText.length(), -lengthDiff);
-	}
 
 	deselectAll();
 	if (newText.length() > 0)
diff --git a/scribus/ui/search.cpp b/scribus/ui/search.cpp
index 673486af5c09db03c6568a65c7232e61da4d02a3..4f3693392d7c1925b06bd1d6173e6b20c5b9c54e 100644
--- a/scribus/ui/search.cpp
+++ b/scribus/ui/search.cpp
@@ -6,19 +6,15 @@ for which a new license (GPL+exception) is in place.
 */
 #include "search.h"
 
-#include <QCheckBox>
-#include <QComboBox>
-#include <QGroupBox>
-#include <QHBoxLayout>
-#include <QGridLayout>
-#include <QVBoxLayout>
-#include <QLabel>
-#include <QLineEdit>
 #include <QListView>
-#include <QMessageBox>
-#include <QPixmap>
-#include <QPushButton>
+
+#include <QApplication>
+#include <QDesktopWidget>
 #include <QScopedValueRollback>
+#include <QCursor>
+#include <QSizePolicy>
+
+#include <QTimer>
 
 #include "appmodes.h"
 #include "canvas.h"
@@ -36,834 +32,811 @@ for which a new license (GPL+exception) is in place.
 #include "scrspinbox.h"
 #include "selection.h"
 #include "shadebutton.h"
+#include "styles/paragraphstyle.h"
+#include "styles/charstyle.h"
 #include "styleselect.h"
 #include "ui/storyeditor.h"
 #include "util.h"
 #include "util_text.h"
 
-SearchReplace::SearchReplace( QWidget* parent, ScribusDoc *doc, PageItem* ite, bool mode )
-	: QDialog( parent )
+SearchReplace::SearchReplace(QWidget* parent, ScribusDoc *doc)
+	: QDialog(parent)
 {
-	m_item = ite;
 	m_doc = doc;
-	m_itemMode = mode;
 
-	setModal(true);
-	setWindowTitle( tr( "Search/Replace" ) );
+	setupUi(this);
+
 	setWindowIcon(IconManager::instance().loadIcon("AppIcon.png"));
 
-	SearchReplaceLayout = new QVBoxLayout( this );
-	SearchReplaceLayout->setMargin(10);
-	SearchReplaceLayout->setSpacing(5);
-	SelLayout = new QHBoxLayout;
-	SelLayout->setMargin(0);
-	SelLayout->setSpacing(5);
-	Search = new QGroupBox( this );
-	Search->setTitle( tr( "Search for:" ) );
-	SearchLayout = new QGridLayout( Search );
-	SearchLayout->setMargin(5);
-	SearchLayout->setSpacing(2);
-	SearchLayout->setAlignment( Qt::AlignTop );
-	SText = new QCheckBox( Search );
-	SText->setText( tr( "Text" ) );
-	SearchLayout->addWidget( SText, 0, 0 );
-	SStyle = new QCheckBox( Search );
-	SStyle->setText( tr( "Style" ) );
-	SearchLayout->addWidget( SStyle, 1, 0 );
-	SAlign = new QCheckBox( Search );
-	SAlign->setText( tr( "Alignment" ) );
-	SearchLayout->addWidget( SAlign, 2, 0 );
-	SFont = new QCheckBox( Search );
-	SFont->setText( tr( "Font" ) );
-	SearchLayout->addWidget( SFont, 3, 0 );
-	SSize = new QCheckBox( Search );
-	SSize->setText( tr( "Font Size" ) );
-	SearchLayout->addWidget( SSize, 4, 0 );
-	SEffect = new QCheckBox( Search );
-	SEffect->setText( tr( "Font Effects" ) );
-	SearchLayout->addWidget( SEffect, 5, 0 );
-	SFill = new QCheckBox( Search);
-	SFill->setText( tr( "Fill Color" ) );
-	SearchLayout->addWidget( SFill, 6, 0 );
-	SFillS = new QCheckBox( Search );
-	SFillS->setText( tr( "Fill Shade" ) );
-	SearchLayout->addWidget( SFillS, 7, 0 );
-	SStroke = new QCheckBox( Search );
-	SStroke->setText( tr( "Stroke Color" ) );
-	SearchLayout->addWidget( SStroke, 8, 0 );
-	SStrokeS = new QCheckBox( Search );
-	SStrokeS->setText( tr( "Stroke Shade" ) );
-	SearchLayout->addWidget( SStrokeS, 9, 0 );
-	STextVal = new QLineEdit( Search );
-	STextVal->setEnabled(false);
-	SearchLayout->addWidget( STextVal, 0, 1 );
-	SStyleVal = new QComboBox( Search );
-	SStyleVal->setEditable(false);
-	for (int x = 0; x < doc->paragraphStyles().count(); ++x)
-		SStyleVal->addItem(doc->paragraphStyles()[x].name());
-	QListView *tmpView = dynamic_cast<QListView*>(SStyleVal->view()); Q_ASSERT(tmpView);
+	{
+		QSizePolicy sp = messageLabel->sizePolicy();
+		sp.setRetainSizeWhenHidden(true);
+		messageLabel->setSizePolicy(sp);
+	}
+	hideMessage();
+
+	for (int i = 0; i < doc->paragraphStyles().count(); ++i)
+	{
+		const auto name = doc->paragraphStyles()[i].name();
+		searchStyleComboBox->addItem(name);
+		replaceStyleComboBox->addItem(name);
+	}
+	searchStyleComboBox->setCurrentIndex(findParagraphStyle(doc, doc->currentStyle));
+	replaceStyleComboBox->setCurrentIndex(findParagraphStyle(doc, doc->currentStyle));
+	/*
+	QListView *tmpView = dynamic_cast<QListView*>(searchStyleComboBox->view()); Q_ASSERT(tmpView);
 	int tmpWidth = tmpView->sizeHintForColumn(0);
 	if (tmpWidth > 0)
 		tmpView->setMinimumWidth(tmpWidth + 24);
-	SStyleVal->setCurrentIndex(findParagraphStyle(doc, doc->currentStyle));
-	SStyleVal->setEnabled(false);
-	SearchLayout->addWidget( SStyleVal, 1, 1 );
-	SAlignVal = new QComboBox( Search );
-	SAlignVal->setEditable(false);
-	QString tmp_sty[] = { tr("Left"), tr("Center"), tr("Right"), tr("Block"), tr("Forced")};
-	size_t ar_sty = sizeof(tmp_sty) / sizeof(*tmp_sty);
-	for (uint a = 0; a < ar_sty; ++a)
-		SAlignVal->addItem( tmp_sty[a] );
-	tmpView = dynamic_cast<QListView*>(SAlignVal->view()); Q_ASSERT(tmpView);
+	tmpView = dynamic_cast<QListView*>(replaceStyleComboBox->view()); Q_ASSERT(tmpView);
 	tmpWidth = tmpView->sizeHintForColumn(0);
 	if (tmpWidth > 0)
 		tmpView->setMinimumWidth(tmpWidth + 24);
-	SAlignVal->setEnabled(false);
-	SearchLayout->addWidget( SAlignVal, 2, 1 );
-	SFontVal = new FontCombo(Search);
-	SFontVal->setMaximumSize(190, 30);
-	setCurrentComboItem(SFontVal, doc->currentStyle.charStyle().font().scName());
-	SFontVal->setEnabled(false);
-	SearchLayout->addWidget( SFontVal, 3, 1 );
-	SSizeVal = new ScrSpinBox( 0.5, 2048, Search, 0 );
-	SSizeVal->setValue( doc->currentStyle.charStyle().fontSize() / 10.0 );
-	SSizeVal->setEnabled(false);
-	SearchLayout->addWidget( SSizeVal, 4, 1 );
-	SEffVal = new StyleSelect( Search );
-	SEffVal->setStyle(0);
-	SEffVal->setEnabled(false);
-	SearchLayout->addWidget( SEffVal, 5, 1, Qt::AlignLeft );
-	SFillVal = new ColorCombo( Search );
-	SFillVal->setEditable(false);
-	SFillVal->setPixmapType(ColorCombo::fancyPixmaps);
-	SFillVal->setColors(doc->PageColors, true);
-	SFillVal->setMinimumWidth(SFillVal->view()->maximumViewportSize().width() + 24);
-	setCurrentComboItem(SFillVal, doc->currentStyle.charStyle().fillColor());
-	SFillVal->setEnabled(false);
-	SearchLayout->addWidget( SFillVal, 6, 1 );
-	SFillSVal = new ShadeButton(Search);
-	SFillSVal->setEnabled(false);
-	SearchLayout->addWidget( SFillSVal, 7, 1, Qt::AlignLeft );
-	SStrokeVal = new ColorCombo( Search );
-	SStrokeVal->setEditable(false);
-	SStrokeVal->setPixmapType(ColorCombo::fancyPixmaps);
-	SStrokeVal->setColors(doc->PageColors, true);
-	SStrokeVal->view()->setMinimumWidth(SStrokeVal->view()->maximumViewportSize().width() + 24);
-	setCurrentComboItem(SStrokeVal, doc->currentStyle.charStyle().strokeColor());
-	SStrokeVal->setEnabled(false);
-	SearchLayout->addWidget( SStrokeVal, 8, 1 );
-	SStrokeSVal =  new ShadeButton(Search);
-	SStrokeSVal->setEnabled(false);
-	SearchLayout->addWidget( SStrokeSVal, 9, 1, Qt::AlignLeft );
-	SelLayout->addWidget( Search );
-
-	Replace = new QGroupBox( this );
-	Replace->setTitle( tr( "Replace with:" ) );
-	ReplaceLayout = new QGridLayout( Replace );
-	ReplaceLayout->setSpacing( 2 );
-	ReplaceLayout->setMargin( 5 );
-	ReplaceLayout->setAlignment( Qt::AlignTop );
-	RText = new QCheckBox( Replace );
-	RText->setText( tr( "Text" ) );
-	ReplaceLayout->addWidget( RText, 0, 0 );
-	RStyle = new QCheckBox( Replace );
-	RStyle->setText( tr( "Style" ) );
-	ReplaceLayout->addWidget( RStyle, 1, 0 );
-	RAlign = new QCheckBox( Replace );
-	RAlign->setText( tr( "Alignment" ) );
-	ReplaceLayout->addWidget( RAlign, 2, 0 );
-	RFont = new QCheckBox( Replace );
-	RFont->setText( tr( "Font" ) );
-	ReplaceLayout->addWidget( RFont, 3, 0 );
-	RSize = new QCheckBox( Replace );
-	RSize->setText( tr( "Font Size" ) );
-	ReplaceLayout->addWidget( RSize, 4, 0 );
-	REffect = new QCheckBox( Replace );
-	REffect->setText( tr( "Font Effects" ) );
-	ReplaceLayout->addWidget( REffect, 5, 0 );
-	RFill = new QCheckBox( Replace );
-	RFill->setText( tr( "Fill Color" ) );
-	ReplaceLayout->addWidget( RFill, 6, 0 );
-	RFillS = new QCheckBox( Replace );
-	RFillS->setText( tr( "Fill Shade" ) );
-	ReplaceLayout->addWidget( RFillS, 7, 0 );
-	RStroke = new QCheckBox( Replace );
-	RStroke->setText( tr( "Stroke Color" ) );
-	ReplaceLayout->addWidget( RStroke, 8, 0 );
-	RStrokeS = new QCheckBox( Replace );
-	RStrokeS->setText( tr( "Stroke Shade" ) );
-	ReplaceLayout->addWidget( RStrokeS, 9, 0 );
-	RTextVal = new QLineEdit( Replace );
-	RTextVal->setEnabled(false);
-	ReplaceLayout->addWidget( RTextVal, 0, 1 );
-	RStyleVal = new QComboBox( Replace );
-	RStyleVal->setEditable(false);
-	for (int x = 0; x < doc->paragraphStyles().count(); ++x)
-		RStyleVal->addItem(doc->paragraphStyles()[x].name());
-	tmpView = dynamic_cast<QListView*>(RStyleVal->view()); Q_ASSERT(tmpView);
+	*/
+
+	{
+		QStringList alignment{tr("Left"), tr("Center"), tr("Right"), tr("Block"), tr("Forced")};
+		searchAlignmentComboBox->addItems(alignment);
+		replaceAlignmentComboBox->addItems(alignment);
+	}
+
+	/*
+	tmpView = dynamic_cast<QListView*>(searchAlignmentComboBox->view()); Q_ASSERT(tmpView);
 	tmpWidth = tmpView->sizeHintForColumn(0);
 	if (tmpWidth > 0)
 		tmpView->setMinimumWidth(tmpWidth + 24);
-	RStyleVal->setCurrentIndex(findParagraphStyle(doc, doc->currentStyle));
-	RStyleVal->setEnabled(false);
-	ReplaceLayout->addWidget( RStyleVal, 1, 1 );
-	RAlignVal = new QComboBox( Replace );
-	RAlignVal->setEditable(false);
-	for (uint a = 0; a < ar_sty; ++a)
-		RAlignVal->addItem(tmp_sty[a]);
-	tmpView = dynamic_cast<QListView*>(RAlignVal->view()); Q_ASSERT(tmpView);
+	tmpView = dynamic_cast<QListView*>(replaceAlignmentComboBox->view()); Q_ASSERT(tmpView);
 	tmpWidth = tmpView->sizeHintForColumn(0);
 	if (tmpWidth > 0)
 		tmpView->setMinimumWidth(tmpWidth + 24);
-	RAlignVal->setEnabled(false);
-	ReplaceLayout->addWidget( RAlignVal, 2, 1 );
-	RFontVal = new FontCombo(Replace);
-	RFontVal->setMaximumSize(190, 30);
-	setCurrentComboItem(RFontVal, doc->currentStyle.charStyle().font().scName());
-	RFontVal->setEnabled(false);
-	ReplaceLayout->addWidget( RFontVal, 3, 1 );
-	RSizeVal = new ScrSpinBox( 0.5, 2048, Replace, 0 );
-	RSizeVal->setValue( doc->currentStyle.charStyle().fontSize() / 10.0 );
-	RSizeVal->setEnabled(false);
-	ReplaceLayout->addWidget( RSizeVal, 4, 1 );
-	REffVal = new StyleSelect( Replace );
-	REffVal->setStyle(0);
-	REffVal->setEnabled(false);
-	ReplaceLayout->addWidget( REffVal, 5, 1, Qt::AlignLeft );
-	RFillVal = new ColorCombo( true, Replace );
-	RFillVal->setEditable(false);
-	RFillVal->setPixmapType(ColorCombo::fancyPixmaps);
-	RFillVal->setColors(doc->PageColors, true);
-	RFillVal->view()->setMinimumWidth(RFillVal->view()->maximumViewportSize().width() + 24);
-	setCurrentComboItem(RFillVal, doc->currentStyle.charStyle().fillColor());
-	RFillVal->setEnabled(false);
-	ReplaceLayout->addWidget( RFillVal, 6, 1 );
-	RFillSVal = new ShadeButton(Replace);
-	RFillSVal->setEnabled(false);
-	ReplaceLayout->addWidget( RFillSVal, 7, 1, Qt::AlignLeft );
-	RStrokeVal = new ColorCombo( true, Replace );
-	RStrokeVal->setEditable(false);
-	RStrokeVal->setPixmapType(ColorCombo::fancyPixmaps);
-	RStrokeVal->setColors(doc->PageColors, true);
-	RStrokeVal->view()->setMinimumWidth(RStrokeVal->view()->maximumViewportSize().width() + 24);
-	setCurrentComboItem(RStrokeVal, doc->currentStyle.charStyle().strokeColor());
-	RStrokeVal->setEnabled(false);
-	ReplaceLayout->addWidget( RStrokeVal, 8, 1 );
-	RStrokeSVal = new ShadeButton(Replace);;
-	RStrokeSVal->setEnabled(false);
-	ReplaceLayout->addWidget( RStrokeSVal, 9, 1, Qt::AlignLeft );
-	SelLayout->addWidget( Replace );
-	SearchReplaceLayout->addLayout( SelLayout );
-
-	OptsLayout = new QHBoxLayout;
-	OptsLayout->setSpacing( 5 );
-	OptsLayout->setMargin( 0 );
-	Word = new QCheckBox( tr( "&Whole Word" ), this );
-	if (mode)
-		Word->setEnabled(false);
-	OptsLayout->addWidget( Word );
-	CaseIgnore = new QCheckBox( tr( "&Ignore Case, Diacritics and Kashida" ), this );
-	if (mode)
-		CaseIgnore->setEnabled(false);
-	OptsLayout->addWidget( CaseIgnore );
-	SearchReplaceLayout->addLayout( OptsLayout );
-
-	ButtonsLayout = new QHBoxLayout;
-	ButtonsLayout->setSpacing( 5 );
-	ButtonsLayout->setMargin( 0 );
-	DoSearch = new QPushButton( tr( "&Search" ), this );
-	DoSearch->setDefault( true );
-	ButtonsLayout->addWidget( DoSearch );
-	DoReplace = new QPushButton( tr( "&Replace" ), this );
-	DoReplace->setEnabled(false);
-	ButtonsLayout->addWidget( DoReplace );
-	AllReplace = new QPushButton( tr( "Replace &All" ), this );
-	AllReplace->setEnabled(false);
-	ButtonsLayout->addWidget( AllReplace );
-	clearButton = new QPushButton( tr("C&lear"), this);
-	ButtonsLayout->addWidget(clearButton);
-	Leave = new QPushButton( tr( "&Close" ), this );
-	ButtonsLayout->addWidget( Leave );
-	SearchReplaceLayout->addLayout( ButtonsLayout );
-
-	resize(minimumSizeHint());
-
- // signals and slots connections
-	connect( Leave, SIGNAL( clicked() ), this, SLOT( writePrefs() ) );
-	connect( DoSearch, SIGNAL( clicked() ), this, SLOT( slotSearch() ) );
-	connect( DoReplace, SIGNAL( clicked() ), this, SLOT( slotReplace() ) );
-	connect( AllReplace, SIGNAL( clicked() ), this, SLOT( slotReplaceAll() ) );
-	connect( STextVal, SIGNAL( textChanged(QString) ), this, SLOT( updateSearchButtonState() ) );
-	connect( SText, SIGNAL( clicked() ), this, SLOT( enableTxSearch() ) );
-	connect( SStyle, SIGNAL( clicked() ), this, SLOT( enableStyleSearch() ) );
-	connect( SAlign, SIGNAL( clicked() ), this, SLOT( enableAlignSearch() ) );
-	connect( SFont, SIGNAL( clicked() ), this, SLOT( enableFontSearch() ) );
-	connect( SSize, SIGNAL( clicked() ), this, SLOT( enableSizeSearch() ) );
-	connect( SEffect, SIGNAL( clicked() ), this, SLOT( enableEffSearch() ) );
-	connect( SFill, SIGNAL( clicked() ), this, SLOT( enableFillSearch() ) );
-	connect( SFillS, SIGNAL( clicked() ), this, SLOT( enableFillSSearch() ) );
-	connect( SStrokeS, SIGNAL( clicked() ), this, SLOT( enableStrokeSSearch() ) );
-	connect( SStroke, SIGNAL( clicked() ), this, SLOT( enableStrokeSearch() ) );
-	connect( RText, SIGNAL( clicked() ), this, SLOT( enableTxReplace() ) );
-	connect( RStyle, SIGNAL( clicked() ), this, SLOT( enableStyleReplace() ) );
-	connect( RAlign, SIGNAL( clicked() ), this, SLOT( enableAlignReplace() ) );
-	connect( RFont, SIGNAL( clicked() ), this, SLOT( enableFontReplace() ) );
-	connect( RSize, SIGNAL( clicked() ), this, SLOT( enableSizeReplace() ) );
-	connect( REffect, SIGNAL( clicked() ), this, SLOT( enableEffReplace() ) );
-	connect( RFill, SIGNAL( clicked() ), this, SLOT( enableFillReplace() ) );
-	connect( RStroke, SIGNAL( clicked() ), this, SLOT( enableStrokeReplace() ) );
-	connect( RFillS, SIGNAL( clicked() ), this, SLOT( enableFillSReplace() ) );
-	connect( RStrokeS, SIGNAL( clicked() ), this, SLOT( enableStrokeSReplace() ) );
-	connect(clearButton, SIGNAL(clicked()), this, SLOT(clear()));
+	*/
+	/*
+	searchFontComboBox->setMaximumSize(190, 30);
+	setCurrentComboItem(searchFontComboBox, doc->currentStyle.charStyle().font().scName());
+	searchFontSizeSpinBox->setValue( doc->currentStyle.charStyle().fontSize() / 10.0 );
+	replaceFontComboBox->setMaximumSize(190, 30);
+	setCurrentComboItem(replaceFontComboBox, doc->currentStyle.charStyle().font().scName());
+	replaceFontSizeSpinBox->setValue( doc->currentStyle.charStyle().fontSize() / 10.0 );
+	*/
+
+	searchFillColorComboBox->setPixmapType(ColorCombo::fancyPixmaps);
+	searchFillColorComboBox->setColors(doc->PageColors, true);
+	searchFillColorComboBox->setMinimumWidth(searchFillColorComboBox->view()->maximumViewportSize().width() + 24);
+	setCurrentComboItem(searchFillColorComboBox, doc->currentStyle.charStyle().fillColor());
+
+	searchStrokeColorComboBox->setPixmapType(ColorCombo::fancyPixmaps);
+	searchStrokeColorComboBox->setColors(doc->PageColors, true);
+	searchStrokeColorComboBox->view()->setMinimumWidth(searchStrokeColorComboBox->view()->maximumViewportSize().width() + 24);
+	setCurrentComboItem(searchStrokeColorComboBox, doc->currentStyle.charStyle().strokeColor());
+
+	replaceFillColorComboBox->setPixmapType(ColorCombo::fancyPixmaps);
+	replaceFillColorComboBox->setColors(doc->PageColors, true);
+	replaceFillColorComboBox->view()->setMinimumWidth(replaceFillColorComboBox->view()->maximumViewportSize().width() + 24);
+	setCurrentComboItem(replaceFillColorComboBox, doc->currentStyle.charStyle().fillColor());
+	replaceStrokeColorComboBox->setPixmapType(ColorCombo::fancyPixmaps);
+	replaceStrokeColorComboBox->setColors(doc->PageColors, true);
+	replaceStrokeColorComboBox->view()->setMinimumWidth(replaceStrokeColorComboBox->view()->maximumViewportSize().width() + 24);
+	setCurrentComboItem(replaceStrokeColorComboBox, doc->currentStyle.charStyle().strokeColor());
+
+	connect(collapseButton, &QPushButton::clicked, this, &SearchReplace::slotCollapseFormat);
+
+	connect(closeButton, &QPushButton::clicked, this, &SearchReplace::accept);
+	connect(searchButton, &QPushButton::clicked, this, &SearchReplace::slotSearch);
+	connect(replaceButton, &QPushButton::clicked, this, &SearchReplace::slotReplace);
+	connect(clearButton, &QPushButton::clicked, this, &SearchReplace::clear);
+	connect(replaceAllButton, &QPushButton::clicked, this, &SearchReplace::slotReplaceAll);
+
+	connect(searchStyleCheckBox, &QCheckBox::clicked, [this](){enableStyleSearch(); updateButtonState();});
+	connect(searchAlignmentCheckBox, &QCheckBox::clicked, [this](){enableAlignmentSearch(); updateButtonState();});
+	connect(searchFontCheckBox, &QCheckBox::clicked, [this](){enableFontSearch(); updateButtonState();});
+	connect(searchFontSizeCheckBox, &QCheckBox::clicked, [this](){enableFontSizeSearch(); updateButtonState();});
+	connect(searchFontEffectsCheckBox, &QCheckBox::clicked, [this](){enableFontEffectsSearch(); updateButtonState();});
+	connect(searchFillColorCheckBox, &QCheckBox::clicked, [this](){enableFillColorSearch(); updateButtonState();});
+	connect(searchFillShadeCheckBox, &QCheckBox::clicked, [this](){enableFillShadeSearch(); updateButtonState();});
+	connect(searchStrokeColorCheckBox, &QCheckBox::clicked, [this](){enableStrokeColorSearch(); updateButtonState();});
+	connect(searchStrokeShadeCheckBox, &QCheckBox::clicked, [this](){enableStrokeShadeSearch(); updateButtonState();});
+
+	connect(searchTextValue, &QLineEdit::textChanged, [this]() {enableTextSearch(); updateButtonState();});
+
+	connect(replaceStyleCheckBox, &QCheckBox::clicked, this, &SearchReplace::enableStyleReplace);
+	connect(replaceAlignmentCheckBox, &QCheckBox::clicked, this, &SearchReplace::enableAlignmentReplace);
+	connect(replaceFontCheckBox, &QCheckBox::clicked, this, &SearchReplace::enableFontReplace);
+	connect(replaceFontSizeCheckBox, &QCheckBox::clicked, this, &SearchReplace::enableFontSizeReplace);
+	connect(replaceFontEffectsCheckBox, &QCheckBox::clicked, this, &SearchReplace::enableFontEffectsReplace);
+	connect(replaceFillColorCheckBox, &QCheckBox::clicked, this, &SearchReplace::enableFillColorReplace);
+	connect(replaceFillShadeCheckBox, &QCheckBox::clicked, this, &SearchReplace::enableFillShadeReplace);
+	connect(replaceStrokeColorCheckBox, &QCheckBox::clicked, this, &SearchReplace::enableStrokeColorReplace);
+	connect(replaceStrokeShadeCheckBox, &QCheckBox::clicked, this, &SearchReplace::enableStrokeShadeReplace);
+
+	connect(replaceTextValue, &QLineEdit::textChanged, this, &SearchReplace::updateButtonState);
 
 	//tooltips
-	DoSearch->setToolTip( tr( "Search for text or formatting in the current text" ) );
-	DoReplace->setToolTip( tr( "Replace the searched for formatting with the replacement values" ) );
-	AllReplace->setToolTip( tr( "Replace all found instances" ) );
+	collapseButton->setToolTip( tr( "Collapse or expand the formatting options" ) );
+	searchButton->setToolTip( tr( "Search for text or formatting in the current text" ) );
+	replaceButton->setToolTip( tr( "Replace the searched for formatting with the replacement values" ) );
+	replaceAllButton->setToolTip( tr( "Replace all found instances" ) );
 	clearButton->setToolTip( tr( "Clear all search and replace options" ) );
-	Leave->setToolTip( tr( "Close search and replace" ) );
-
- // tab order
-	setTabOrder( SText, SStyle );
-	setTabOrder( SStyle, SFont );
-	setTabOrder( SFont, SSize );
-	setTabOrder( SSize, SEffect );
-	setTabOrder( SEffect, SFill );
-	setTabOrder( SFill, SStroke );
-	setTabOrder( SStroke, STextVal );
-	setTabOrder( STextVal, SStyleVal );
-	setTabOrder( SStyleVal, SAlignVal );
-	setTabOrder( SAlignVal, SFontVal );
-	setTabOrder( SFontVal, SSizeVal );
-	setTabOrder( SSizeVal, SEffVal );
-	setTabOrder( SEffVal, SFillVal );
-	setTabOrder( SFillVal, SStrokeVal );
-	setTabOrder( SStrokeVal, RText );
-	setTabOrder( RText, RStyle );
-	setTabOrder( RStyle, RFont );
-	setTabOrder( RFont, RSize );
-	setTabOrder( RSize, REffect );
-	setTabOrder( REffect, RFill );
-	setTabOrder( RFill, RStroke );
-	setTabOrder( RStroke, RTextVal );
-	setTabOrder( RTextVal, RStyleVal );
-	setTabOrder( RStyleVal, RAlignVal );
-	setTabOrder( RAlignVal, RFontVal );
-	setTabOrder( RFontVal, RSizeVal );
-	setTabOrder( RSizeVal, REffVal );
-	setTabOrder( REffVal, RFillVal );
-	setTabOrder( RFillVal, RStrokeVal );
-	setTabOrder( RStrokeVal, Word );
-	setTabOrder( Word, CaseIgnore );
-	setTabOrder( CaseIgnore, DoSearch );
-	setTabOrder( DoSearch, DoReplace );
-	setTabOrder( DoReplace, AllReplace );
-	setTabOrder( AllReplace, Leave );
+	closeButton->setToolTip( tr( "Close search and replace" ) );
 
 	m_prefs = PrefsManager::instance().prefsFile->getContext("SearchReplace");
 	readPrefs();
+
+	collapseFormat();
 }
 
-void SearchReplace::slotSearch()
+void SearchReplace::processCurrentSelection(QString selection)
 {
-	doSearch();
+	selection = selection.trimmed();
+	if (!selection.isEmpty())
+	{
+		// \r newlines from the frames and QChar::ParagraphSeparator for the story editor
+		if (selection.contains("\r") || selection.contains(QChar::ParagraphSeparator))
+			// TODO: add a "Current selection only" checkbox
+			/* enableSelectionSearch()*/ ;
+		else
+			searchTextValue->setText(selection);
+	}
+}
 
-	if (m_itemMode)
-		m_item->update();
-	if (!m_found)
-		showNotFoundMessage();
+QPair<int, int> SearchReplace::cursorPosition()
+{
+	return {m_selectionStart, m_selectionEnd};
 }
 
-void SearchReplace::doSearch()
+void SearchReplace::slotSearch()
 {
-	int maxChar = m_item->itemText.length() - 1;
-	DoReplace->setEnabled(false);
-	AllReplace->setEnabled(false);
-	if (m_itemMode)
+	if (m_storyEditorMode)
+		searchInStoryEditor();
+	else
+		searchOnCanvas();
+}
+
+void SearchReplace::searchInStoryEditor()
+{
+	hideMessage();
+
+	auto options = getSearchOptions();
+
+	SEditor* storyTextEdit = m_doc->scMW()->CurrStED->Editor;
+	StoryText& storyText = storyTextEdit->StyledText;
+	QTextCursor cursor = storyTextEdit->textCursor();
+	// TODO: as soon as we have them, use optional and const auto [start, end] = ...
+	auto pos = searchStory(storyText, cursor.position(), storyText.length(), options);
+
+	if (pos.first < 0)
 	{
-		m_item->itemText.deselectAll();
-		m_item->HasSel = false;
+		showMessage(tr("Reached the end of the selection."));
+		pos = searchStory(storyText, 0, storyText.length(), options);
 	}
 
-	QString fCol = "";
-	QString sCol = "";
-	QString sFont = "";
-	QString sText = "";
-	int sStyle = 0;
-	int sAlign = 0;
-	int sSize = 0;
-	int sEff = 0;
-	int sFillSh = 100;
-	int sStrokeSh = 100;
-	bool searchForReplace = false;
-	bool rep = false;
-	bool found = true;
-
-	m_found = true;
-
-	if ((RFill->isChecked()) || (RStroke->isChecked()) || (RStyle->isChecked()) || (RFont->isChecked())
-		|| (RStrokeS->isChecked()) || (RFillS->isChecked()) || (RSize->isChecked()) || (RText->isChecked())
-		|| (REffect->isChecked())  || (RAlign->isChecked()))
-		rep = true;
-	if ((SFill->isChecked()) || (SStroke->isChecked()) || (SStyle->isChecked()) || (SFont->isChecked())
-			|| (SStrokeS->isChecked()) || (SFillS->isChecked()) || (SSize->isChecked()) || (SText->isChecked())
-			|| (SEffect->isChecked())  || (SAlign->isChecked()))
-		searchForReplace = true;
-	if (SText->isChecked())
-		sText = STextVal->text();
-	if (CaseIgnore->isChecked())
-		sText = sText.toLower();
-	if (SEffect->isChecked())
-		sEff = SEffVal->getStyle();
-	if (SFill->isChecked())
-		fCol = SFillVal->currentText();
-	if (SFillS->isChecked())
-		sFillSh = SFillSVal->getValue();
-	if (SStroke->isChecked())
-		sCol = SStrokeVal->currentText();
-	if (SStrokeS->isChecked())
-		sStrokeSh = SStrokeSVal->getValue();
-	if (SFont->isChecked())
-		sFont = SFontVal->currentText();
-	if (SStyle->isChecked())
-		sStyle = SStyleVal->currentIndex();
-	if (SAlign->isChecked())
-		sAlign = SAlignVal->currentIndex();
-	if (SSize->isChecked())
-		sSize = qRound(SSizeVal->value() * 10);
-	if (sText.length() > 0)
-		found = false;
-	
-	int a, textLen(0);
-	int cursorPos = m_item->itemText.cursorPosition();
-	m_replStart = cursorPos;
+	if (pos.first >= 0)
+	{
+		m_selectionStart = pos.first;
+		m_selectionEnd = pos.second + 1;
+		cursor.setPosition(pos.first);
+		cursor.setPosition(pos.second + 1, QTextCursor::KeepAnchor);
+		storyTextEdit->setTextCursor(cursor);
+	}
+	else
+		showMessage(tr("No match found."));
+}
 
-	if (m_itemMode)
+void SearchReplace::searchOnCanvas()
+{
+	hideMessage();
+
+	auto options = getSearchOptions();
+
+	initCanvasSelection();
+
+	PageItem* pageItem;
+	int itemsToBeProcessed = m_pageItems.size();
+	bool matchFound{false};
+
+	while ((pageItem = currentPageItem()))
 	{
-		Qt::CaseSensitivity cs = Qt::CaseSensitive;
-		if (CaseIgnore->isChecked())
-			cs = Qt::CaseInsensitive;
+		if (itemsToBeProcessed < 0)
+			break;
+		--itemsToBeProcessed;
 
-		for (a = cursorPos; a < m_item->itemText.length(); ++a)
+		StoryText& storyText = pageItem->itemText;
+
+		if (m_doc->appMode != modeEdit)
 		{
-			found = true;
-			if (SText->isChecked())
-			{
-				a = m_item->itemText.indexOf(sText, a, cs, &textLen);
-				found = (a >= 0);
-				if (!found) break;
-
-				if (Word->isChecked() && (a > 0) && m_item->itemText.text(a - 1).isLetterOrNumber())
-					found = false;
-				if (Word->isChecked())
-				{
-					int lastChar = qMin(a + textLen, maxChar);
-					found = ((lastChar == maxChar) || !m_item->itemText.text(lastChar).isLetterOrNumber());
-				}
-				if (!found) continue;
-			}
-			if (SSize->isChecked())
-			{
-				if (m_item->itemText.charStyle(a).fontSize() != sSize)
-					found = false;
-			}
-			if (SFont->isChecked())
-			{
-				if (m_item->itemText.charStyle(a).font().scName() != sFont)
-					found = false;
-			}
-
-			if (SStyle->isChecked())
-			{
-				if (m_item->itemText.paragraphStyle(a).parent() != m_doc->paragraphStyles()[sStyle].name())
-					found = false;
-			}
-
-			if (SAlign->isChecked())
-			{
-				if (m_item->itemText.paragraphStyle(a).alignment() != sAlign)
-					found = false;
-			}
-			if (SStroke->isChecked())
-			{
-				if (m_item->itemText.charStyle(a).strokeColor() != sCol)
-					found = false;
-			}
-			if (SStrokeS->isChecked())
-			{
-				if (m_item->itemText.charStyle(a).strokeShade() != sStrokeSh)
-					found = false;
-			}
-			if (SFillS->isChecked())
-			{
-				if (m_item->itemText.charStyle(a).fillShade() != sFillSh)
-					found = false;
-			}
-			if (SEffect->isChecked())
-			{
-				if ((m_item->itemText.charStyle(a).effects() & ScStyle_UserStyles) != sEff)
-					found = false;
-			}
-			if (SFill->isChecked())
-			{
-				if (m_item->itemText.charStyle(a).fillColor() != fCol)
-					found = false;
-			}
-			if (found && searchForReplace)
-			{
-				m_item->itemText.select(a, textLen);
-				m_item->HasSel = true;
-				if (rep)
-				{
-					DoReplace->setEnabled(true);
-					AllReplace->setEnabled(true);
-				}
-				m_item->itemText.setCursorPosition(a + textLen);
-
-				if (!SText->isChecked())
-					break;
-
-				m_replStart = a;
-				break;
-			}
-			if (SText->isChecked())
-			{
-				for (int xx = m_replStart; xx < a + 1; ++xx)
-					m_item->itemText.select(qMin(xx, maxChar), 1, false);
-				m_item->HasSel = false;
-			}
+			m_doc->view()->requestMode(modeEdit);
+			pageItem->itemText.setCursorPosition(0);
 		}
-		if (found && !m_replacingAll)
+
+		auto pos = searchStory(storyText, storyText.cursorPosition(), storyText.length(), options);
+
+		if (pos.first < 0)
 		{
-			QPointF textCanvasPos;
-			int foundPos = m_item->itemText.cursorPosition();
-			if (m_item->itemText.selectionLength() > 0)
-				foundPos = m_item->itemText.startOfSelection();
-			bool cPosFound = m_doc->textCanvasPosition(m_item, foundPos, textCanvasPos);
-			if (cPosFound)
-			{
-				QRectF updateRect;
-				PageItem* textItem = m_item->frameOfChar(foundPos);
-				if (textItem != m_item)
-				{
-					updateRect = m_item->getVisualBoundingRect();
-					updateRect = updateRect.united(textItem->getVisualBoundingRect());
-					int selLength = m_item->itemText.selectionLength();
-					m_item->itemText.deselectAll();
-					m_item->HasSel = false;
-					m_doc->m_Selection->delaySignalsOn();
-					m_doc->m_Selection->removeItem(m_item);
-					m_doc->m_Selection->addItem(textItem);
-					m_item = textItem;
-					m_item->itemText.deselectAll();
-					if (selLength > 0)
-						m_item->itemText.select(foundPos, selLength);
-					m_item->itemText.setCursorPosition(foundPos + selLength);
-					m_item->HasSel = true;
-					m_doc->m_Selection->delaySignalsOff();
-				}
-				QRectF visibleCanvasRect = m_doc->view()->visibleCanvasRect();
-				if (!visibleCanvasRect.contains(textCanvasPos))
-					m_doc->view()->setCanvasCenterPos(textCanvasPos.x(), textCanvasPos.y());
-				if (!updateRect.isEmpty())
-					m_doc->regionsChanged()->update(updateRect.adjusted(-10.0, -10.0, 10.0, 10.0));
-			}
+			nextPageItem();
+			pageItem = currentPageItem();
+
+			if (m_endReached)
+				showMessage(tr("Reached the end of the selection."));
+
+			pos = searchStory(storyText, storyText.cursorPosition(), storyText.length(), options);
 		}
-		if ((!found) || (a == m_item->itemText.length()))
+
+		if (pos.first >= 0)
 		{
-			m_doc->DoDrawing = true;
-			m_item->update();
-			DoReplace->setEnabled(false);
-			AllReplace->setEnabled(false);
-			m_item->itemText.setCursorPosition(0);
-			m_found = false;
+			storyText.select(pos.first, pos.second + 1 - pos.first);
+			storyText.setCursorPosition(pos.second + 1);
+			matchFound = true;
+
+			break;
 		}
 	}
-	else if (m_doc->scMW()->CurrStED != nullptr)
+	updatePageItemSelection(pageItem);
+
+	if (!matchFound)
+		showMessage(tr("No match found."));
+}
+
+void SearchReplace::initCanvasSelection()
+{
+	if (m_doc->Items->count() == 0)
+		// the document has no items: do nothing
+		return;
+
+	if (m_doc->m_Selection->count() == 0)
 	{
-		found = false;
-		SEditor* storyTextEdit = m_doc->scMW()->CurrStED->Editor;
-		if (storyTextEdit->StyledText.length() == 0)
-			return;
+		// TODO: search the whole document or the whole page?
+		readAllPageItems();
+		selectPageItem(m_pageItems.at(0));
+	}
+	else
+		readSelectedPageItems();
+}
 
-		QTextCursor cursor = storyTextEdit->textCursor();
-		int position  = cursor.position();
-		StoryText& styledText = storyTextEdit->StyledText;
-		int firstChar = -1, lastChar = styledText.length();
-		if (SText->isChecked())
-		{
-			Qt::CaseSensitivity cs = Qt::CaseSensitive;
-			if (CaseIgnore->isChecked())
-				cs = Qt::CaseInsensitive;
-
-			for (int i = position; i < styledText.length(); ++i)
-			{
-				i = styledText.indexOf(sText, i, cs, &textLen);
-				found = (i >= 0);
-				if (!found)
-					break;
-
-				if (Word->isChecked() && (i > 0) && styledText.text(i - 1).isLetterOrNumber())
-					found = false;
-				if (Word->isChecked())
-				{
-					int lastChar = qMin(i + textLen, maxChar);
-					found = ((lastChar == maxChar) || !styledText.text(lastChar).isLetterOrNumber());
-				}
-				if (!found) continue;
-
-				int selStart = i;
-				for (int ap = 0; ap < textLen; ++ap)
-				{
-					const ParagraphStyle& parStyle = storyTextEdit->StyledText.paragraphStyle(selStart + ap);
-					const CharStyle& charStyle = storyTextEdit->StyledText.charStyle(selStart + ap);
-					if (SSize->isChecked() && (charStyle.fontSize() != sSize))
-						found = false;
-					if (SFont->isChecked() && (charStyle.font().scName() != sFont))
-						found = false;
-					if (SStyle->isChecked() && (parStyle.parent() != m_doc->paragraphStyles()[sStyle].name()))
-						found = false;
-					if (SAlign->isChecked() && (parStyle.alignment() != sAlign))
-						found = false;
-					if (SFill->isChecked() && (charStyle.fillColor() != fCol))
-						found = false;
-					if (SStroke->isChecked() && (charStyle.strokeColor() != sCol))
-						found = false;
-					if (SStrokeS->isChecked() && (charStyle.strokeShade() != sStrokeSh))
-						found = false;
-					if (SFillS->isChecked() && (charStyle.fillShade() != sFillSh))
-						found = false;
-					if (SEffect->isChecked() && ((charStyle.effects() & ScStyle_UserStyles) != sEff))
-						found = false;
-				}
-
-				if (found)
-				{
-					firstChar = i;
-					lastChar = i + textLen;
-					break;
-				}
-			}
-		}
-		else
+void SearchReplace::readAllPageItems()
+{
+	if (m_pageItems.size() > 0)
+		return;
+
+	QMap<int, std::vector<PageItem*>> pages;
+	PageItem* pageItem;
+	for (int i = 0; i < m_doc->Items->count(); i++)
+	{
+		pageItem = m_doc->Items->at(i);
+
+		if (!(pageItem->isTextFrame()))
+			continue;
+
+		// text chains are automatically searched until the end
+		if (!(pageItem->firstInChain()))
+			continue;
+
+		pages[pageItem->OwnPage].push_back(pageItem);
+	}
+
+	// TODO: add it as compareCoordinates to util.h, or pageItem.h... or implement the < operator for PageItem.
+	auto sortCoordinates = [](const PageItem* a, const PageItem* b) {
+		return a->yPos() == b->yPos() ?
+				(a->xPos() < b->xPos()) :
+				(a->yPos() < b->yPos());
+	};
+	for (int i = m_doc->currentPageNumber(); i < m_doc->DocPages.count(); i++)
+	{
+		auto pageItems = pages.value(i, {});
+		if (pageItems.empty())
+			continue;
+
+		std::sort(pageItems.begin(), pageItems.end(), sortCoordinates);
+
+		m_pageItems.insert(m_pageItems.end(), pageItems.begin(), pageItems.end() );
+	}
+
+	for (int i = 0; i < m_doc->currentPageNumber(); i++)
+	{
+		auto pageItems = pages.value(i, {});
+		if (pageItems.empty())
+			continue;
+
+		std::sort(pageItems.begin(), pageItems.end(), sortCoordinates);
+
+		m_pageItems.insert(m_pageItems.end(), pageItems.begin(), pageItems.end() );
+	}
+}
+
+// TODO: if only one frame is selected: add an option to search for the whole story (automatically enabled; currently it just follows the story)
+void SearchReplace::readSelectedPageItems()
+{
+	// TODO: sort by position?
+	// only read once
+	if (m_pageItems.size() > 0)
+		return;
+
+	m_currentPageItem = 0;
+
+	if (m_doc->m_Selection->count() == 0)
+		return;
+
+	for (int i = 0 ; i < m_doc->m_Selection->count(); i++)
+	{
+		auto pageItem = m_doc->m_Selection->itemAt(i);
+
+		if (!(pageItem->isTextFrame()))
+			continue;
+
+		m_pageItems.push_back(pageItem);
+	}
+}
+
+// TODO: as soon as available, use std::optional
+PageItem* SearchReplace::currentPageItem()
+{
+	return m_pageItems.at(m_currentPageItem);
+}
+
+void SearchReplace::nextPageItem()
+{
+	m_endReached = false;
+	++m_currentPageItem;
+	if (m_currentPageItem >= m_pageItems.size())
+	{
+		m_currentPageItem = 0;
+		m_endReached = true;
+	}
+
+	auto pageItem = m_pageItems.at(m_currentPageItem);
+
+	if (!m_endReached && pageItem == m_doc->m_Selection->itemAt(0))
+		return;
+
+	selectPageItem(pageItem);
+}
+
+void SearchReplace::selectPageItem(PageItem* pageItem)
+{
+	m_doc->m_Selection->clear();
+	m_doc->m_Selection->addItem(pageItem);
+	m_doc->view()->requestMode(modeNormal);
+}
+
+SearchReplace::MatchRange SearchReplace::getMatchRange(const StoryText& storyText, int position, const int length, const Options& options)
+{
+	if (length == 0)
+		return {-1, 0, 1};
+
+	int matchStart{position};
+	int matchEnd{position};
+
+	if (options.textEnabled)
+	{
+		// match the text
+		int matchLength{0};
+
+		Qt::CaseSensitivity cs = options.ignoreCase ? Qt::CaseInsensitive : Qt::CaseSensitive;
+		matchStart = storyText.indexOf(options.text, matchStart, cs, &matchLength);
+
+		if (matchStart < 0)
+			// there is no match after position
+			return {-1, 0, length + 1};
+
+		// TODO: can it be longer than length?
+		// if yes qMin(i + matchLength, length[ -1])
+		matchEnd = matchStart + matchLength - 1;
+
+		if (options.wholeWords)
 		{
-			for (int i = position; i < styledText.length(); ++i)
-			{
-				found = true;
-				const ParagraphStyle& parStyle = storyTextEdit->StyledText.paragraphStyle(i);
-				const CharStyle& charStyle = styledText.charStyle(i);
-				if (SSize->isChecked() && (charStyle.fontSize() != sSize))
-					found = false;
-				if (SFont->isChecked() && (charStyle.font().scName() != sFont))
-					found = false;
-				if (SStyle->isChecked() && (parStyle.parent() != m_doc->paragraphStyles()[sStyle].name()))
-					found = false;
-				if (SAlign->isChecked() && (parStyle.alignment() != sAlign))
-					found = false;
-				if (SFill->isChecked() && (charStyle.fillColor() != fCol))
-					found = false;
-				if (SFillS->isChecked() && (charStyle.fillShade() != sFillSh))
-					found = false;
-				if (SStroke->isChecked() && (charStyle.strokeColor() != sCol))
-					found = false;
-				if (SStrokeS->isChecked() && (charStyle.strokeShade() != sStrokeSh))
-					found = false;
-				if (SEffect->isChecked() && ((charStyle.effects() & ScStyle_UserStyles) != sEff))
-					found = false;
-				if (found && (firstChar < 0))
-					firstChar = i;
-				else if ((firstChar >= 0) && !found)
-				{
-					lastChar = i;
-					break;
-				}
-				// When searching paragraph styles break at the end of each found paragraph
-				if (SStyle->isChecked() && (firstChar >= 0) && styledText.text(i) == SpecialChars::PARSEP)
-				{
-					lastChar = i;
-					break;
-				}
-			}
+			if (matchStart > 0 && storyText.text(matchStart - 1).isLetterOrNumber())
+				return {-1, 0, matchEnd + 1};
+			if (matchEnd + 1 < length && storyText.text(matchEnd + 1).isLetterOrNumber())
+				return {-1, 0, matchEnd + 1};
 		}
-		found = (firstChar >= 0);
-		if (found)
+	}
+
+	// check the first char or the matched text
+	// TODO: is it possible/worth to include all contiguous matching when not textEnabled? (move in here the loop below)
+	bool formatMatching{true};
+	for (int i = matchStart; i <= matchEnd; ++i)
+	{
+		const auto& parStyle = storyText.paragraphStyle(i);
+		const auto& charStyle = storyText.charStyle(i);
+		formatMatching = isFormatMatching(parStyle, charStyle, options);
+		if (!formatMatching)
+			break;
+	}
+	if (!formatMatching)
+	{
+		// skip all contiguous non matching chars
+		for (int i = matchEnd; i <= length; ++i)
 		{
-			cursor.setPosition(firstChar);
-			cursor.setPosition(lastChar, QTextCursor::KeepAnchor);
-			storyTextEdit->setTextCursor(cursor);
+			const auto& parStyle = storyText.paragraphStyle(i);
+			const auto& charStyle = storyText.charStyle(i);
+			formatMatching = isFormatMatching(parStyle, charStyle, options);
+			if (formatMatching)
+				break;
+			matchEnd = i;
 		}
-		if (found && searchForReplace)
+		return {-1, 0, matchEnd + 1};
+	}
+
+	if (!options.textEnabled)
+	{
+		// include all contiguous chars with matching format
+		for (int i = matchEnd + 1; i < length; ++i)
 		{
-			// m_doc->scMW()->CurrStED->updateProps(); FIXME
-			if (rep)
-			{
-				DoReplace->setEnabled(true);
-				AllReplace->setEnabled(true);
-			}
-			m_matchesFound++;
-			m_firstMatchPosition = storyTextEdit->textCursor().selectionStart();
+			const auto& parStyle = storyText.paragraphStyle(i);
+			const auto& charStyle = storyText.charStyle(i);
+			if (!(formatMatching = isFormatMatching(parStyle, charStyle, options)))
+				break;
+			matchEnd = i;
 		}
+	}
+
+	return {matchStart, matchEnd, matchEnd + 1};
+}
+
+// TODO: return std::optional<QPair> as soon as we have it.
+QPair<int, int> SearchReplace::searchStory(const StoryText& storyText, const int start, const int length, const Options& options)
+{
+	if (length == 0)
+		return {-1, -1};
+
+	int matchStart{start};
+	int matchEnd{start};
+	bool found{false};
+
+	while (!found && matchStart < length)
+	{
+		const auto match = getMatchRange(storyText, matchStart, length, options);
+		if (match.start == -1)
+			matchStart = match.nextPosition;
 		else
 		{
-			m_found = false;
-			QTextCursor cursor = storyTextEdit->textCursor();
-			cursor.clearSelection();
-			cursor.movePosition(QTextCursor::Start, QTextCursor::MoveAnchor);
-			storyTextEdit->setTextCursor(cursor);
+			matchStart = match.start;
+			matchEnd = match.end;
+			found = true;
 		}
 	}
+
+	if (!found)
+		return {-1, -1};
+
+	return {matchStart, matchEnd};
+}
+
+void SearchReplace::updatePageItemSelection(PageItem* pageItem)
+{
+	StoryText& storyText = pageItem->itemText;
+
+	int foundPos = storyText.cursorPosition();
+	if (storyText.selectionLength() > 0)
+		foundPos = storyText.startOfSelection();
+
+	QPointF textCanvasPos;
+	if (!m_doc->textCanvasPosition(pageItem, foundPos, textCanvasPos))
+		return;
+
+	QRectF updateRect;
+	PageItem* textItem = pageItem->frameOfChar(foundPos);
+	if (textItem != pageItem)
+	{
+		updateRect = pageItem->getVisualBoundingRect();
+		updateRect = updateRect.united(textItem->getVisualBoundingRect());
+		int selLength = pageItem->itemText.selectionLength();
+		pageItem->itemText.deselectAll();
+		pageItem->HasSel = false;
+		m_doc->m_Selection->delaySignalsOn();
+		m_doc->m_Selection->removeItem(pageItem);
+		m_doc->m_Selection->addItem(textItem);
+		pageItem = textItem;
+		pageItem->itemText.deselectAll();
+		if (selLength > 0)
+			pageItem->itemText.select(foundPos, selLength);
+		pageItem->itemText.setCursorPosition(foundPos + selLength);
+		pageItem->HasSel = true;
+		m_doc->m_Selection->delaySignalsOff();
+	}
+	QRectF visibleCanvasRect = m_doc->view()->visibleCanvasRect();
+	if (!visibleCanvasRect.contains(textCanvasPos))
+		m_doc->view()->setCanvasCenterPos(textCanvasPos.x(), textCanvasPos.y());
+	if (!updateRect.isEmpty())
+		m_doc->regionsChanged()->update(updateRect.adjusted(-10.0, -10.0, 10.0, 10.0));
+	pageItem->update();
+}
+
+bool SearchReplace::isFormatMatching(const ParagraphStyle& parStyle, const CharStyle& charStyle, const Options& options)
+{
+	if (options.paragraphStyleEnabled &&
+			parStyle.parent() != m_doc->paragraphStyles()[options.paragraphStyle].name())
+		return false;
+	if (options.alignmentEnabled &&
+			parStyle.alignment() != options.alignment)
+		return false;
+	if (options.fontEnabled &&
+			charStyle.font().scName() != options.font)
+		return false;
+	if (options.fontSizeEnabled &&
+			charStyle.fontSize() != options.fontSize)
+		return false;
+	if (options.fontEffectsEnabled &&
+			(charStyle.effects() & ScStyle_UserStyles) != options.fontEffects)
+		return false;
+	if (options.fillColorEnabled &&
+			charStyle.fillColor() != options.fillColor)
+		return false;
+	if (options.fillShadeEnabled &&
+			charStyle.fillShade() != options.fillShade)
+		return false;
+	if (options.strokeColorEnabled &&
+			charStyle.strokeColor() != options.strokeColor)
+		return false;
+	if (options.strokeShadeEnabled &&
+			charStyle.strokeShade() != options.strokeShade)
+		return false;
+	return true;
 }
 
 void SearchReplace::slotReplace()
 {
-	doReplace();
+	if (m_storyEditorMode)
+		replaceInStoryEditor();
+	else
+		replaceOnCanvas();
+}
 
-	if (m_itemMode)
-		m_item->update();
-	if (!m_found)
-		showNotFoundMessage();
+void SearchReplace::replaceInStoryEditor()
+{
+	auto options = getSearchOptions();
+
+	SEditor* storyTextEdit = m_doc->scMW()->CurrStED->Editor;
+	StoryText& storyText =  storyTextEdit->StyledText;
+
+	int selectionStart = storyTextEdit->textCursor().selectionStart();
+	int selectionEnd = storyTextEdit->textCursor().selectionEnd();
+
+	MatchRange match = getMatchRange(storyText, selectionStart, storyText.length(), options);
+
+	if (selectionStart == match.start && selectionEnd - 1 == match.end)
+		replaceSelectionInStoryEditor();
+
+
+	searchInStoryEditor();
 }
 
-void SearchReplace::doReplace()
+void SearchReplace::replaceOnCanvas()
 {
-	if (m_itemMode)
+
+	auto options = getSearchOptions();
+
+	initCanvasSelection();
+
+	auto pageItem = currentPageItem();
+	StoryText& storyText = pageItem->itemText;
+
+	int selectionStart = storyText.startOfSelection();
+	int selectionEnd = storyText.endOfSelection();
+
+	if (selectionEnd < 0)
 	{
-		if (RText->isChecked())
-		{
-			QString repl = RTextVal->text();
-			m_item->itemText.replaceSelection(repl);
-		}
-		if (RStyle->isChecked())
-		{
-			int oldMode = m_doc->appMode;
-			m_doc->appMode = modeEdit;
-			m_doc->itemSelection_SetNamedParagraphStyle(m_doc->paragraphStyles()[RStyleVal->currentIndex()].name());
-			m_doc->appMode = oldMode;
-		}
-		if (RAlign->isChecked())
-		{
-			int oldMode = m_doc->appMode;
-			m_doc->appMode = modeEdit;
-			m_doc->itemSelection_SetAlignment(RAlignVal->currentIndex());
-			m_doc->appMode = oldMode;
-		}
-		if (RFill->isChecked())
-			m_doc->itemSelection_SetFillColor(RFillVal->currentText());
-		if (RFillS->isChecked())
-			m_doc->itemSelection_SetFillShade(RFillSVal->getValue());
-		if (RStroke->isChecked())
-			m_doc->itemSelection_SetStrokeColor(RStrokeVal->currentText());
-		if (RStrokeS->isChecked())
-			m_doc->itemSelection_SetStrokeShade(RStrokeSVal->getValue());
-		if (RFont->isChecked())
-			m_doc->itemSelection_SetFont(RFontVal->currentText());
-		if (RSize->isChecked())
-			m_doc->itemSelection_SetFontSize(qRound(RSizeVal->value() * 10.0));
-		if (REffect->isChecked() && (m_item->itemText.isSelected()))
-		{
-			int s = REffVal->getStyle() & ScStyle_UserStyles;
-			m_doc->currentStyle.charStyle().setFeatures(static_cast<StyleFlag>(s).featureList()); // ???
-			for (int i = 0; i < m_item->itemText.length(); ++i)
-			{
-				if (m_item->itemText.selected(i))
-				{
-					CharStyle newFeatures;
-					newFeatures.setFeatures(static_cast<StyleFlag>(s).featureList());
-					m_item->itemText.applyCharStyle(i, 1, newFeatures);
-				}
-			}
-		}
-		m_item->itemText.deselectAll();
+		searchOnCanvas();
+		return;
 	}
-	else if (m_doc->scMW()->CurrStED != nullptr)
+
+	MatchRange match = getMatchRange(storyText, selectionStart, storyText.length(), options);
+
+	if (selectionStart == match.start && selectionEnd - 1 == match.end)
+		replaceSelectionOnCanvas();
+
+	searchOnCanvas();
+}
+
+
+void SearchReplace::replaceSelectionInStoryEditor()
+{
+	StoryEditor* se = m_doc->scMW()->CurrStED;
+	auto options = getReplaceOptions();
+
+	// TODO: undo does not work
+	if (options.textEnabled)
 	{
-		StoryEditor* se = m_doc->scMW()->CurrStED;
-		if (RText->isChecked())
+		disconnect(se->Editor, SIGNAL(cursorPositionChanged()), se, SLOT(updateProps()));
+		int SelStart = se->Editor->textCursor().selectionStart();
+		int SelEnd = se->Editor->textCursor().selectionEnd();
+		se->Editor->textCursor().setPosition(SelStart);
+		se->Editor->textCursor().setPosition(SelEnd, QTextCursor::KeepAnchor);
+		se->Editor->textCursor().removeSelectedText();
+		QString newText = options.text;
+		se->Editor->insertPlainText(newText);
+		if (newText.length() > 0)
 		{
-			disconnect(se->Editor, SIGNAL(cursorPositionChanged()), se, SLOT(updateProps()));
-			int SelStart = se->Editor->textCursor().selectionStart();
-			int SelEnd = se->Editor->textCursor().selectionEnd();
-//			se->Editor->insChars(RTextVal->text());
-			se->Editor->textCursor().setPosition(SelStart);
-			se->Editor->textCursor().setPosition(SelEnd, QTextCursor::KeepAnchor);
-			se->Editor->textCursor().removeSelectedText();
-//FIXME		se->Editor->setEffects(se->Editor->CurrentEffects);
-			QString newText = RTextVal->text();
-			se->Editor->insertPlainText(newText);
-			if (newText.length() > 0)
-			{
-				QTextCursor textCursor = se->Editor->textCursor();
-				textCursor.setPosition(SelStart);
-				textCursor.setPosition(SelStart + newText.length(), QTextCursor::KeepAnchor);
-				se->Editor->setTextCursor(textCursor);
-			}
-			connect(se->Editor, SIGNAL(cursorPositionChanged()), se, SLOT(updateProps()));
-//			se->newAlign(se->Editor->currentParaStyle);
+			QTextCursor textCursor = se->Editor->textCursor();
+			textCursor.setPosition(SelStart);
+			textCursor.setPosition(SelStart + newText.length(), QTextCursor::KeepAnchor);
+			se->Editor->setTextCursor(textCursor);
 		}
-		if (RStyle->isChecked())
-			se->newStyle(m_doc->paragraphStyles()[RStyleVal->currentIndex()].name());
-		if (RAlign->isChecked())
-			se->newAlign(RAlignVal->currentIndex());
-		if (RFill->isChecked())
-			se->newTxFill(RFillVal->currentIndex(), -1);
-		if (RFillS->isChecked())
-			se->newTxFill(-1, RFillSVal->getValue());
-		if (RStroke->isChecked())
-			se->newTxStroke(RStrokeVal->currentIndex(), -1);
-		if (RStrokeS->isChecked())
-			se->newTxStroke(-1, RStrokeSVal->getValue());
-		if (RFont->isChecked())
-			se->newTxFont(RFontVal->currentText());
-		if (RSize->isChecked())
-			se->newTxSize(RSizeVal->value());
-		if (REffect->isChecked())
-			se->newTxStyle(REffVal->getStyle());
-
-		QTextCursor textCursor = se->Editor->textCursor();
-		int selStart = textCursor.selectionStart();
-		int selEnd   = textCursor.selectionEnd();
-		int selPos   = qMax(selStart, selEnd);
-		textCursor.setPosition(selPos);
-		se->Editor->setTextCursor(textCursor);
+		connect(se->Editor, SIGNAL(cursorPositionChanged()), se, SLOT(updateProps()));
 	}
-	DoReplace->setEnabled(false);
-	AllReplace->setEnabled(false);
-	doSearch();
+	if (options.paragraphStyleEnabled)
+		se->newStyle(m_doc->paragraphStyles()[options.paragraphStyle].name());
+	if (options.alignmentEnabled)
+		se->newAlign(options.alignment);
+	if (options.fillColorEnabled)
+		se->newTxFill(options.fillColorId, -1);
+	if (options.fillShadeEnabled)
+		se->newTxFill(-1, options.fillShade);
+	if (options.strokeColorEnabled)
+		se->newTxStroke(options.strokeColorId, -1);
+	if (options.strokeShadeEnabled)
+		se->newTxStroke(-1, options.strokeShade);
+	if (options.fontEnabled)
+		se->newTxFont(options.font);
+	if (options.fontSizeEnabled)
+		se->newTxSize(options.fontSize / 10);
+	if (options.fontEffectsEnabled)
+		se->newTxStyle(options.fontEffects);
+
+	// TODO: not sure that this is needed, since we then jump away...
+	QTextCursor textCursor = se->Editor->textCursor();
+	int selStart = textCursor.selectionStart();
+	int selEnd   = textCursor.selectionEnd();
+	int selPos   = qMax(selStart, selEnd);
+	textCursor.setPosition(selPos);
+	se->Editor->setTextCursor(textCursor);
 }
 
-int SearchReplace::firstMatchCursorPosition()
+void SearchReplace::replaceSelectionOnCanvas()
 {
-	return m_firstMatchPosition;
+	auto options = getReplaceOptions();
+
+	auto pageItem = currentPageItem();
+
+	if (options.textEnabled)
+	{
+		// TODO: undo does not work
+		pageItem->itemText.replaceSelection(options.text);
+	}
+
+	if (options.paragraphStyleEnabled)
+	{
+		int oldMode = m_doc->appMode;
+		m_doc->appMode = modeEdit;
+		m_doc->itemSelection_SetNamedParagraphStyle(m_doc->paragraphStyles()[options.paragraphStyle].name());
+		m_doc->appMode = oldMode;
+	}
+
+	if (options.alignmentEnabled)
+	{
+		int oldMode = m_doc->appMode;
+		m_doc->appMode = modeEdit;
+		m_doc->itemSelection_SetAlignment(options.alignment);
+		m_doc->appMode = oldMode;
+	}
+
+	if (options.fontEnabled)
+		m_doc->itemSelection_SetFont(options.font);
+
+	if (options.fontSizeEnabled)
+		m_doc->itemSelection_SetFontSize(options.fontSize);
+
+	// TODO: undo does not work
+	if (options.fontEffectsEnabled && (pageItem->itemText.isSelected()))
+	{
+		int start = pageItem->itemText.startOfSelection();
+		int end = pageItem->itemText.endOfSelection();
+		int s = options.fontEffects & ScStyle_UserStyles;
+		m_doc->currentStyle.charStyle().setFeatures(static_cast<StyleFlag>(s).featureList()); // ???
+		for (int i = start; i < end; ++i)
+		{
+			CharStyle newFeatures;
+			newFeatures.setFeatures(static_cast<StyleFlag>(s).featureList());
+			pageItem->itemText.applyCharStyle(i, 1, newFeatures);
+		}
+	}
+
+	if (options.fillColorEnabled)
+		m_doc->itemSelection_SetFillColor(options.fillColor);
+
+	if (options.fillShadeEnabled)
+		m_doc->itemSelection_SetFillShade(options.fillShade);
+
+	if (options.strokeColorEnabled)
+		m_doc->itemSelection_SetStrokeColor(options.strokeColor);
+
+	if (options.strokeShadeEnabled)
+		m_doc->itemSelection_SetStrokeShade(options.strokeShade);
+
+	// TODO: we need less than that... can we have a subset that updates the screen without making sure that the selection is visible?
+	updatePageItemSelection(pageItem);
 }
 
-void SearchReplace::setSearchedText(const QString& text)
+void SearchReplace::slotReplaceAll()
 {
-	if (SText->isChecked())
-		STextVal->setText(text);
+	// TODO: what does this do?
+	// QScopedValueRollback<bool> replaceAllRollback(m_replacingAll, true);
+	if (m_storyEditorMode)
+		replaceAllInStoryEditor();
+	else
+		replaceAllOnCanvas();
 }
 
-void SearchReplace::slotReplaceAll()
+void SearchReplace::replaceAllInStoryEditor()
 {
-	QScopedValueRollback<bool> replaceAllRollback(m_replacingAll, true);
+	auto options = getSearchOptions();
 
-	if (m_itemMode)
-		m_doc->DoDrawing = false;
+	SEditor* storyTextEdit = m_doc->scMW()->CurrStED->Editor;
+	StoryText& storyText =  storyTextEdit->StyledText;
+
+	bool found{false};
+
+	MatchRange match = getMatchRange(storyText, 0, storyText.length(), options);
 
-	do
+	while (match.start >= 0)
 	{
-		doReplace();
+		found = true;
+
+		QTextCursor cursor = storyTextEdit->textCursor();
+		cursor.setPosition(match.start);
+		cursor.setPosition(match.end + 1, QTextCursor::KeepAnchor);
+		storyTextEdit->setTextCursor(cursor);
+
+		replaceSelectionInStoryEditor();
+
+		match = getMatchRange(storyText, match.start + 1, storyText.length(), options);
 	}
-	while (m_found);
 
-	if (m_itemMode)
+	if (!found)
+		showMessage(tr("No match found."));
+}
+
+void SearchReplace::replaceAllOnCanvas()
+{
+	m_doc->DoDrawing = false;
+
+	auto options = getSearchOptions();
+
+	initCanvasSelection();
+
+	bool found{false};
+	for (auto pageItem: m_pageItems)
 	{
-		m_doc->DoDrawing = true;
-		if (m_item->isTextFrame())
-			m_item->asTextFrame()->invalidateLayout(true);
-		m_doc->regionsChanged()->update(QRectF());
+		StoryText& storyText = pageItem->itemText;
+
+		MatchRange match = getMatchRange(storyText, 0, storyText.length(), options);
+
+		while (match.start >= 0)
+		{
+			found = true;
+
+			storyText.select(match.start, match.end + 1 - match.start);
+			storyText.setCursorPosition(match.end + 1);
+
+			replaceSelectionOnCanvas();
+
+			match = getMatchRange(storyText, match.start + 1, storyText.length(), options);
+		}
 	}
 
-	showNotFoundMessage();
+	if (!found)
+		showMessage(tr("No match found."));
+
+	m_doc->DoDrawing = true;
+	for (auto pageItem: m_pageItems)
+		pageItem->asTextFrame()->invalidateLayout(true);
+	m_doc->regionsChanged()->update(QRectF());
+}
+
+
+void SearchReplace::slotCollapseFormat()
+{
+	m_stateCollapsed = !m_stateCollapsed;
+	collapseFormat();
+}
+
+void SearchReplace::collapseFormat()
+{
+	searchGroupBox->setVisible(!m_stateCollapsed);
+	replaceGroupBox->setVisible(!m_stateCollapsed);
+	// move the following line to setCollapseLabel() if we have to catch languageChange()
+	collapseButton->setText(m_stateCollapsed ? tr("More...") : tr("Less..."));
+	// It's important to call QApplication::processEvents() before calling adjustSize()
+	// https://stackoverflow.com/questions/1675499/how-do-i-auto-adjust-the-size-of-a-qdialog-depending-on-the-text-length-of-one-o#comment82074153_1679399
+	QTimer::singleShot(0, [this](){adjustSize();});
 }
 
 void SearchReplace::showNotFoundMessage()
@@ -881,338 +854,444 @@ void SearchReplace::showNotFoundMessage()
 	}
 }
 
-void SearchReplace::enableTxSearch()
+void SearchReplace::enableTextSearch()
 {
-	bool setter = SText->isChecked();
-	STextVal->setEnabled(setter);
-	Word->setEnabled(setter);
-	CaseIgnore->setEnabled(setter);
-	if (setter)
-		STextVal->setFocus();
-	updateSearchButtonState();
+	bool enabled = !searchTextValue->text().isEmpty();
+	wholeWordsCheckBox->setEnabled(enabled);
+	ignoreCaseCheckBox->setEnabled(enabled);
 }
 
 void SearchReplace::enableStyleSearch()
 {
-	SStyleVal->setEnabled(SStyleVal->count() ? SStyle->isChecked() : false);
-	updateSearchButtonState();
+	searchStyleComboBox->setEnabled(searchStyleCheckBox->isChecked());
 }
 
-void SearchReplace::enableAlignSearch()
+void SearchReplace::enableAlignmentSearch()
 {
-	SAlignVal->setEnabled(SAlign->isChecked());
-	updateSearchButtonState();
+	searchAlignmentComboBox->setEnabled(searchAlignmentCheckBox->isChecked());
 }
 
 void SearchReplace::enableFontSearch()
 {
-	SFontVal->setEnabled(SFont->isChecked());
-	updateSearchButtonState();
-}
-
-void SearchReplace::enableSizeSearch()
-{
-	SSizeVal->setEnabled(SSize->isChecked());
-	updateSearchButtonState();
+	searchFontComboBox->setEnabled(searchFontCheckBox->isChecked());
 }
 
-void SearchReplace::enableEffSearch()
+void SearchReplace::enableFontSizeSearch()
 {
-	SEffVal->setEnabled(SEffect->isChecked());
-	updateSearchButtonState();
+	searchFontSizeSpinBox->setEnabled(searchFontSizeCheckBox->isChecked());
 }
 
-void SearchReplace::enableFillSearch()
+void SearchReplace::enableFontEffectsSearch()
 {
-	SFillVal->setEnabled(SFill->isChecked());
-	updateSearchButtonState();
+	searchFontEffects->setEnabled(searchFontEffectsCheckBox->isChecked());
 }
 
-void SearchReplace::enableFillSSearch()
+void SearchReplace::enableFillColorSearch()
 {
-	SFillSVal->setEnabled(SFillS->isChecked());
-	updateSearchButtonState();
+	searchFillColorComboBox->setEnabled(searchFillColorCheckBox->isChecked());
 }
 
-void SearchReplace::enableStrokeSearch()
+void SearchReplace::enableFillShadeSearch()
 {
-	SStrokeVal->setEnabled(SStroke->isChecked());
-	updateSearchButtonState();
+	searchFillShadeToolButton->setEnabled(searchFillShadeCheckBox->isChecked());
 }
 
-void SearchReplace::enableStrokeSSearch()
+void SearchReplace::enableStrokeColorSearch()
 {
-	SStrokeSVal->setEnabled(SStrokeS->isChecked());
-	updateSearchButtonState();
+	searchStrokeColorComboBox->setEnabled(searchStrokeColorCheckBox->isChecked());
 }
 
-void SearchReplace::enableTxReplace()
+void SearchReplace::enableStrokeShadeSearch()
 {
-	RTextVal->setEnabled(RText->isChecked());
-	if (RText->isChecked())
-		RTextVal->setFocus();
-	updateReplaceButtonsState();
+	searchStrokeShadeToolButton->setEnabled(searchStrokeShadeCheckBox->isChecked());
 }
 
 void SearchReplace::enableStyleReplace()
 {
-	RStyleVal->setEnabled(RStyle->isChecked());
-	updateReplaceButtonsState();
+	replaceStyleComboBox->setEnabled(replaceStyleCheckBox->isChecked());
 }
 
-void SearchReplace::enableAlignReplace()
+void SearchReplace::enableAlignmentReplace()
 {
-	RAlignVal->setEnabled(RAlign->isChecked());
-	updateReplaceButtonsState();
+	replaceAlignmentComboBox->setEnabled(replaceAlignmentCheckBox->isChecked());
 }
 
 void SearchReplace::enableFontReplace()
 {
-	RFontVal->setEnabled(RFont->isChecked());
-	updateReplaceButtonsState();
+	replaceFontComboBox->setEnabled(replaceFontCheckBox->isChecked());
 }
 
-void SearchReplace::enableSizeReplace()
+void SearchReplace::enableFontSizeReplace()
 {
-	RSizeVal->setEnabled(RSize->isChecked());
-	updateReplaceButtonsState();
+	replaceFontSizeSpinBox->setEnabled(replaceFontSizeCheckBox->isChecked());
 }
 
-void SearchReplace::enableEffReplace()
+void SearchReplace::enableFontEffectsReplace()
 {
-	REffVal->setEnabled(REffect->isChecked());
-	updateReplaceButtonsState();
+	replaceFontEffects->setEnabled(replaceFontEffectsCheckBox->isChecked());
 }
 
-void SearchReplace::enableFillReplace()
+void SearchReplace::enableFillColorReplace()
 {
-	RFillVal->setEnabled(RFill->isChecked());
-	updateReplaceButtonsState();
+	replaceFillColorComboBox->setEnabled(replaceFillColorCheckBox->isChecked());
 }
 
-void SearchReplace::enableFillSReplace()
+void SearchReplace::enableFillShadeReplace()
 {
-	RFillSVal->setEnabled(RFillS->isChecked());
-	updateReplaceButtonsState();
+	replaceFillShadeToolButton->setEnabled(replaceFillShadeCheckBox->isChecked());
 }
 
-void SearchReplace::enableStrokeReplace()
+void SearchReplace::enableStrokeColorReplace()
 {
-	RStrokeVal->setEnabled(RStroke->isChecked());
-	updateReplaceButtonsState();
+	replaceStrokeColorComboBox->setEnabled(replaceStrokeColorCheckBox->isChecked());
 }
 
-void SearchReplace::enableStrokeSReplace()
+void SearchReplace::enableStrokeShadeReplace()
 {
-	RStrokeSVal->setEnabled(RStrokeS->isChecked());
-	updateReplaceButtonsState();
+	replaceStrokeShadeToolButton->setEnabled(replaceStrokeShadeCheckBox->isChecked());
 }
 
 void SearchReplace::clear()
 {
-	SAlign->setChecked(false);
-	SStroke->setChecked(false);
-	SFill->setChecked(false);
-	SStrokeS->setChecked(false);
-	SFillS->setChecked(false);
-	SSize->setChecked(false);
-	SFont->setChecked(false);
-	SStyle->setChecked(false);
-	SText->setChecked(false);
-	SEffect->setChecked(false);
-	REffect->setChecked(false);
-	STextVal->setText("");
-	int currentParaStyle = findParagraphStyle(m_doc, m_doc->currentStyle);
-	SStyleVal->setCurrentIndex(currentParaStyle);
-	RAlignVal->setCurrentIndex(m_doc->currentStyle.alignment());
-	setCurrentComboItem(SFontVal, m_doc->currentStyle.charStyle().font().scName());
-	setCurrentComboItem(SFillVal, m_doc->currentStyle.charStyle().fillColor());
-	setCurrentComboItem(SStrokeVal, m_doc->currentStyle.charStyle().strokeColor());
-	SSizeVal->setValue(m_doc->currentStyle.charStyle().fontSize() / 10.0);
-	RStroke->setChecked(false);
-	RStrokeS->setChecked(false);
-	RFill->setChecked(false);
-	RFillS->setChecked(false);
-	RSize->setChecked(false);
-	RFont->setChecked(false);
-	RStyle->setChecked(false);
-	RText->setChecked(false);
-	RTextVal->setText("");
-	RStyleVal->setCurrentIndex(currentParaStyle);
-	RAlignVal->setCurrentIndex(m_doc->currentStyle.alignment());
-	setCurrentComboItem(RFontVal, m_doc->currentStyle.charStyle().font().scName());
-	setCurrentComboItem(RFillVal, m_doc->currentStyle.charStyle().fillColor());
-	setCurrentComboItem(RStrokeVal, m_doc->currentStyle.charStyle().strokeColor());
-	RSizeVal->setValue(m_doc->currentStyle.charStyle().fontSize() / 10.0);
-	Word->setChecked(false);
-	CaseIgnore->setChecked(false);
-	enableTxSearch();
-	enableStyleSearch();
-	enableFontSearch();
-	enableSizeSearch();
-	enableEffSearch();
-	enableFillSearch();
-	enableFillSSearch();
-	enableStrokeSearch();
-	enableStrokeSSearch();
-	enableTxReplace();
-	enableStyleReplace();
-	enableFontReplace();
-	enableSizeReplace();
-	enableEffReplace();
-	enableFillReplace();
-	enableFillSReplace();
-	enableStrokeReplace();
-	enableStrokeSReplace();
+	searchStyleCheckBox->setChecked(false);
+	searchAlignmentCheckBox->setChecked(false);
+	searchFontCheckBox->setChecked(false);
+	searchFontSizeCheckBox->setChecked(false);
+	searchFontEffectsCheckBox->setChecked(false);
+	searchFillColorCheckBox->setChecked(false);
+	searchFillShadeCheckBox->setChecked(false);
+	searchStrokeColorCheckBox->setChecked(false);
+	searchStrokeShadeCheckBox->setChecked(false);
+
+	replaceStyleCheckBox->setChecked(false);
+	replaceAlignmentCheckBox->setChecked(false);
+	replaceFontCheckBox->setChecked(false);
+	replaceFontSizeCheckBox->setChecked(false);
+	replaceFontEffectsCheckBox->setChecked(false);
+	replaceFillColorCheckBox->setChecked(false);
+	replaceFillShadeCheckBox->setChecked(false);
+	replaceStrokeColorCheckBox->setChecked(false);
+	replaceStrokeShadeCheckBox->setChecked(false);
+
+	auto currentParaStyle = findParagraphStyle(m_doc, m_doc->currentStyle);
+
+	searchTextValue->setText("");
+	searchStyleComboBox->setCurrentIndex(currentParaStyle);
+	searchAlignmentComboBox->setCurrentIndex(m_doc->currentStyle.alignment());
+	setCurrentComboItem(searchFontComboBox, m_doc->currentStyle.charStyle().font().scName());
+	searchFontSizeSpinBox->setValue(m_doc->currentStyle.charStyle().fontSize() / 10.0);
+	setCurrentComboItem(searchFillColorComboBox, m_doc->currentStyle.charStyle().fillColor());
+	setCurrentComboItem(searchStrokeColorComboBox, m_doc->currentStyle.charStyle().strokeColor());
+
+	replaceTextValue->setText("");
+	replaceStyleComboBox->setCurrentIndex(currentParaStyle);
+	replaceAlignmentComboBox->setCurrentIndex(m_doc->currentStyle.alignment());
+	setCurrentComboItem(replaceFontComboBox, m_doc->currentStyle.charStyle().font().scName());
+	replaceFontSizeSpinBox->setValue(m_doc->currentStyle.charStyle().fontSize() / 10.0);
+	setCurrentComboItem(replaceFillColorComboBox, m_doc->currentStyle.charStyle().fillColor());
+	setCurrentComboItem(replaceStrokeColorComboBox, m_doc->currentStyle.charStyle().strokeColor());
+
+
+	wholeWordsCheckBox->setChecked(false);
+	ignoreCaseCheckBox->setChecked(false);
+	
+	searchStyleComboBox->setEnabled(false);
+	searchAlignmentComboBox->setEnabled(false);
+	searchFontComboBox->setEnabled(false);
+	searchFontSizeSpinBox->setEnabled(false);
+	searchFontEffects->setEnabled(false);
+	searchFillColorComboBox->setEnabled(false);
+	searchFillShadeToolButton->setEnabled(false);
+	searchStrokeColorComboBox->setEnabled(false);
+	searchStrokeShadeToolButton->setEnabled(false);
+
+	replaceStyleComboBox->setEnabled(false);
+	replaceAlignmentComboBox->setEnabled(false);
+	replaceFontComboBox->setEnabled(false);
+	replaceFontSizeSpinBox->setEnabled(false);
+	replaceFontEffects->setEnabled(false);
+	replaceFillColorComboBox->setEnabled(false);
+	replaceFillShadeToolButton->setEnabled(false);
+	replaceStrokeColorComboBox->setEnabled(false);
+	replaceStrokeShadeToolButton->setEnabled(false);
+
+	searchButton->setEnabled(false);
+	replaceButton->setEnabled(false);
+	replaceAllButton->setEnabled(false);
+}
+
+bool SearchReplace::anySearchChecked()
+{
+	return
+		!searchTextValue->text().isEmpty() ||
+		searchStyleCheckBox->isChecked() ||
+		searchAlignmentCheckBox->isChecked() ||
+		searchFontCheckBox->isChecked() ||
+		searchFontSizeCheckBox->isChecked() ||
+		searchFontEffectsCheckBox->isChecked() ||
+		searchFillColorCheckBox->isChecked() ||
+		searchFillShadeCheckBox->isChecked() ||
+		searchStrokeColorCheckBox->isChecked() ||
+		searchStrokeShadeCheckBox->isChecked();
+}
+
+bool SearchReplace::anyReplaceChecked()
+{
+	return
+		replaceStyleCheckBox->isChecked() ||
+		replaceAlignmentCheckBox->isChecked() ||
+		replaceFontCheckBox->isChecked() ||
+		replaceFontSizeCheckBox->isChecked() ||
+		replaceFontEffectsCheckBox->isChecked() ||
+		replaceFillColorCheckBox->isChecked() ||
+		replaceFillShadeCheckBox->isChecked() ||
+		replaceStrokeColorCheckBox->isChecked() ||
+		replaceStrokeShadeCheckBox->isChecked();
 }
 
-void SearchReplace::updateReplaceButtonsState()
+SearchReplace::Options SearchReplace::getSearchOptions()
 {
-	bool replaceEnabled = false;
-	if (RFill->isChecked() || RStroke->isChecked() || RStyle->isChecked() || RFont->isChecked()  ||
-		RStrokeS->isChecked() || RFillS->isChecked() || RSize->isChecked() || REffect->isChecked() ||
-		RAlign->isChecked())
+	Options options{};
+
+	options.textEnabled = !searchTextValue->text().isEmpty();
+	options.ignoreCase = ignoreCaseCheckBox->isChecked();
+	options.wholeWords = wholeWordsCheckBox->isChecked();
+	options.paragraphStyleEnabled = searchStyleCheckBox->isChecked();
+	options.alignmentEnabled = searchAlignmentCheckBox->isChecked();
+	options.fontEnabled = searchFontCheckBox->isChecked();
+	options.fontSizeEnabled = searchFontSizeCheckBox->isChecked();
+	options.fontEffectsEnabled = searchFontEffectsCheckBox->isChecked();
+	options.fillColorEnabled = searchFillColorCheckBox->isChecked();
+	options.fillShadeEnabled = searchFillShadeCheckBox->isChecked();
+	options.strokeColorEnabled = searchStrokeColorCheckBox->isChecked();
+	options.strokeShadeEnabled = searchStrokeShadeCheckBox->isChecked();
+
+	if (options.textEnabled)
+		options.text = searchTextValue->text();
+	if (options.paragraphStyleEnabled)
+		options.paragraphStyle = searchStyleComboBox->currentIndex();
+	if (options.alignmentEnabled)
+		options.alignment = searchAlignmentComboBox->currentIndex();
+	if (options.fontEnabled)
+		options.font = searchFontComboBox->currentText();
+	if (options.fontSizeEnabled)
+		options.fontSize = qRound(searchFontSizeSpinBox->value() * 10);
+	if (options.fontEffectsEnabled)
+		options.fontEffects = searchFontEffects->getStyle();
+	if (options.fillColorEnabled)
+		options.fillColor = searchFillColorComboBox->currentText();
+	if (options.fillShadeEnabled)
+		options.fillShade = searchFillShadeToolButton->getValue();
+	if (options.strokeColorEnabled)
 	{
-		replaceEnabled = true;
+		options.strokeColor = searchStrokeColorComboBox->currentText();
 	}
-	replaceEnabled |= RText->isChecked();
-	if (m_itemMode)
-		replaceEnabled &= (m_item->itemText.isSelected());
-	else if (m_doc->scMW()->CurrStED != nullptr)
-		replaceEnabled &= m_doc->scMW()->CurrStED->Editor->textCursor().hasSelection();
-	else
-		replaceEnabled = false;
-	replaceEnabled &= m_found;
-	DoReplace->setEnabled(replaceEnabled);
-	AllReplace->setEnabled(replaceEnabled);
+	if (options.strokeShadeEnabled)
+		options.strokeShade = searchStrokeShadeToolButton->getValue();
+
+	return options;
 }
 
-void SearchReplace::updateSearchButtonState()
+SearchReplace::Options SearchReplace::getReplaceOptions()
 {
-	bool searchEnabled = false;
-	if (SFill->isChecked() || SStroke->isChecked() || SStyle->isChecked() || SFont->isChecked() ||
-		SStrokeS->isChecked() || SFillS->isChecked() || SSize->isChecked() || SEffect->isChecked() ||
-		SAlign->isChecked())
+	Options options{};
+
+	// replace if there is a text or if no format is selected
+	options.textEnabled = !replaceTextValue->text().isEmpty() || !anyReplaceChecked();
+	options.paragraphStyleEnabled = replaceStyleCheckBox->isChecked();
+	options.alignmentEnabled = replaceAlignmentCheckBox->isChecked();
+	options.fontEnabled = replaceFontCheckBox->isChecked();
+	options.fontSizeEnabled = replaceFontSizeCheckBox->isChecked();
+	options.fontEffectsEnabled = replaceFontEffectsCheckBox->isChecked();
+	options.fillColorEnabled = replaceFillColorCheckBox->isChecked();
+	options.fillShadeEnabled = replaceFillShadeCheckBox->isChecked();
+	options.strokeColorEnabled = replaceStrokeColorCheckBox->isChecked();
+	options.strokeShadeEnabled = replaceStrokeShadeCheckBox->isChecked();
+
+	if (options.textEnabled)
+		options.text = replaceTextValue->text();
+	if (options.paragraphStyleEnabled)
+		options.paragraphStyle = replaceStyleComboBox->currentIndex();
+	if (options.alignmentEnabled)
+		options.alignment = replaceAlignmentComboBox->currentIndex();
+	if (replaceFontCheckBox->isChecked())
+		options.font = replaceFontComboBox->currentText();
+	if (options.fontSizeEnabled)
+		options.fontSize = qRound(replaceFontSizeSpinBox->value() * 10);
+	if (options.fontEffectsEnabled)
+		options.fontEffects = replaceFontEffects->getStyle();
+	if (options.fillColorEnabled)
+	{
+		options.fillColor = replaceFillColorComboBox->currentText();
+		options.fillColorId = searchFillColorComboBox->currentIndex();
+	}
+	if (options.fillShadeEnabled)
+		options.fillShade = replaceFillShadeToolButton->getValue();
+	if (options.strokeColorEnabled)
 	{
-		searchEnabled = true;
+		options.strokeColor = replaceStrokeColorComboBox->currentText();
+		options.strokeColorId = searchStrokeColorComboBox->currentIndex();
 	}
-	searchEnabled |= (SText->isChecked() && !STextVal->text().isEmpty());
-	DoSearch->setEnabled(searchEnabled);
+	if (options.strokeShadeEnabled)
+		options.strokeShade = replaceStrokeShadeToolButton->getValue();
+
+
+	return options;
+}
+
+void SearchReplace::updateButtonState()
+{
+	bool enabled = anySearchChecked();
+
+	searchButton->setEnabled(enabled);
+	replaceButton->setEnabled(enabled);
+	replaceAllButton->setEnabled(enabled);
+
+	hideMessage();
+}
+
+void SearchReplace::hideMessage()
+{
+	messageLabel->setVisible(false);
+}
+
+void SearchReplace::showMessage(const QString& message)
+{
+	messageLabel->setText(message);
+	messageLabel->setVisible(true);
 }
 
 void SearchReplace::readPrefs()
 {
-	SStroke->setChecked(m_prefs->getBool("SStroke", false));
-	SFill->setChecked(m_prefs->getBool("SFill", false));
-	SStrokeS->setChecked(m_prefs->getBool("SStrokeS", false));
-	SFillS->setChecked(m_prefs->getBool("SFillS", false));
-	SSize->setChecked(m_prefs->getBool("SSize", false));
-	SFont->setChecked(m_prefs->getBool("SFont", false));
-	SStyle->setChecked(m_prefs->getBool("SStyle", false));
-	SAlign->setChecked(m_prefs->getBool("SAlign", false));
-	SText->setChecked(m_prefs->getBool("SText", true));
-	SEffect->setChecked(m_prefs->getBool("SEffect", false));
-	REffect->setChecked(m_prefs->getBool("REffect", false));
-	STextVal->setText(m_prefs->get("STextVal", ""));
+	m_stateCollapsed = m_prefs->getBool("collapsed", false);
+	{
+		QRect screen = QApplication::desktop()->availableGeometry(this);
+		// by default in the middle of the screen
+		int left = m_prefs->getInt("left", screen.width() > width() ? (screen.width() - width()) / 2 : 0);
+		int top = m_prefs->getInt("top", screen.height() > height() ? (screen.height() - height()) / 2 : 0);
+		// ensure that it's in the screen
+		if (left + width() > screen.width())
+			left = screen.width() - left;
+		if (left < 0)
+			left = 0;
+		move(left, top);
+	}
+	searchStrokeColorCheckBox->setChecked(m_prefs->getBool("SStroke", false));
+	searchFillColorCheckBox->setChecked(m_prefs->getBool("SFill", false));
+	searchStrokeShadeCheckBox->setChecked(m_prefs->getBool("SStrokeS", false));
+	searchFillShadeCheckBox->setChecked(m_prefs->getBool("SFillS", false));
+	searchFontSizeCheckBox->setChecked(m_prefs->getBool("SSize", false));
+	searchFontCheckBox->setChecked(m_prefs->getBool("SFont", false));
+	replaceStyleCheckBox->setChecked(m_prefs->getBool("SStyle", false));
+	searchAlignmentCheckBox->setChecked(m_prefs->getBool("SAlign", false));
+	searchFontEffectsCheckBox->setChecked(m_prefs->getBool("SEffect", false));
+	replaceFontEffectsCheckBox->setChecked(m_prefs->getBool("REffect", false));
+	searchTextValue->setText(m_prefs->get("STextVal", ""));
 	int tmp = m_prefs->getInt("SStyleVal", findParagraphStyle(m_doc, m_doc->currentStyle));
-	if (tmp < 0 || tmp >= SStyleVal->count())
+	if (tmp < 0 || tmp >= searchStyleComboBox->count())
 		tmp = 0;
-	SStyleVal->setCurrentIndex(tmp);
+	searchStyleComboBox->setCurrentIndex(tmp);
 	tmp = m_prefs->getInt("SAlignVal", m_doc->currentStyle.alignment());
-	if (tmp < 0 || tmp >= SAlignVal->count())
+	if (tmp < 0 || tmp >= searchAlignmentComboBox->count())
 		tmp = 0;
-	SAlignVal->setCurrentIndex(tmp);
-	setCurrentComboItem(SFontVal, m_prefs->get("SFontVal", m_doc->currentStyle.charStyle().font().scName()));
-	setCurrentComboItem(SFillVal, m_prefs->get("SFillVal", m_doc->currentStyle.charStyle().fillColor()));
-	setCurrentComboItem(SStrokeVal, m_prefs->get("SStrokeVal", m_doc->currentStyle.charStyle().strokeColor()));
-	SSizeVal->setValue(m_prefs->getDouble("SSizeVal", m_doc->currentStyle.charStyle().fontSize() / 10.0));
-	RStroke->setChecked(m_prefs->getBool("RStroke", false));
-	RStrokeS->setChecked(m_prefs->getBool("RStrokeS", false));
-	RFill->setChecked(m_prefs->getBool("RFill", false));
-	RFillS->setChecked(m_prefs->getBool("RFillS", false));
-	RSize->setChecked(m_prefs->getBool("RSize", false));
-	RFont->setChecked(m_prefs->getBool("RFont", false));
-	RStyle->setChecked(m_prefs->getBool("RStyle", false));
-	RAlign->setChecked(m_prefs->getBool("RAlign", false));
-	RText->setChecked(m_prefs->getBool("RText", true));
-	RTextVal->setText(m_prefs->get("RTextVal", ""));
+	searchAlignmentComboBox->setCurrentIndex(tmp);
+	setCurrentComboItem(searchFontComboBox, m_prefs->get("SFontVal", m_doc->currentStyle.charStyle().font().scName()));
+	setCurrentComboItem(searchFillColorComboBox, m_prefs->get("SFillVal", m_doc->currentStyle.charStyle().fillColor()));
+	setCurrentComboItem(searchStrokeColorComboBox, m_prefs->get("SStrokeVal", m_doc->currentStyle.charStyle().strokeColor()));
+	searchFontSizeSpinBox->setValue(m_prefs->getDouble("SSizeVal", m_doc->currentStyle.charStyle().fontSize() / 10.0));
+	replaceStrokeColorCheckBox->setChecked(m_prefs->getBool("RStroke", false));
+	replaceStrokeShadeCheckBox->setChecked(m_prefs->getBool("RStrokeS", false));
+	replaceFillColorCheckBox->setChecked(m_prefs->getBool("RFill", false));
+	replaceFillShadeCheckBox->setChecked(m_prefs->getBool("RFillS", false));
+	replaceFontSizeCheckBox->setChecked(m_prefs->getBool("RSize", false));
+	replaceFontCheckBox->setChecked(m_prefs->getBool("RFont", false));
+	replaceStyleCheckBox->setChecked(m_prefs->getBool("RStyle", false));
+	replaceAlignmentCheckBox->setChecked(m_prefs->getBool("RAlign", false));
+	replaceTextValue->setText(m_prefs->get("RTextVal", ""));
 	tmp = m_prefs->getInt("RStyleVal", findParagraphStyle(m_doc, m_doc->currentStyle));
-	if (tmp < 0 || tmp >= RStyleVal->count())
+	if (tmp < 0 || tmp >= replaceStyleComboBox->count())
 		tmp = 0;
-	RStyleVal->setCurrentIndex(tmp);
+	replaceStyleComboBox->setCurrentIndex(tmp);
 	tmp = m_prefs->getInt("RAlignVal", m_doc->currentStyle.alignment());
-	if (tmp < 0 || tmp >= RAlignVal->count())
+	if (tmp < 0 || tmp >= replaceAlignmentComboBox->count())
 		tmp = 0;
-	RAlignVal->setCurrentIndex(tmp);
-	setCurrentComboItem(RFontVal, m_prefs->get("RFontVal", m_doc->currentStyle.charStyle().font().scName()));
-	setCurrentComboItem(RFillVal, m_prefs->get("RFillVal", m_doc->currentStyle.charStyle().fillColor()));
-	setCurrentComboItem(RStrokeVal, m_prefs->get("RStrokeVal", m_doc->currentStyle.charStyle().strokeColor()));
-	RSizeVal->setValue(m_prefs->getDouble("RSizeVal", m_doc->currentStyle.charStyle().fontSize() / 10.0));
-	Word->setChecked(m_prefs->getBool("Word", false));
-	CaseIgnore->setChecked(m_prefs->getBool("CaseIgnore", false));
-
-	enableTxSearch();
+	replaceAlignmentComboBox->setCurrentIndex(tmp);
+	setCurrentComboItem(replaceFontComboBox, m_prefs->get("RFontVal", m_doc->currentStyle.charStyle().font().scName()));
+	setCurrentComboItem(replaceFillColorComboBox, m_prefs->get("RFillVal", m_doc->currentStyle.charStyle().fillColor()));
+	setCurrentComboItem(replaceStrokeColorComboBox, m_prefs->get("RStrokeVal", m_doc->currentStyle.charStyle().strokeColor()));
+	replaceFontSizeSpinBox->setValue(m_prefs->getDouble("RSizeVal", m_doc->currentStyle.charStyle().fontSize() / 10.0));
+	wholeWordsCheckBox->setChecked(m_prefs->getBool("Word", false));
+	ignoreCaseCheckBox->setChecked(m_prefs->getBool("IgnoreCase", false));
+
+	enableTextSearch();
 	enableStyleSearch();
-	enableAlignSearch();
+	enableAlignmentSearch();
 	enableFontSearch();
-	enableSizeSearch();
-	enableEffSearch();
-	enableFillSearch();
-	enableFillSSearch();
-	enableStrokeSearch();
-	enableStrokeSSearch();
-	enableTxReplace();
+	enableFontSizeSearch();
+	enableFontEffectsSearch();
+	enableFillColorSearch();
+	enableFillShadeSearch();
+	enableStrokeColorSearch();
+	enableStrokeShadeSearch();
 	enableStyleReplace();
-	enableAlignReplace();
+	enableAlignmentReplace();
 	enableFontReplace();
-	enableSizeReplace();
-	enableEffReplace();
-	enableFillReplace();
-	enableFillSReplace();
-	enableStrokeReplace();
-	enableStrokeSReplace();
-
-	if (SText->isChecked() && RText->isChecked())
-		STextVal->setFocus();
+	enableFontSizeReplace();
+	enableFontEffectsReplace();
+	enableFillColorReplace();
+	enableFillShadeReplace();
+	enableStrokeColorReplace();
+	enableStrokeShadeReplace();
+
+	updateButtonState();
+
+	searchTextValue->setFocus();
+}
+
+void SearchReplace::accept()
+{
+	writePrefs();
+	QDialog::accept();
 }
 
 void SearchReplace::writePrefs()
 {
-	m_prefs->set("SStroke", SStroke->isChecked());
-	m_prefs->set("SFill", SFill->isChecked());
-	m_prefs->set("SStrokeS", SStrokeS->isChecked());
-	m_prefs->set("SFillS", SFillS->isChecked());
-	m_prefs->set("SSize", SSize->isChecked());
-	m_prefs->set("SFont", SFont->isChecked());
-	m_prefs->set("SStyle", SStyle->isChecked());
-	m_prefs->set("SAlign", SAlign->isChecked());
-	m_prefs->set("SText", SText->isChecked());
-	m_prefs->set("SEffect", SEffect->isChecked());
-	m_prefs->set("REffect", REffect->isChecked());
-	m_prefs->set("STextVal", STextVal->text());
-	m_prefs->set("SStyleVal", SStyleVal->currentIndex());
-	m_prefs->set("SAlignVal", SAlignVal->currentIndex());
-	m_prefs->set("SFontVal", SFontVal->currentText());
-	m_prefs->set("SSizeVal", SSizeVal->value());
-	m_prefs->set("SFillVal", SFillVal->currentText());
-	m_prefs->set("SStrokeVal", SStrokeVal->currentText());
-	m_prefs->set("RStroke", RStroke->isChecked());
-	m_prefs->set("RStrokeS", RStrokeS->isChecked());
-	m_prefs->set("RFill", RFill->isChecked());
-	m_prefs->set("RFillS", RFillS->isChecked());
-	m_prefs->set("RSize", RSize->isChecked());
-	m_prefs->set("RFont", RFont->isChecked());
-	m_prefs->set("RStyle", RStyle->isChecked());
-	m_prefs->set("RAlign", RAlign->isChecked());
-	m_prefs->set("RText", RText->isChecked());
-	m_prefs->set("RTextVal", RTextVal->text());
-	m_prefs->set("RStyleVal", RStyleVal->currentText());
-	m_prefs->set("RAlignVal", RAlignVal->currentIndex());
-	m_prefs->set("RFontVal", RFontVal->currentText());
-	m_prefs->set("RSizeVal", RSizeVal->value());
-	m_prefs->set("RFillVal", RFillVal->currentText());
-	m_prefs->set("RStrokeVal", RStrokeVal->currentText());
-	m_prefs->set("Word", Word->isChecked());
-	m_prefs->set("CaseIgnore", CaseIgnore->isChecked());
-	accept();
+	m_prefs->set("collapsed", m_stateCollapsed);
+	m_prefs->set("left", pos().x());
+	m_prefs->set("top", pos().y());
+	m_prefs->set("SStroke", searchStrokeColorCheckBox->isChecked());
+	m_prefs->set("SStroke", searchStrokeColorCheckBox->isChecked());
+	m_prefs->set("SFill", searchFillColorCheckBox->isChecked());
+	m_prefs->set("SStrokeS", searchStrokeShadeCheckBox->isChecked());
+	m_prefs->set("SFillS", replaceFillShadeCheckBox->isChecked());
+	m_prefs->set("SSize", searchFontSizeCheckBox->isChecked());
+	m_prefs->set("SFont", searchFontCheckBox->isChecked());
+	m_prefs->set("SStyle", replaceStyleCheckBox->isChecked());
+	m_prefs->set("SAlign", searchAlignmentCheckBox->isChecked());
+	m_prefs->set("SEffect", searchFontEffectsCheckBox->isChecked());
+	m_prefs->set("REffect", replaceFontEffectsCheckBox->isChecked());
+	m_prefs->set("STextVal", searchTextValue->text());
+	m_prefs->set("SStyleVal", searchStyleComboBox->currentIndex());
+	m_prefs->set("SAlignVal", searchAlignmentComboBox->currentIndex());
+	m_prefs->set("SFontVal", searchFontComboBox->currentText());
+	m_prefs->set("SSizeVal", searchFontSizeSpinBox->value());
+	m_prefs->set("SFillVal", searchFillColorComboBox->currentText());
+	m_prefs->set("SStrokeVal", searchStrokeColorComboBox->currentText());
+	m_prefs->set("RStroke", replaceStrokeColorCheckBox->isChecked());
+	m_prefs->set("RStrokeS", replaceStrokeShadeCheckBox->isChecked());
+	m_prefs->set("RFill", replaceFillColorCheckBox->isChecked());
+	m_prefs->set("RFillS", replaceFillShadeCheckBox->isChecked());
+	m_prefs->set("RSize", replaceFontSizeCheckBox->isChecked());
+	m_prefs->set("RFont", replaceFontCheckBox->isChecked());
+	m_prefs->set("RStyle", replaceStyleCheckBox->isChecked());
+	m_prefs->set("RAlign", replaceAlignmentCheckBox->isChecked());
+	m_prefs->set("RTextVal", replaceTextValue->text());
+	m_prefs->set("RStyleVal", replaceStyleComboBox->currentText());
+	m_prefs->set("RAlignVal", replaceAlignmentComboBox->currentIndex());
+	m_prefs->set("RFontVal", replaceFontComboBox->currentText());
+	m_prefs->set("RSizeVal", replaceFontSizeSpinBox->value());
+	m_prefs->set("RFillVal", replaceFillColorComboBox->currentText());
+	m_prefs->set("RStrokeVal", replaceStrokeColorComboBox->currentText());
+	m_prefs->set("Word", wholeWordsCheckBox->isChecked());
+	m_prefs->set("IgnoreCase", ignoreCaseCheckBox->isChecked());
 }
diff --git a/scribus/ui/search.h b/scribus/ui/search.h
index 0d0a42e20dbe9cb35d4a17421713f5c7c8302c62..52e684db55c07f84be2322932081ff18de7b383b 100644
--- a/scribus/ui/search.h
+++ b/scribus/ui/search.h
@@ -7,145 +7,173 @@ for which a new license (GPL+exception) is in place.
 #ifndef SEARCHREPLACE_H
 #define SEARCHREPLACE_H
 
-#include <QDialog>
-class QVBoxLayout;
-class QHBoxLayout;
-class QGridLayout;
-class QCheckBox;
-class QComboBox;
-class QGroupBox;
-class QLineEdit;
-class QPushButton;
-class QLabel;
+#include "ui_searchbase.h"
 
 #include "scribusapi.h"
-class ScrSpinBox;
-class FontCombo;
-class StyleSelect;
-class ShadeButton;
+
 class PrefsContext;
-class ColorCombo;
+class ParagraphStyle;
+class CharStyle;
+
 class ScribusDoc;
 class PageItem;
 
-class SCRIBUS_API SearchReplace : public QDialog
+class SCRIBUS_API SearchReplace : public QDialog, Ui::SearchReplaceBase
 {
 	Q_OBJECT
 
+private:
+	struct Options 
+	{
+		bool textEnabled{false};
+		bool ignoreCase{false};
+		bool wholeWords{false};
+		bool paragraphStyleEnabled{false};
+		bool alignmentEnabled{false};
+		bool fontEnabled{false};
+		bool fontSizeEnabled{false};
+		bool fontEffectsEnabled{false};
+		bool fillColorEnabled{false};
+		bool fillShadeEnabled{false};
+		bool strokeColorEnabled{false};
+		bool strokeShadeEnabled{false};
+
+		QString text{};
+		int paragraphStyle{0};
+		int alignment{0};
+		QString font{};
+		int fontSize{0};
+		int fontEffects{0};
+		QString fillColor{};
+		int fillColorId{0};
+		int fillShade{100};
+		QString strokeColor{};
+		int strokeColorId{0};
+		int strokeShade{100};
+	};
+
+	// TODO: as soon as we have c++ 17: std::optional<int> start
+	struct MatchRange
+	{
+		int start;
+		int end;
+		int nextPosition;
+	};
+
 public:
-	SearchReplace( QWidget* parent, ScribusDoc *doc, PageItem* ite, bool mode = true );
+	// mode is false when calling from the story editor
+	SearchReplace( QWidget* parent, ScribusDoc *doc);
 	~SearchReplace() {};
 
-	int firstMatchCursorPosition();
-	void setSearchedText(const QString& text);
-
-	QLabel* SText1;
-	QLabel* RText1;
-	QGroupBox* Search;
-	QCheckBox* SStroke;
-	QCheckBox* SFill;
-	QCheckBox* SStrokeS;
-	QCheckBox* SFillS;
-	QCheckBox* SSize;
-	QCheckBox* SFont;
-	QCheckBox* SStyle;
-	QCheckBox* SAlign;
-	QCheckBox* SText;
-	QCheckBox* SEffect;
-	QCheckBox* REffect;
-	QLineEdit* STextVal;
-	QComboBox* SStyleVal;
-	QComboBox* SAlignVal;
-	FontCombo* SFontVal;
-	ScrSpinBox* SSizeVal;
-	ColorCombo* SFillVal;
-	ShadeButton *SFillSVal;
-	ColorCombo* SStrokeVal;
-	ShadeButton *SStrokeSVal;
-	QGroupBox* Replace;
-	QCheckBox* RStroke;
-	QCheckBox* RStrokeS;
-	QCheckBox* RFill;
-	QCheckBox* RFillS;
-	QCheckBox* RSize;
-	QCheckBox* RFont;
-	QCheckBox* RStyle;
-	QCheckBox* RAlign;
-	QCheckBox* RText;
-	QLineEdit* RTextVal;
-	QComboBox* RStyleVal;
-	QComboBox* RAlignVal;
-	FontCombo* RFontVal;
-	ScrSpinBox* RSizeVal;
-	ColorCombo* RFillVal;
-	ShadeButton *RFillSVal;
-	ColorCombo* RStrokeVal;
-	ShadeButton *RStrokeSVal;
-	StyleSelect* SEffVal;
-	StyleSelect* REffVal;
-	QCheckBox* Word;
-	QCheckBox* CaseIgnore;
-	QPushButton* DoSearch;
-	QPushButton* DoReplace;
-	QPushButton* AllReplace;
-	QPushButton* clearButton;
-	QPushButton* Leave;
+	void setStoryEditorMode(bool mode = true) {m_storyEditorMode = true;}
+	QPair<int, int> cursorPosition();
+	//! \brief fill the text field with the current selection, if the selection does not contain a newline.
+	void processCurrentSelection(QString selection);
 
 public slots:
-	virtual void slotSearch();
-	virtual void slotReplace();
-	virtual void slotReplaceAll();
-	virtual void enableTxSearch();
-	virtual void enableStyleSearch();
-	virtual void enableAlignSearch();
-	virtual void enableFontSearch();
-	virtual void enableSizeSearch();
-	virtual void enableEffSearch();
-	virtual void enableFillSearch();
-	virtual void enableFillSSearch();
-	virtual void enableStrokeSearch();
-	virtual void enableStrokeSSearch();
-	virtual void enableTxReplace();
-	virtual void enableStyleReplace();
-	virtual void enableAlignReplace();
-	virtual void enableFontReplace();
-	virtual void enableSizeReplace();
-	virtual void enableEffReplace();
-	virtual void enableFillReplace();
-	virtual void enableFillSReplace();
-	virtual void enableStrokeReplace();
-	virtual void enableStrokeSReplace();
-	virtual void updateReplaceButtonsState();
-	virtual void updateSearchButtonState();
-	virtual void writePrefs();
-	virtual void clear();
-
-protected:
-	PageItem*   m_item;
+	void slotSearch();
+	void slotReplace();
+	void slotReplaceAll();
+	void slotCollapseFormat();
+	void enableTextSearch();
+	void enableStyleSearch();
+	void enableAlignmentSearch();
+	void enableFontSearch();
+	void enableFontSizeSearch();
+	void enableFontEffectsSearch();
+	void enableFillColorSearch();
+	void enableFillShadeSearch();
+	void enableStrokeColorSearch();
+	void enableStrokeShadeSearch();
+	void enableStyleReplace();
+	void enableAlignmentReplace();
+	void enableFontReplace();
+	void enableFontSizeReplace();
+	void enableFontEffectsReplace();
+	void enableFillColorReplace();
+	void enableFillShadeReplace();
+	void enableStrokeColorReplace();
+	void enableStrokeShadeReplace();
+	void updateButtonState();
+	void accept() override;
+	void clear();
+
+private:
+	PageItem*   m_item{nullptr};
 	ScribusDoc* m_doc;
 
+	bool m_storyEditorMode{false};
+
+	bool m_stateCollapsed{true};
+
 	uint m_replStart;
 	PrefsContext* m_prefs;
 	bool m_found { false };
 	bool m_itemMode;
 	bool m_replacingAll { false };
 
-	QVBoxLayout* SearchReplaceLayout;
-	QHBoxLayout* SelLayout;
-	QGridLayout* SearchLayout;
-	QGridLayout* ReplaceLayout;
-	QHBoxLayout* OptsLayout;
-	QHBoxLayout* ButtonsLayout;
+	void collapseFormat();
+	void readPrefs();
+	void writePrefs();
+
+	void hideMessage();
+	void showMessage(const QString& message);
+
+	void searchInStoryEditor();
+	void searchOnCanvas();
+	//! /brief Search storyText starting from start.
+	//! /param length The length of the story
+	//! /return The start and end position of the selection or (-1, -1) if nothing found.
+	QPair<int, int> searchStory(const StoryText& storyText, int start, const int length, const Options& options);
+
+	//! \brief Check if there is a match starting at position
+	//!
+	//! When textEnabled, the resulting start position might be different than position.
+	MatchRange getMatchRange(const StoryText& storyText, int position, const int length, const Options& options);
+	//! If the current selection matches the search, replace the selection.
+	// Otherwise do a search.
+	void replaceInStoryEditor();
+	void replaceOnCanvas();
+	//! \brief if the current selection matches the search, replace the selection.
+	// otherwise do a search.
+	void replaceSelectionInStoryEditor();
+	void replaceSelectionOnCanvas();
+	void replaceAllInStoryEditor();
+	void replaceAllOnCanvas();
+
+	//! \brief Initialize pageItems based on the current selection
+	void initCanvasSelection();
+	//! \brief Put all the document's text frames into pageItems
+	void readAllPageItems();
+	//! \brief Put all the selected text frames into pageItems
+	void readSelectedPageItems();
+	PageItem* currentPageItem();
+	void nextPageItem();
+	void selectPageItem(PageItem* pageItem);
+
+	size_t m_currentPageItem{0};
+	std::vector<PageItem*> m_pageItems{};
+	bool m_endReached{false};
+
+	void updatePageItemSelection(PageItem* pageItem);
+
+	void showNotFoundMessage();
 
-	virtual void doSearch();
-	virtual void doReplace();
-	virtual void showNotFoundMessage();
 
-	virtual void readPrefs();
+	// \brief is any search checkbox checked?
+	bool anySearchChecked();
+	// \brief is any replace checkbox checked?
+	bool anyReplaceChecked();
+	Options getSearchOptions();
+	Options getReplaceOptions();
+	// \brief Check for all search options but the text
+	bool isFormatMatching(const ParagraphStyle& parStyle, const CharStyle& charStyle, const Options& options);
 
 	/// Number of matches found thus far in a search
-	int m_matchesFound { 0 };
-	int m_firstMatchPosition { -1 };
+	int m_matchesFound {0};
+	// TODO: isn't 0, 0 good enough?
+	int m_selectionStart{-1};
+	int m_selectionEnd{-1};
 
 };
 
diff --git a/scribus/ui/searchbase.ui b/scribus/ui/searchbase.ui
new file mode 100644
index 0000000000000000000000000000000000000000..877ad4864bdd3a85ce47aac4f2cf9b4809207a8d
--- /dev/null
+++ b/scribus/ui/searchbase.ui
@@ -0,0 +1,594 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>SearchReplaceBase</class>
+ <widget class="QDialog" name="SearchReplaceBase">
+  <property name="windowModality">
+   <enum>Qt::ApplicationModal</enum>
+  </property>
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>651</width>
+    <height>459</height>
+   </rect>
+  </property>
+  <property name="sizePolicy">
+   <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+    <horstretch>0</horstretch>
+    <verstretch>0</verstretch>
+   </sizepolicy>
+  </property>
+  <property name="windowTitle">
+   <string>Search/Replace</string>
+  </property>
+  <property name="modal">
+   <bool>true</bool>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout_4">
+     <item>
+      <layout class="QGridLayout" name="gridLayout_2">
+       <item row="0" column="0">
+        <widget class="QLabel" name="searchTextLabel">
+         <property name="text">
+          <string>Search:</string>
+         </property>
+        </widget>
+       </item>
+       <item row="0" column="1">
+        <widget class="QLineEdit" name="searchTextValue">
+         <property name="enabled">
+          <bool>true</bool>
+         </property>
+         <property name="minimumSize">
+          <size>
+           <width>250</width>
+           <height>0</height>
+          </size>
+         </property>
+        </widget>
+       </item>
+       <item row="1" column="1">
+        <widget class="QCheckBox" name="wholeWordsCheckBox">
+         <property name="enabled">
+          <bool>false</bool>
+         </property>
+         <property name="text">
+          <string>&amp;Whole Word</string>
+         </property>
+        </widget>
+       </item>
+       <item row="2" column="1">
+        <widget class="QCheckBox" name="ignoreCaseCheckBox">
+         <property name="enabled">
+          <bool>false</bool>
+         </property>
+         <property name="text">
+          <string>&amp;Ignore Case, Diacritics and Kashida</string>
+         </property>
+        </widget>
+       </item>
+      </layout>
+     </item>
+     <item>
+      <layout class="QGridLayout" name="gridLayout_4">
+       <item row="0" column="0">
+        <widget class="QLabel" name="replaceTextLabel">
+         <property name="text">
+          <string>Replace:</string>
+         </property>
+        </widget>
+       </item>
+       <item row="0" column="1">
+        <widget class="QLineEdit" name="replaceTextValue">
+         <property name="enabled">
+          <bool>true</bool>
+         </property>
+         <property name="minimumSize">
+          <size>
+           <width>250</width>
+           <height>0</height>
+          </size>
+         </property>
+        </widget>
+       </item>
+       <item row="2" column="1">
+        <layout class="QHBoxLayout" name="horizontalLayout_2">
+         <item>
+          <spacer name="horizontalSpacer_2">
+           <property name="orientation">
+            <enum>Qt::Horizontal</enum>
+           </property>
+           <property name="sizeHint" stdset="0">
+            <size>
+             <width>40</width>
+             <height>20</height>
+            </size>
+           </property>
+          </spacer>
+         </item>
+         <item>
+          <widget class="QPushButton" name="collapseButton">
+           <property name="text">
+            <string>More...</string>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </item>
+       <item row="1" column="1">
+        <spacer name="horizontalSpacer_3">
+         <property name="orientation">
+          <enum>Qt::Horizontal</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>40</width>
+           <height>20</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+      </layout>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <widget class="QLabel" name="messageLabel">
+     <property name="enabled">
+      <bool>true</bool>
+     </property>
+     <property name="text">
+      <string>Feedback</string>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout">
+     <item>
+      <widget class="QGroupBox" name="searchGroupBox">
+       <property name="title">
+        <string/>
+       </property>
+       <layout class="QGridLayout" name="gridLayout">
+        <item row="11" column="1">
+         <widget class="ColorCombo" name="searchStrokeColorComboBox">
+          <property name="enabled">
+           <bool>false</bool>
+          </property>
+         </widget>
+        </item>
+        <item row="5" column="1">
+         <widget class="QComboBox" name="searchAlignmentComboBox">
+          <property name="enabled">
+           <bool>false</bool>
+          </property>
+         </widget>
+        </item>
+        <item row="11" column="0">
+         <widget class="QCheckBox" name="searchStrokeColorCheckBox">
+          <property name="text">
+           <string>Stroke Colour</string>
+          </property>
+         </widget>
+        </item>
+        <item row="10" column="1">
+         <widget class="ShadeButton" name="searchFillShadeToolButton">
+          <property name="enabled">
+           <bool>false</bool>
+          </property>
+          <property name="text">
+           <string>...</string>
+          </property>
+         </widget>
+        </item>
+        <item row="7" column="1">
+         <widget class="ScrSpinBox" name="searchFontSizeSpinBox">
+          <property name="enabled">
+           <bool>false</bool>
+          </property>
+          <property name="minimum">
+           <double>0.500000000000000</double>
+          </property>
+          <property name="maximum">
+           <double>2048.000000000000000</double>
+          </property>
+         </widget>
+        </item>
+        <item row="5" column="0">
+         <widget class="QCheckBox" name="searchAlignmentCheckBox">
+          <property name="text">
+           <string>Alignment</string>
+          </property>
+         </widget>
+        </item>
+        <item row="9" column="1">
+         <widget class="ColorCombo" name="searchFillColorComboBox">
+          <property name="enabled">
+           <bool>false</bool>
+          </property>
+         </widget>
+        </item>
+        <item row="12" column="0">
+         <widget class="QCheckBox" name="searchStrokeShadeCheckBox">
+          <property name="text">
+           <string>Stroke Shade</string>
+          </property>
+         </widget>
+        </item>
+        <item row="8" column="1">
+         <widget class="StyleSelect" name="searchFontEffects" native="true">
+          <property name="enabled">
+           <bool>false</bool>
+          </property>
+         </widget>
+        </item>
+        <item row="7" column="0">
+         <widget class="QCheckBox" name="searchFontSizeCheckBox">
+          <property name="text">
+           <string>Font Size</string>
+          </property>
+         </widget>
+        </item>
+        <item row="9" column="0">
+         <widget class="QCheckBox" name="searchFillColorCheckBox">
+          <property name="text">
+           <string>Fill Colour</string>
+          </property>
+         </widget>
+        </item>
+        <item row="6" column="0">
+         <widget class="QCheckBox" name="searchFontCheckBox">
+          <property name="text">
+           <string>Font</string>
+          </property>
+         </widget>
+        </item>
+        <item row="8" column="0">
+         <widget class="QCheckBox" name="searchFontEffectsCheckBox">
+          <property name="text">
+           <string>Font Effects</string>
+          </property>
+         </widget>
+        </item>
+        <item row="6" column="1">
+         <widget class="FontCombo" name="searchFontComboBox">
+          <property name="enabled">
+           <bool>false</bool>
+          </property>
+         </widget>
+        </item>
+        <item row="12" column="1">
+         <widget class="ShadeButton" name="searchStrokeShadeToolButton">
+          <property name="enabled">
+           <bool>false</bool>
+          </property>
+          <property name="text">
+           <string>...</string>
+          </property>
+         </widget>
+        </item>
+        <item row="4" column="1">
+         <widget class="QComboBox" name="searchStyleComboBox">
+          <property name="sizePolicy">
+           <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+            <horstretch>0</horstretch>
+            <verstretch>0</verstretch>
+           </sizepolicy>
+          </property>
+         </widget>
+        </item>
+        <item row="10" column="0">
+         <widget class="QCheckBox" name="searchFillShadeCheckBox">
+          <property name="text">
+           <string>Fill Shade</string>
+          </property>
+         </widget>
+        </item>
+        <item row="4" column="0">
+         <widget class="QCheckBox" name="searchStyleCheckBox">
+          <property name="text">
+           <string>Style</string>
+          </property>
+         </widget>
+        </item>
+       </layout>
+      </widget>
+     </item>
+     <item>
+      <widget class="QGroupBox" name="replaceGroupBox">
+       <property name="title">
+        <string/>
+       </property>
+       <layout class="QGridLayout" name="gridLayout_3">
+        <item row="5" column="0">
+         <widget class="QCheckBox" name="replaceFontEffectsCheckBox">
+          <property name="text">
+           <string>Font Effects</string>
+          </property>
+         </widget>
+        </item>
+        <item row="8" column="0">
+         <widget class="QCheckBox" name="replaceStrokeColorCheckBox">
+          <property name="text">
+           <string>Stroke Colour</string>
+          </property>
+         </widget>
+        </item>
+        <item row="1" column="0">
+         <widget class="QCheckBox" name="replaceStyleCheckBox">
+          <property name="text">
+           <string>Style</string>
+          </property>
+         </widget>
+        </item>
+        <item row="2" column="0">
+         <widget class="QCheckBox" name="replaceAlignmentCheckBox">
+          <property name="text">
+           <string>Alignment</string>
+          </property>
+         </widget>
+        </item>
+        <item row="6" column="0">
+         <widget class="QCheckBox" name="replaceFillColorCheckBox">
+          <property name="text">
+           <string>Fill Colour</string>
+          </property>
+         </widget>
+        </item>
+        <item row="3" column="0">
+         <widget class="QCheckBox" name="replaceFontCheckBox">
+          <property name="text">
+           <string>Font</string>
+          </property>
+         </widget>
+        </item>
+        <item row="4" column="0">
+         <widget class="QCheckBox" name="replaceFontSizeCheckBox">
+          <property name="text">
+           <string>Font Size</string>
+          </property>
+         </widget>
+        </item>
+        <item row="9" column="0">
+         <widget class="QCheckBox" name="replaceStrokeShadeCheckBox">
+          <property name="text">
+           <string>Stroke Shade</string>
+          </property>
+         </widget>
+        </item>
+        <item row="7" column="0">
+         <widget class="QCheckBox" name="replaceFillShadeCheckBox">
+          <property name="text">
+           <string>Fill Shade</string>
+          </property>
+         </widget>
+        </item>
+        <item row="1" column="1">
+         <widget class="QComboBox" name="replaceStyleComboBox">
+          <property name="enabled">
+           <bool>false</bool>
+          </property>
+          <property name="sizePolicy">
+           <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+            <horstretch>0</horstretch>
+            <verstretch>0</verstretch>
+           </sizepolicy>
+          </property>
+         </widget>
+        </item>
+        <item row="5" column="1">
+         <widget class="StyleSelect" name="replaceFontEffects" native="true">
+          <property name="enabled">
+           <bool>false</bool>
+          </property>
+         </widget>
+        </item>
+        <item row="4" column="1">
+         <widget class="ScrSpinBox" name="replaceFontSizeSpinBox">
+          <property name="enabled">
+           <bool>false</bool>
+          </property>
+          <property name="minimum">
+           <double>0.500000000000000</double>
+          </property>
+          <property name="maximum">
+           <double>2048.000000000000000</double>
+          </property>
+         </widget>
+        </item>
+        <item row="3" column="1">
+         <widget class="FontCombo" name="replaceFontComboBox">
+          <property name="enabled">
+           <bool>false</bool>
+          </property>
+         </widget>
+        </item>
+        <item row="2" column="1">
+         <widget class="QComboBox" name="replaceAlignmentComboBox">
+          <property name="enabled">
+           <bool>false</bool>
+          </property>
+         </widget>
+        </item>
+        <item row="6" column="1">
+         <widget class="ColorCombo" name="replaceFillColorComboBox">
+          <property name="enabled">
+           <bool>false</bool>
+          </property>
+         </widget>
+        </item>
+        <item row="7" column="1">
+         <widget class="ShadeButton" name="replaceFillShadeToolButton">
+          <property name="enabled">
+           <bool>false</bool>
+          </property>
+          <property name="text">
+           <string>...</string>
+          </property>
+         </widget>
+        </item>
+        <item row="8" column="1">
+         <widget class="ColorCombo" name="replaceStrokeColorComboBox">
+          <property name="enabled">
+           <bool>false</bool>
+          </property>
+         </widget>
+        </item>
+        <item row="9" column="1">
+         <widget class="ShadeButton" name="replaceStrokeShadeToolButton">
+          <property name="enabled">
+           <bool>false</bool>
+          </property>
+          <property name="text">
+           <string>...</string>
+          </property>
+         </widget>
+        </item>
+       </layout>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout_3">
+     <item>
+      <widget class="QPushButton" name="searchButton">
+       <property name="text">
+        <string>&amp;Search</string>
+       </property>
+       <property name="default">
+        <bool>true</bool>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="replaceButton">
+       <property name="enabled">
+        <bool>false</bool>
+       </property>
+       <property name="text">
+        <string>&amp;Replace</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="replaceAllButton">
+       <property name="enabled">
+        <bool>false</bool>
+       </property>
+       <property name="text">
+        <string>&amp;ReplaceAll</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <spacer name="horizontalSpacer">
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
+        <size>
+         <width>40</width>
+         <height>20</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+     <item>
+      <widget class="QPushButton" name="clearButton">
+       <property name="text">
+        <string>C&amp;lear</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="closeButton">
+       <property name="text">
+        <string>&amp;Close</string>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+  </layout>
+ </widget>
+ <customwidgets>
+  <customwidget>
+   <class>ScrSpinBox</class>
+   <extends>QDoubleSpinBox</extends>
+   <header>ui/scrspinbox.h</header>
+  </customwidget>
+  <customwidget>
+   <class>StyleSelect</class>
+   <extends>QWidget</extends>
+   <header>ui/styleselect.h</header>
+   <container>1</container>
+  </customwidget>
+  <customwidget>
+   <class>FontCombo</class>
+   <extends>QComboBox</extends>
+   <header>ui/fontcombo.h</header>
+  </customwidget>
+  <customwidget>
+   <class>ColorCombo</class>
+   <extends>QComboBox</extends>
+   <header>ui/colorcombo.h</header>
+  </customwidget>
+  <customwidget>
+   <class>ShadeButton</class>
+   <extends>QToolButton</extends>
+   <header>ui/shadebutton.h</header>
+  </customwidget>
+ </customwidgets>
+ <tabstops>
+  <tabstop>searchTextValue</tabstop>
+  <tabstop>replaceTextValue</tabstop>
+  <tabstop>searchButton</tabstop>
+  <tabstop>replaceButton</tabstop>
+  <tabstop>replaceAllButton</tabstop>
+  <tabstop>wholeWordsCheckBox</tabstop>
+  <tabstop>ignoreCaseCheckBox</tabstop>
+  <tabstop>collapseButton</tabstop>
+  <tabstop>searchStyleCheckBox</tabstop>
+  <tabstop>searchStyleComboBox</tabstop>
+  <tabstop>replaceStyleCheckBox</tabstop>
+  <tabstop>replaceStyleComboBox</tabstop>
+  <tabstop>searchAlignmentCheckBox</tabstop>
+  <tabstop>searchAlignmentComboBox</tabstop>
+  <tabstop>replaceAlignmentCheckBox</tabstop>
+  <tabstop>replaceAlignmentComboBox</tabstop>
+  <tabstop>searchFontCheckBox</tabstop>
+  <tabstop>searchFontComboBox</tabstop>
+  <tabstop>replaceFontCheckBox</tabstop>
+  <tabstop>replaceFontComboBox</tabstop>
+  <tabstop>searchFontSizeCheckBox</tabstop>
+  <tabstop>searchFontSizeSpinBox</tabstop>
+  <tabstop>replaceFontSizeCheckBox</tabstop>
+  <tabstop>replaceFontSizeSpinBox</tabstop>
+  <tabstop>searchFontEffectsCheckBox</tabstop>
+  <tabstop>replaceFontEffectsCheckBox</tabstop>
+  <tabstop>searchFillColorCheckBox</tabstop>
+  <tabstop>searchFillColorComboBox</tabstop>
+  <tabstop>replaceFillColorCheckBox</tabstop>
+  <tabstop>replaceFillColorComboBox</tabstop>
+  <tabstop>searchFillShadeCheckBox</tabstop>
+  <tabstop>searchFillShadeToolButton</tabstop>
+  <tabstop>replaceFillShadeCheckBox</tabstop>
+  <tabstop>replaceFillShadeToolButton</tabstop>
+  <tabstop>searchStrokeColorCheckBox</tabstop>
+  <tabstop>searchStrokeColorComboBox</tabstop>
+  <tabstop>replaceStrokeColorCheckBox</tabstop>
+  <tabstop>replaceStrokeColorComboBox</tabstop>
+  <tabstop>searchStrokeShadeCheckBox</tabstop>
+  <tabstop>searchStrokeShadeToolButton</tabstop>
+  <tabstop>replaceStrokeShadeCheckBox</tabstop>
+  <tabstop>replaceStrokeShadeToolButton</tabstop>
+  <tabstop>clearButton</tabstop>
+  <tabstop>closeButton</tabstop>
+ </tabstops>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/scribus/ui/storyeditor.cpp b/scribus/ui/storyeditor.cpp
index 645e625154d8158d96351e7e4874b5fc4ee53df3..286631b7d8e3632b003337385f53f3f78a4c9088 100644
--- a/scribus/ui/storyeditor.cpp
+++ b/scribus/ui/storyeditor.cpp
@@ -3032,16 +3032,24 @@ void StoryEditor::SearchText()
 {
 	m_blockUpdate = true;
 	EditorBar->setRepaint(false);
-	QScopedPointer<SearchReplace> dia(new SearchReplace(this, m_doc, m_item, false));
-	if (dia->exec())
+	SearchReplace dia(this, m_doc);
+
+	dia.setStoryEditorMode();
+
+	dia.processCurrentSelection(Editor->textCursor().selectedText());
+
+	if (dia.exec())
 	{
-		int pos = dia->firstMatchCursorPosition();
-		if (pos >= 0)
+		// TODO: when available: const auto [start, end] = ...
+		const auto pos = dia.cursorPosition();
+		if (pos.first >= 0)
 		{
-			QTextCursor tCursor = Editor->textCursor();
-			tCursor.setPosition(pos);
-			Editor->setTextCursor(tCursor);
-			Editor->SelStack.push(std::make_tuple(pos, -1, Editor->verticalScrollBar()->value()));
+			// TODO: why isn't enough to set the cursor in the search dialog?
+			QTextCursor cursor = Editor->textCursor();
+			cursor.setPosition(pos.first);
+			cursor.setPosition(pos.second, QTextCursor::KeepAnchor);
+			Editor->setTextCursor(cursor);
+			Editor->SelStack.push(std::make_tuple(pos.first, pos.second, Editor->verticalScrollBar()->value()));
 		}
 	}
 	qApp->processEvents();
search-and-replace.diff (121,047 bytes)

jfl

2020-05-07 05:32

reporter   ~0047599

Any news on this? This is a fundamental feature for any page layout software. I'm facing having to edit about 130 text frames in a book I'm working on and this is not a prospect I'm looking forward to. And editing the XML does not seem too straightforward either.

ale

2020-05-07 07:19

manager   ~0047600

@jfl as you see i got zero feedback from the team in about five months.

my contributions to scribus are basically on hold until i see what are the plans with this software.

jfl

2020-05-07 07:37

reporter   ~0047601

I hear you, ale. It looks like this project is in need of some serious management.

Issue History

Date Modified Username Field Change
2017-01-18 11:07 Fahad New Issue
2018-01-18 08:18 Fahad File Added: searchAll.patch
2018-01-18 08:18 Fahad Note Added: 0044857
2018-01-19 17:05 PeterBenedek Note Added: 0044860
2018-03-17 06:41 Fahad Note Added: 0045046
2018-03-17 13:15 ale File Added: search-and-replace.epgz
2018-03-17 13:15 ale Note Added: 0045047
2018-03-17 13:16 ale File Added: search-and-replace.png
2018-03-17 13:16 ale File Deleted: search-and-replace.png
2018-03-17 13:17 ale File Added: search-and-replace.png
2018-03-17 13:17 ale Note Added: 0045048
2018-03-17 13:18 ale Note Added: 0045050
2018-03-17 14:34 ale Note Added: 0045051
2018-03-18 03:54 Fahad Note Added: 0045053
2018-03-22 08:09 Fahad Note Added: 0045073
2018-05-15 13:38 ale Note Added: 0045247
2019-12-05 18:00 Fahad Note Added: 0047204
2019-12-06 13:13 ale Note Added: 0047206
2019-12-14 09:08 ale Tag Attached: search
2019-12-14 09:09 ale Note Added: 0047268
2019-12-16 18:34 ale Relationship added related to 0013483
2019-12-16 18:35 ale Relationship added related to 0012271
2019-12-16 18:36 ale Relationship deleted related to 0012271
2019-12-16 19:46 ale Relationship added has duplicate 0010477
2019-12-17 07:42 ale Assigned To => ale
2019-12-17 07:42 ale Status new => assigned
2019-12-20 13:29 ale File Added: search-and-replace.diff
2019-12-20 13:29 ale Note Added: 0047300
2019-12-20 13:29 ale Summary Search \ replace all text frames => [PATCH] Search \ replace all text frames
2019-12-20 13:29 ale Patch No => Yes
2019-12-20 17:28 ale Note Edited: 0047300 View Revisions
2020-05-07 05:32 jfl Note Added: 0047599
2020-05-07 07:19 ale Note Added: 0047600
2020-05-07 07:37 jfl Note Added: 0047601