View Issue Details
| ID | Project | Category | View Status | Date Submitted | Last Update |
|---|---|---|---|---|---|
| 0016197 | Scribus | Text Frames / Story Editor | public | 2020-07-29 20:20 | 2026-06-06 20:35 |
| Reporter | ale | Assigned To | |||
| Priority | normal | Severity | minor | Reproducibility | N/A |
| Status | new | Resolution | open | ||
| Product Version | 1.5.6.svn | ||||
| Summary | 0016197: RTL in the text ruler: tabs and margins | ||||
| Description | for RTL text, the ruler correctly counts from right to left. but the tabs do not work and the left / right margins are inversed. i'm working on a patch to fix this. | ||||
| Tags | No tags attached. | ||||
| Patch | No | ||||
|
|
i got the tabs to work. the margins do not work yet, but during the testing i'm doing the following changes: - when the left / right text margin is "not set" do not show the frame marings (the one marked with [ ]) - when grabbing the rulers items, first grab the text margins and then the frame margins. the behavior of the margins feels strange and further changes might be needed. - [x] instead of using blue marks, make the background white (Window) and the foreground black (Text) - [ ] highlight the margin marks on mouse over - [ ] increase the width area for selecting the margin marks - [ ] the frame margin is always available in the middle. in the higher and the lower part only if the margin marks are far enough. - [ ] when moving the left margin, the first line indent also moves - [ ] snap to 0 and end - [ ] snap the first line to the left margin - [ ] in text frames with multiple columns, only show the values for the current column - except for the left / right frame spacing - (since tabs and indents are paragraph based it does not make sense to show the current tabs and indents in other columns) - [x] simplify the scales in unitChange() - [x] do not show the numbers in the "page rule" when the text is being edited (only show numbers in the text part) - [x] do not show all black at very low zoom levels (< 3%) (basically, i'm copying how it works in libreoffice...) patch will follow as soon as it is ready. open questions: - in unitChange(), what should the values for m_iter and m_labelScale be, when sc < 0.1? |
|
|
@ale What are your finding in this issue? here what I think: - make rtl tabs work as ltr tabs which means: RTL left = LTR left the other tabs same. Tab stops must function independently of text direction I don't know if it is possible? what do you think? |
|
|
here is a patch adding RTL mode for hruler & tabruler. in md file you will find more detail about my approach of how I implement RTL ruler. RTL ruler.patch (45,068 bytes)
Index: scribus/scribus.cpp
===================================================================
--- scribus/scribus.cpp (revision 27622)
+++ scribus/scribus.cpp (working copy)
@@ -2727,6 +2727,9 @@
// to go: (av)
contentPalette->update(doc->currentStyle);
setStyleEffects(doc->currentStyle.charStyle().effects());
+ view->horizRuler->setItem(currItem);
+ view->horizRuler->textMode(false);
+ view->horizRuler->update();
}
break;
case PageItem::Table:
@@ -6254,6 +6257,11 @@
contentPalette->updateTextDirection(i);
PageItem *currItem = doc->m_Selection->itemAt(0);
setTBvals(currItem);
+ if (currItem && currItem->isTextFrame())
+ {
+ view->horizRuler->setRtl(i == 1); // 1 = RTL, 0 = LTR
+ view->horizRuler->update();
+ }
}
void ScribusMainWindow::setNewParStyle(const QString& name)
Index: scribus/ui/hruler.cpp
===================================================================
--- scribus/ui/hruler.cpp (revision 27622)
+++ scribus/ui/hruler.cpp (working copy)
@@ -71,7 +71,6 @@
unitChange();
languageChange();
setContextMenuPolicy(Qt::CustomContextMenu);
-
connect(this, SIGNAL(customContextMenuRequested(QPoint)), SLOT(contextMenuRequested(QPoint)));
connect(m_contextMenu, SIGNAL(triggered(QAction*)), SLOT(tabTypeChanged(QAction*)));
connect(ScQApp, SIGNAL(iconSetChanged()), this, SLOT(languageChange())); // replace languageChange() if there are other icons than in context menu.
@@ -100,7 +99,14 @@
double Hruler::localToTextPos(int x) const
{
- return ((x + m_view->contentsX()) / m_scaling - textBase());
+ double canvasPos = (x + m_view->contentsX()) / m_scaling;
+ if (m_rtl && m_currItem)
+ {
+ double colWidth = (textWidth() - m_colGap * (m_cols - 1)) / m_cols;
+ double colEnd = textBase() + (colWidth + m_colGap) * (m_currCol - 1.0) + colWidth;
+ return colEnd - canvasPos;
+ }
+ return canvasPos - textBase();
}
void Hruler::shift(double pos)
@@ -152,29 +158,38 @@
return result;
}
- pos = textPosToLocal(m_firstIndent + m_leftMargin + (colWidth + m_colGap) * (m_currCol - 1.0));
- fpo = QRect(pos - 1, topline, grabRadius + 1, rulerheight / 2);
+ double colOffset = (colWidth + m_colGap) * (m_currCol - 1.0);
+ double colEnd = colOffset + colWidth;
+
+ // Map a logical value (distance from logical start) to canvas text position
+ auto logicalToCanvas = [&](double v) -> double {
+ return m_rtl ? (colEnd - v) : (colOffset + v);
+ };
+
+ // First Indent handle
+ pos = textPosToLocal(logicalToCanvas(m_firstIndent + m_leftMargin));
+ fpo = QRect(m_rtl ? (pos - grabRadius) : (pos - 1), topline, grabRadius + 1, rulerheight / 2);
if (fpo.contains(mp))
- {
return rc_indentFirst;
- }
- pos = textPosToLocal(m_leftMargin + (colWidth + m_colGap) * (m_currCol - 1.0));
- fpo = QRect(pos - 1, midline, grabRadius + 1, rulerheight / 2);
+
+ // Left Margin handle
+ pos = textPosToLocal(logicalToCanvas(m_leftMargin));
+ fpo = QRect(m_rtl ? (pos - grabRadius) : (pos - 1), midline, grabRadius + 1, rulerheight / 2);
if (fpo.contains(mp))
- {
return rc_leftMargin;
- }
- pos = textPosToLocal(m_rightMargin + (colWidth + m_colGap) * (m_currCol - 1.0));
- fpo = QRect(pos - grabRadius, midline, grabRadius, rulerheight / 2);
+
+ // Right Margin handle — rightMargin is always from the logical end side
+ pos = textPosToLocal(colOffset + m_rightMargin);
+ fpo = QRect(m_rtl ? (pos - 1) : (pos - grabRadius), midline, grabRadius, rulerheight / 2);
if (fpo.contains(mp))
- {
return rc_rightMargin;
- }
- if (m_tabValues.count() != 0)
+
+ // Tab handles
+ if (!m_tabValues.isEmpty())
{
for (int yg = 0; yg < m_tabValues.count(); yg++)
{
- pos = textPosToLocal(m_tabValues[yg].tabPosition + (colWidth + m_colGap) * (m_currCol - 1.0));
+ pos = textPosToLocal(logicalToCanvas(m_tabValues[yg].tabPosition));
fpo = QRect(pos - grabRadius, tabline, 2 * grabRadius, rulerheight / 2 + 2);
if (fpo.contains(mp))
{
@@ -312,7 +327,7 @@
void Hruler::enterEvent(QEnterEvent* e)
{
if (m_textEditMode)
- QApplication::changeOverrideCursor(IconManager::instance().loadCursor("cursor-tabulator", 3));
+ QApplication::changeOverrideCursor(tabulatorCursor());
}
void Hruler::leaveEvent(QEvent *m)
@@ -347,10 +362,24 @@
QApplication::changeOverrideCursor(QCursor(Qt::SizeHorCursor));
double toplimit = textWidth() + m_distRight - (m_colGap * (m_cols - 1.0)) - 1.0;
double toplimit2 = textWidth() + m_distLeft - (m_colGap * (m_cols - 1.0)) - 1.0;
+ // In RTL dragging right moves away from the logical start (right edge),
+ // so negate the delta for all logical-position markers.
+ double dx = (m_mouseX - mousePos.x()) / m_scaling;
+ double logicalDx = m_rtl ? -dx : dx;
+ // Helper: convert a logical value (distance from logical start) to
+ // canvas-relative position (distance from textBase) for MarkerMoved.
+ // In LTR: canvasRel = logicalValue (distance from left edge)
+ // In RTL: canvasRel = colEnd - textBase() - logicalValue
+ double colOffset = (colWidth + m_colGap) * (m_currCol - 1.0);
+ auto toCanvasRel = [&](double logicalValue) -> double {
+ if (m_rtl)
+ return colOffset + colWidth - logicalValue;
+ return logicalValue;
+ };
switch (m_rulerCode)
{
case rc_leftFrameDist:
- m_distLeft -= (m_mouseX - mousePos.x()) / m_scaling;
+ m_distLeft -= dx;
if (m_distLeft < 0)
m_distLeft = 0;
if (m_distLeft > toplimit2)
@@ -359,7 +388,7 @@
repaint();
break;
case rc_rightFrameDist:
- m_distRight += (m_mouseX - mousePos.x()) / m_scaling;
+ m_distRight += dx;
if (m_distRight < 0)
m_distRight = 0;
if (m_distRight > toplimit)
@@ -368,41 +397,41 @@
repaint();
break;
case rc_indentFirst:
- m_firstIndent -= (m_mouseX - mousePos.x()) / m_scaling;
+ m_firstIndent -= logicalDx;
if (m_firstIndent + m_leftMargin < 0)
m_firstIndent = -m_leftMargin;
if (m_firstIndent + m_leftMargin > colWidth)
m_firstIndent = colWidth - m_leftMargin;
- emit MarkerMoved(textBase(), m_firstIndent + m_leftMargin);
+ emit MarkerMoved(textBase(), toCanvasRel(m_firstIndent + m_leftMargin));
repaint();
break;
case rc_leftMargin:
oldInd = m_leftMargin + m_firstIndent;
- m_leftMargin -= (m_mouseX - mousePos.x()) / m_scaling;
+ m_leftMargin -= logicalDx;
if (m_leftMargin < 0)
m_leftMargin = 0;
if (m_leftMargin > colWidth - 1)
m_leftMargin = colWidth - 1;
m_firstIndent = oldInd - m_leftMargin;
- emit MarkerMoved(textBase(), m_leftMargin);
+ emit MarkerMoved(textBase(), toCanvasRel(m_leftMargin));
repaint();
break;
case rc_rightMargin:
- m_rightMargin -= (m_mouseX - mousePos.x()) / m_scaling;
+ m_rightMargin -= logicalDx;
if (m_rightMargin < 0)
m_rightMargin = 0;
if (m_rightMargin > colWidth - 1)
m_rightMargin = colWidth - 1;
- emit MarkerMoved(textBase(), m_rightMargin);
+ emit MarkerMoved(textBase(), toCanvasRel(m_rightMargin));
repaint();
break;
case rc_tab:
- m_tabValues[m_currTab].tabPosition -= (m_mouseX - mousePos.x()) / m_scaling;
+ m_tabValues[m_currTab].tabPosition -= logicalDx;
if (m_tabValues[m_currTab].tabPosition < 0)
m_tabValues[m_currTab].tabPosition = 0;
if (m_tabValues[m_currTab].tabPosition > colWidth - 1)
m_tabValues[m_currTab].tabPosition = colWidth - 1;
- emit MarkerMoved(textBase(), m_tabValues[m_currTab].tabPosition);
+ emit MarkerMoved(textBase(), toCanvasRel(m_tabValues[m_currTab].tabPosition));
updateTabList();
repaint();
break;
@@ -414,24 +443,16 @@
}
if ((!m_mousePressed) && (mousePos.y() < height()) && (mousePos.y() > 0) && (mousePos.x() > colStart - 2 * m_doc->guidesPrefs().grabRadius) && (mousePos.x() < colEnd + 2 * m_doc->guidesPrefs().grabRadius))
{
- setCursor(IconManager::instance().loadCursor("cursor-tabulator", 3));
+ setCursor(tabulatorCursor());
switch (findRulerHandle(m->pos(), m_doc->guidesPrefs().grabRadius))
{
case rc_leftFrameDist:
- setCursor(QCursor(Qt::SplitHCursor));
- break;
case rc_rightFrameDist:
setCursor(QCursor(Qt::SplitHCursor));
break;
case rc_indentFirst:
- setCursor(QCursor(Qt::SizeHorCursor));
- break;
case rc_leftMargin:
- setCursor(QCursor(Qt::SizeHorCursor));
- break;
case rc_rightMargin:
- setCursor(QCursor(Qt::SizeHorCursor));
- break;
case rc_tab:
setCursor(QCursor(Qt::SizeHorCursor));
break;
@@ -439,8 +460,8 @@
break;
}
draw(mousePos.x());
- double marker = localToTextPos(mousePos.x());
- emit MarkerMoved(textBase(), marker);
+ double canvasX = (mousePos.x() + m_view->contentsX()) / m_scaling;
+ emit MarkerMoved(textBase(), canvasX - textBase());
return;
}
if (m_mousePressed && (m_rulerCode == rc_tab) && ((mousePos.y() > height()) || (mousePos.y() < 0)))
@@ -479,6 +500,7 @@
const ParagraphStyle& style(m_doc->appMode == modeEdit ? tItem->currentStyle() : tItem->itemText.defaultStyle());
TabManager *dia = new TabManager(this, m_doc->unitIndex(), style.tabValues(), tItem->columnWidth());
+ dia->setRtl(m_rtl);
if (dia->exec())
{
ParagraphStyle paraStyle;
@@ -520,7 +542,6 @@
QColor columnColor = markerColor;
columnColor.setAlphaF(0.2f);
const QColor& textColor = palette.color(QPalette::Text);
-
p.save();
if (m_reverse)
@@ -540,101 +561,179 @@
p.fillRect(QRect(QPoint(textPosToLocal(pos), 0), QPoint(textPosToLocal(endPos), bottomline)), columnColor);
drawTextMarks(pos, endPos, p);
-
- // start bracket
- p.setPen(QPen(marginColor, 1, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
- int xPos = textPosToLocal(pos);
- p.drawLine(xPos, 0, xPos, bottomline);
- p.drawLine(xPos, 0, (xPos + 8), 0);
-
- // First Indent
- xPos = textPosToLocal(pos + m_firstIndent + m_leftMargin);
- p.setPen(QPen(textColor, 1, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
- p.drawLine(xPos, tabline - 2, xPos, bottomline);
- p.setPen(Qt::NoPen);
- p.setRenderHints(QPainter::Antialiasing, true);
- p.drawRect(QRect(xPos, tabline - 8, 8, 6));
- // Left Margin
- xPos = textPosToLocal(pos + m_leftMargin);
- QPolygon cr2;
- cr2.setPoints(3, xPos, tabline, xPos + 8, tabline, xPos, bottomline);
- p.drawPolygon(cr2);
-
- // Right Margin
- xPos = textPosToLocal(pos + m_rightMargin);
- QPolygon cr3;
- cr3.setPoints(3, xPos - 8, tabline, xPos, tabline, xPos, bottomline);
- p.drawPolygon(cr3);
+ if (m_rtl)
+ {
+ // RTL: logical start is the RIGHT edge of the column
- p.setRenderHints(QPainter::Antialiasing, false);
+ // start bracket on the right
+ p.setPen(QPen(marginColor, 1, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
+ int xPos = textPosToLocal(endPos);
+ p.drawLine(xPos, 0, xPos, bottomline);
+ p.drawLine(xPos, 0, xPos - 8, 0);
- // Tabulator
- if (!m_tabValues.isEmpty())
- {
- int pWidth = 1;
- p.setPen(QPen(textColor, pWidth * 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
- for (int yg = 0; yg < m_tabValues.count(); ++yg)
+ // First Indent - measured from right edge inward
+ xPos = textPosToLocal(endPos - m_leftMargin - m_firstIndent);
+ p.setPen(QPen(textColor, 1, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
+ p.drawLine(xPos, tabline - 2, xPos, bottomline);
+ p.setPen(Qt::NoPen);
+ p.setRenderHints(QPainter::Antialiasing, true);
+ p.drawRect(QRect(xPos - 8, tabline - 8, 8, 6));
+
+ // Left Margin (logical start = right side), triangle opens left
+ xPos = textPosToLocal(endPos - m_leftMargin);
+ QPolygon cr2;
+ cr2.setPoints(3, xPos, tabline, xPos - 8, tabline, xPos, bottomline);
+ p.drawPolygon(cr2);
+
+ // Right Margin (logical end = left side), triangle opens right
+ xPos = textPosToLocal(pos + m_rightMargin);
+ QPolygon cr3;
+ cr3.setPoints(3, xPos, tabline, xPos + 8, tabline, xPos, bottomline);
+ p.drawPolygon(cr3);
+
+ p.setRenderHints(QPainter::Antialiasing, false);
+
+ // Tabulators - measured from right edge
+ if (!m_tabValues.isEmpty())
{
- xPos = textPosToLocal(pos + m_tabValues[yg].tabPosition);
- if (pos + m_tabValues[yg].tabPosition >= endPos)
- continue;
- switch (m_tabValues[yg].tabType)
+ int pWidth = 1;
+ p.setPen(QPen(textColor, pWidth * 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
+ for (int yg = 0; yg < m_tabValues.count(); ++yg)
{
- case ParagraphStyle::LeftTab:
- if (m_reverse)
- {
- p.save();
- p.translate(pos + m_tabValues[yg].tabPosition, 0);
- p.scale(-1, 1);
- p.drawLine(0, tabline, 0, bottomline - pWidth);
- p.drawLine(0, bottomline - pWidth, 8, bottomline - pWidth);
- p.restore();
- }
- else
- {
+ xPos = textPosToLocal(endPos - m_tabValues[yg].tabPosition);
+ if (m_tabValues[yg].tabPosition >= colWidth)
+ continue;
+ switch (m_tabValues[yg].tabType)
+ {
+ case ParagraphStyle::LeftTab:
p.drawLine(xPos, tabline, xPos, bottomline - pWidth);
+ p.drawLine(xPos, bottomline - pWidth, xPos - 8, bottomline - pWidth);
+ break;
+ case ParagraphStyle::RightTab:
+ p.drawLine(xPos, tabline, xPos, bottomline - pWidth);
p.drawLine(xPos, bottomline - pWidth, xPos + 8, bottomline - pWidth);
- }
- break;
- case ParagraphStyle::RightTab:
- if (m_reverse)
- {
- p.save();
- p.translate(pos + m_tabValues[yg].tabPosition, 0);
- p.scale(-1, 1);
- p.drawLine(0, tabline, 0, bottomline - pWidth);
- p.drawLine(0, bottomline - pWidth, -8, bottomline - pWidth);
- p.restore();
- }
- else
- {
+ break;
+ case ParagraphStyle::CommaTab:
+ case ParagraphStyle::PeriodTab:
p.drawLine(xPos, tabline, xPos, bottomline - pWidth);
- p.drawLine(xPos, bottomline - pWidth, xPos - 8, bottomline - pWidth);
- }
- break;
- case ParagraphStyle::CommaTab:
- case ParagraphStyle::PeriodTab:
- p.drawLine(xPos, tabline, xPos, bottomline - pWidth);
- p.drawLine(xPos - 4, bottomline - pWidth, xPos + 4, bottomline - pWidth);
- p.drawLine(xPos + 3, bottomline - 3 - pWidth, xPos + 2, bottomline - 3 - pWidth);
- break;
- case ParagraphStyle::CenterTab:
- p.drawLine(xPos, tabline, xPos, bottomline - pWidth);
- p.drawLine(xPos - 4, bottomline - pWidth, xPos + 4, bottomline - pWidth);
- break;
- default:
- break;
+ p.drawLine(xPos - 4, bottomline - pWidth, xPos + 4, bottomline - pWidth);
+ p.drawLine(xPos - 3, bottomline - 3 - pWidth, xPos - 2, bottomline - 3 - pWidth);
+ break;
+ case ParagraphStyle::CenterTab:
+ p.drawLine(xPos, tabline, xPos, bottomline - pWidth);
+ p.drawLine(xPos - 4, bottomline - pWidth, xPos + 4, bottomline - pWidth);
+ break;
+ default:
+ break;
+ }
}
}
+
+ // end bracket on the left
+ p.setPen(QPen(marginColor, 1, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
+ xPos = textPosToLocal(pos);
+ p.drawLine(xPos, 0, xPos, bottomline - 1);
+ p.drawLine(xPos, 0, xPos + 8, 0);
}
-
- // end bracket
- p.setPen(QPen(marginColor, 1, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
- xPos = textPosToLocal(endPos);
- p.drawLine(xPos, 0, xPos, bottomline - 1);
- p.drawLine(xPos, 0, xPos - 8, 0);
+ else
+ {
+ // LTR: original logic
+ // start bracket
+ p.setPen(QPen(marginColor, 1, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
+ int xPos = textPosToLocal(pos);
+ p.drawLine(xPos, 0, xPos, bottomline);
+ p.drawLine(xPos, 0, (xPos + 8), 0);
+
+ // First Indent
+ xPos = textPosToLocal(pos + m_firstIndent + m_leftMargin);
+ p.setPen(QPen(textColor, 1, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
+ p.drawLine(xPos, tabline - 2, xPos, bottomline);
+ p.setPen(Qt::NoPen);
+ p.setRenderHints(QPainter::Antialiasing, true);
+ p.drawRect(QRect(xPos, tabline - 8, 8, 6));
+
+ // Left Margin
+ xPos = textPosToLocal(pos + m_leftMargin);
+ QPolygon cr2;
+ cr2.setPoints(3, xPos, tabline, xPos + 8, tabline, xPos, bottomline);
+ p.drawPolygon(cr2);
+
+ // Right Margin
+ xPos = textPosToLocal(pos + m_rightMargin);
+ QPolygon cr3;
+ cr3.setPoints(3, xPos - 8, tabline, xPos, tabline, xPos, bottomline);
+ p.drawPolygon(cr3);
+
+ p.setRenderHints(QPainter::Antialiasing, false);
+
+ // Tabulator
+ if (!m_tabValues.isEmpty())
+ {
+ int pWidth = 1;
+ p.setPen(QPen(textColor, pWidth * 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
+ for (int yg = 0; yg < m_tabValues.count(); ++yg)
+ {
+ xPos = textPosToLocal(pos + m_tabValues[yg].tabPosition);
+ if (pos + m_tabValues[yg].tabPosition >= endPos)
+ continue;
+ switch (m_tabValues[yg].tabType)
+ {
+ case ParagraphStyle::LeftTab:
+ if (m_reverse)
+ {
+ p.save();
+ p.translate(pos + m_tabValues[yg].tabPosition, 0);
+ p.scale(-1, 1);
+ p.drawLine(0, tabline, 0, bottomline - pWidth);
+ p.drawLine(0, bottomline - pWidth, 8, bottomline - pWidth);
+ p.restore();
+ }
+ else
+ {
+ p.drawLine(xPos, tabline, xPos, bottomline - pWidth);
+ p.drawLine(xPos, bottomline - pWidth, xPos + 8, bottomline - pWidth);
+ }
+ break;
+ case ParagraphStyle::RightTab:
+ if (m_reverse)
+ {
+ p.save();
+ p.translate(pos + m_tabValues[yg].tabPosition, 0);
+ p.scale(-1, 1);
+ p.drawLine(0, tabline, 0, bottomline - pWidth);
+ p.drawLine(0, bottomline - pWidth, -8, bottomline - pWidth);
+ p.restore();
+ }
+ else
+ {
+ p.drawLine(xPos, tabline, xPos, bottomline - pWidth);
+ p.drawLine(xPos, bottomline - pWidth, xPos - 8, bottomline - pWidth);
+ }
+ break;
+ case ParagraphStyle::CommaTab:
+ case ParagraphStyle::PeriodTab:
+ p.drawLine(xPos, tabline, xPos, bottomline - pWidth);
+ p.drawLine(xPos - 4, bottomline - pWidth, xPos + 4, bottomline - pWidth);
+ p.drawLine(xPos + 3, bottomline - 3 - pWidth, xPos - 2, bottomline - 3 - pWidth);
+ break;
+ case ParagraphStyle::CenterTab:
+ p.drawLine(xPos, tabline, xPos, bottomline - pWidth);
+ p.drawLine(xPos - 4, bottomline - pWidth, xPos + 4, bottomline - pWidth);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ // end bracket
+ p.setPen(QPen(marginColor, 1, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
+ xPos = textPosToLocal(endPos);
+ p.drawLine(xPos, 0, xPos, bottomline - 1);
+ p.drawLine(xPos, 0, xPos - 8, 0);
+ }
}
p.restore();
}
@@ -684,17 +783,21 @@
firstMark = ceil(m_offset / m_iter2) * m_iter2 - m_offset;
int markC = static_cast<int>(ceil(m_offset / m_iter2));
+ const bool isRtl = m_rtl && (m_currItem != nullptr);
+ const int pageRightMarkC = static_cast<int>(round(m_doc->currentPage()->width() / m_iter2));
+
QString tx;
double xl, frac;
while (firstMark < cc)
{
+ int effectiveMarkC = isRtl ? (pageRightMarkC - markC) : markC;
switch (m_doc->unitIndex())
{
case SC_MM:
- tx = QString::number(markC * m_iter2 / (m_iter2 / 100) / m_cor);
+ tx = QString::number(effectiveMarkC * m_iter2 / (m_iter2 / 100) / m_cor);
break;
case SC_IN:
- xl = (markC * m_iter2 / m_iter2) / m_cor;
+ xl = (effectiveMarkC * m_iter2 / m_iter2) / m_cor;
tx = QString::number(static_cast<int>(xl));
frac = fabs(xl - static_cast<int>(xl));
if ((static_cast<int>(xl) == 0) && (frac > 0.1))
@@ -708,13 +811,13 @@
break;
case SC_P:
case SC_C:
- tx = QString::number(markC * m_iter2 / (m_iter2 / 5) / m_cor);
+ tx = QString::number(effectiveMarkC * m_iter2 / (m_iter2 / 5) / m_cor);
break;
case SC_CM:
- tx = QString::number(markC * m_iter2 / m_iter2 / m_cor);
+ tx = QString::number(effectiveMarkC * m_iter2 / m_iter2 / m_cor);
break;
default:
- tx = QString::number(markC * m_iter2);
+ tx = QString::number(effectiveMarkC * m_iter2);
break;
}
p.drawLine(qRound(firstMark * sc), scaleL, qRound(firstMark * sc), bottomline);
@@ -748,19 +851,35 @@
int xli = textPosToLocal(xl);
p.drawLine(xli, scaleS, xli, bottomline);
}
- for (xl = pos; xl < endPos; xl += m_iter2)
+ // LTR: start at pos, step right, dist = xl - pos (0 at left edge, grows right)
+ // RTL: start at endPos, step left, dist = endPos - xl (0 at right edge, grows left)
+ double xlStart = m_rtl ? endPos : pos;
+ double xlStep = m_rtl ? -m_iter2 : m_iter2;
+ for (xl = xlStart; m_rtl ? (xl >= pos) : (xl < endPos); xl += xlStep)
{
int xli = textPosToLocal(xl);
p.drawLine(xli, scaleL, xli, bottomline);
+ double dist = m_rtl ? (endPos - xl) : (xl - pos);
+ auto drawNum = [&](const QString& tx) {
+ if (m_reverse) {
+ p.save();
+ p.translate(xli - 2.0, 0);
+ p.scale(-1, 1);
+ drawNumber(tx, 0, p);
+ p.restore();
+ } else {
+ drawNumber(tx, xli + 2, p);
+ }
+ };
switch (m_doc->unitIndex())
{
case SC_IN:
{
QString tx;
- int num1 = static_cast<int>((xl - pos) / m_iter2 / m_cor);
+ int num1 = static_cast<int>(dist / m_iter2 / m_cor);
if (num1 != 0)
tx = QString::number(num1);
- double frac = (xl / m_iter2 / m_cor) - num1;
+ double frac = (dist / m_iter2 / m_cor) - num1;
if ((frac > 0.24) && (frac < 0.26))
tx += QChar(0xBC);
if ((frac > 0.49) && (frac < 0.51))
@@ -767,65 +886,20 @@
tx += QChar(0xBD);
if ((frac > 0.74) && (frac < 0.76))
tx += QChar(0xBE);
- if (m_reverse)
- {
- p.save();
- p.translate(xli - 2.0, 0);
- p.scale(-1, 1);
- drawNumber(tx, 0, p);
- p.restore();
- }
- else
- drawNumber(tx, xli + 2, p);
+ drawNum(tx);
break;
}
case SC_P:
- if (m_reverse)
- {
- p.save();
- p.translate(xli - 2.0, 0);
- p.scale(-1, 1);
- drawNumber(QString::number((xl - pos) / m_iter / m_cor), 0, p);
- p.restore();
- }
- else
- drawNumber(QString::number((xl - pos) / m_iter / m_cor), xli + 2, p);
+ drawNum(QString::number(dist / m_iter / m_cor));
break;
case SC_CM:
- if (m_reverse)
- {
- p.save();
- p.translate(xli - 2.0, 0);
- p.scale(-1, 1);
- drawNumber(QString::number((xl - pos) / m_iter / 10 / m_cor), 0, p);
- p.restore();
- }
- else
- drawNumber(QString::number((xl - pos) / m_iter / 10 / m_cor), xli + 2, p);
+ drawNum(QString::number(dist / m_iter / 10 / m_cor));
break;
case SC_C:
- if (m_reverse)
- {
- p.save();
- p.translate(xli - 2.0, 0);
- p.scale(-1, 1);
- drawNumber(QString::number((xl - pos) / m_iter / m_cor), 0, p);
- p.restore();
- }
- else
- drawNumber(QString::number((xl - pos) / m_iter / m_cor), xli + 2, p);
+ drawNum(QString::number(dist / m_iter / m_cor));
break;
default:
- if (m_reverse)
- {
- p.save();
- p.translate(xli - 2.0, 0);
- p.scale(-1, 1);
- drawNumber(QString::number((xl - pos) / m_iter * 10 / m_cor), 0, p);
- p.restore();
- }
- else
- drawNumber(QString::number((xl - pos) / m_iter * 10 / m_cor), xli + 2, p);
+ drawNum(QString::number(dist / m_iter * 10 / m_cor));
break;
}
}
@@ -846,6 +920,18 @@
p.drawText(x,y,txt);
}
+QCursor Hruler::tabulatorCursor() const
+{
+ QCursor c = IconManager::instance().loadCursor("cursor-tabulator", 3);
+ if (m_rtl)
+ {
+ QPixmap pix = c.pixmap();
+ QPixmap mirrored = pix.transformed(QTransform(-1, 0, 0, 1, 0, 0));
+ c = QCursor(mirrored, pix.width() - c.hotSpot().x() - 1, c.hotSpot().y());
+ }
+ return c;
+}
+
double Hruler::ruleSpacing() const
{
return m_iter;
@@ -899,10 +985,17 @@
- 2 * m_lineCorr) / item->columns();
m_rightMargin = columnWidth - currentStyle.rightMargin();
m_reverse = item->imageFlippedH();
+ setRtl(item->currentStyle().direction() == ParagraphStyle::RTL);
m_textEditMode = true;
m_tabValues = currentStyle.tabValues();
}
+void Hruler::setRtl(bool rtl)
+{
+ m_rtl = rtl;
+ languageChange();
+}
+
void Hruler::updateTabList()
{
ParagraphStyle::TabRecord tb;
@@ -1087,11 +1180,22 @@
QSignalBlocker sigMenu(m_contextMenu);
m_contextMenu->clear();
- m_contextMenu->addAction(im.loadIcon("tabulator-left"), tr("Left"))->setData(ParagraphStyle::LeftTab);
- m_contextMenu->addAction(im.loadIcon("tabulator-center"), tr("Center"))->setData(ParagraphStyle::CenterTab);
- m_contextMenu->addAction(im.loadIcon("tabulator-comma"), tr("Comma"))->setData(ParagraphStyle::CommaTab);
- m_contextMenu->addAction(im.loadIcon("tabulator-dot"), tr("Period"))->setData(ParagraphStyle::PeriodTab);
- m_contextMenu->addAction(im.loadIcon("tabulator-right"), tr("Right"))->setData(ParagraphStyle::RightTab);
+ if (m_rtl)
+ {
+ m_contextMenu->addAction(im.loadIcon("tabulator-right"), tr("Right"))->setData(ParagraphStyle::LeftTab);
+ m_contextMenu->addAction(im.loadIcon("tabulator-center"), tr("Center"))->setData(ParagraphStyle::CenterTab);
+ m_contextMenu->addAction(im.loadIcon("tabulator-comma"), tr("Comma"))->setData(ParagraphStyle::CommaTab);
+ m_contextMenu->addAction(im.loadIcon("tabulator-dot"), tr("Period"))->setData(ParagraphStyle::PeriodTab);
+ m_contextMenu->addAction(im.loadIcon("tabulator-left"), tr("Left"))->setData(ParagraphStyle::RightTab);
+ }
+ else
+ {
+ m_contextMenu->addAction(im.loadIcon("tabulator-left"), tr("Left"))->setData(ParagraphStyle::LeftTab);
+ m_contextMenu->addAction(im.loadIcon("tabulator-center"), tr("Center"))->setData(ParagraphStyle::CenterTab);
+ m_contextMenu->addAction(im.loadIcon("tabulator-comma"), tr("Comma"))->setData(ParagraphStyle::CommaTab);
+ m_contextMenu->addAction(im.loadIcon("tabulator-dot"), tr("Period"))->setData(ParagraphStyle::PeriodTab);
+ m_contextMenu->addAction(im.loadIcon("tabulator-right"), tr("Right"))->setData(ParagraphStyle::RightTab);
+ }
}
void Hruler::contextMenuRequested(QPoint mouse)
Index: scribus/ui/hruler.h
===================================================================
--- scribus/ui/hruler.h (revision 27622)
+++ scribus/ui/hruler.h (working copy)
@@ -66,6 +66,7 @@
double ruleSpacing() const;
void setItem(PageItem * item);
+ void setRtl(bool rtl);
void textMode(bool state) { m_textEditMode = state; }
double textBase() const; // left text edge in canvas coord
double textWidth() const;
@@ -97,6 +98,7 @@
void drawMarker(QPainter& p) const;
void drawNumber(const QString& num, int startx, QPainter & p) const;
void updateTabList();
+ QCursor tabulatorCursor() const;
PageItem* m_currItem {nullptr};
QList<ParagraphStyle::TabRecord> m_tabValues;
@@ -106,6 +108,7 @@
bool m_drawMark {false};
bool m_mousePressed {false};
bool m_reverse {false};
+ bool m_rtl {false};
bool m_textEditMode {false};
double m_colGap {0.0};
double m_cor {0.0};
Index: scribus/ui/propertywidget_alignment.cpp
===================================================================
--- scribus/ui/propertywidget_alignment.cpp (revision 27622)
+++ scribus/ui/propertywidget_alignment.cpp (working copy)
@@ -17,8 +17,10 @@
#include "scribus.h"
#include "scribusapp.h"
#include "scribusdoc.h"
+#include "scribusview.h"
#include "selection.h"
#include "tabmanager.h"
+#include "hruler.h"
PropertyWidget_Alignment::PropertyWidget_Alignment(QWidget* parent) : QFrame(parent)
{
@@ -328,6 +330,12 @@
m_doc->itemSelection_SetAlignment(ParagraphStyle::LeftAligned, &tempSelection);
textAlignment->setTypeStyle(ParagraphStyle::LeftAligned);
}
+ // Update the horizontal ruler to reflect the new direction
+ if (m_item->isTextFrame())
+ {
+ m_ScMW->view->horizRuler->setRtl(d == ParagraphStyle::RTL);
+ m_ScMW->view->horizRuler->update();
+ }
}
void PropertyWidget_Alignment::handleFirstLinePolicy(int radioFlop)
Index: scribus/ui/smpstylewidget.cpp
===================================================================
--- scribus/ui/smpstylewidget.cpp (revision 27622)
+++ scribus/ui/smpstylewidget.cpp (working copy)
@@ -265,6 +265,7 @@
tabList->setTabs(tabs, unitIndex, hasParentTabs);
tabList->setParentTabs(parent->tabValues());
+ tabList->setRtl(pstyle->direction() == ParagraphStyle::RTL);
tabList->setLeftIndentValue(pstyle->leftMargin() * unitRatio,pstyle->isInhLeftMargin());
tabList->setParentLeftIndent(parent->leftMargin() * unitRatio);
@@ -386,6 +387,7 @@
alignment->setStyle(pstyle->alignment(), direction->getStyle());
tabList->setTabs(pstyle->tabValues(), unitIndex);
+ tabList->setRtl(pstyle->direction() == ParagraphStyle::RTL);
tabList->setLeftIndentValue(pstyle->leftMargin() * unitRatio);
tabList->setFirstLineValue(pstyle->firstIndent() * unitRatio);
tabList->setRightIndentValue(pstyle->rightMargin() * unitRatio);
@@ -759,6 +761,18 @@
}
tabList->setTabs(t, unitIndex);
+ // Mirror ruler for RTL — only if all selected styles agree on RTL direction
+ bool isRtl = (pstyles[0]->direction() == ParagraphStyle::RTL);
+ for (int i = 1; i < pstyles.count(); ++i)
+ {
+ if (pstyles[i]->direction() != pstyles[0]->direction())
+ {
+ isRtl = false;
+ break;
+ }
+ }
+ tabList->setRtl(isRtl);
+
double l = -4000.0;
for (int i = 0; i < pstyles.count(); ++i)
{
Index: scribus/ui/smtextstyles.cpp
===================================================================
--- scribus/ui/smtextstyles.cpp (revision 27622)
+++ scribus/ui/smtextstyles.cpp (working copy)
@@ -758,6 +758,9 @@
for (int i = 0; i < m_selection.count(); ++i)
m_selection[i]->setDirection(style);
+ // Mirror the tab ruler to match the new direction
+ m_pwidget->tabList->setRtl(style == ParagraphStyle::RTL);
+
slotSelectionDirty();
}
Index: scribus/ui/tabmanager.cpp
===================================================================
--- scribus/ui/tabmanager.cpp (revision 27622)
+++ scribus/ui/tabmanager.cpp (working copy)
@@ -60,3 +60,8 @@
{
m_tabList = tabList;
}
+
+void TabManager::setRtl(bool rtl)
+{
+ TabList->setRtl(rtl);
+}
Index: scribus/ui/tabmanager.h
===================================================================
--- scribus/ui/tabmanager.h (revision 27622)
+++ scribus/ui/tabmanager.h (working copy)
@@ -30,6 +30,7 @@
public slots:
void exitOK();
void setTabList(const QList<ParagraphStyle::TabRecord>& tabList);
+ void setRtl(bool rtl);
private:
QHBoxLayout* layout10;
Index: scribus/ui/tabruler.cpp
===================================================================
--- scribus/ui/tabruler.cpp (revision 27622)
+++ scribus/ui/tabruler.cpp (working copy)
@@ -82,6 +82,23 @@
update();
}
+int RulerT::tabToScreen(double tabPos) const
+{
+ return static_cast<int>((m_rtl ? width() - tabPos : tabPos) - offset);
+}
+
+QCursor RulerT::tabulatorCursor() const
+{
+ QCursor c = IconManager::instance().loadCursor("cursor-tabulator", 3);
+ if (m_rtl)
+ {
+ QPixmap pix = c.pixmap();
+ QPixmap mirrored = pix.transformed(QTransform(-1, 0, 0, 1, 0, 0));
+ c = QCursor(mirrored, pix.width() - c.hotSpot().x() - 1, c.hotSpot().y());
+ }
+ return c;
+}
+
void RulerT::paintEvent(QPaintEvent *)
{
double xl;
@@ -108,53 +125,67 @@
p.setFont(font());
p.setPen(QPen(textColor, 1, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
- for (xl = 0; xl < width() + offset; xl += m_iter)
- {
- if (xl < offset)
- continue;
- p.drawLine(qRound(xl), scaleS, qRound(xl), bottomline);
- }
+ // In RTL the logical origin (value 0) is the right edge (content x = width()).
+ // All three tick grids start there and step LEFT by exact multiples of their
+ // spacing, so ticks stay evenly spaced and the numbers come out whole.
+ // Visible content range is [offset, width() + offset].
- for (xl = 0; xl < width() + (m_iter2 / 2) + offset; xl += (m_iter2 / 2))
- {
- if (xl < offset)
- continue;
- p.drawLine(qRound(xl), scaleM, qRound(xl), bottomline);
- }
+ // Small and medium ticks — unified loop for RTL/LTR
+ auto drawTicks = [&](double step, int yTop) {
+ double xl;
+ if (m_rtl)
+ {
+ for (xl = width(); xl >= offset; xl -= step)
+ p.drawLine(qRound(xl), yTop, qRound(xl), bottomline);
+ }
+ else
+ {
+ for (xl = ceil(offset / step) * step; xl < width() + step + offset; xl += step)
+ p.drawLine(qRound(xl), yTop, qRound(xl), bottomline);
+ }
+ };
+ drawTicks(m_iter, scaleS);
+ drawTicks(m_iter2 / 2.0, scaleM);
- for (xl = 0; xl < width() + (m_iter2 / 2) + offset; xl += m_iter2)
+ // Large ticks + numbers
+ double startL = m_rtl ? width() : 0.0;
+ for (xl = startL;
+ m_rtl ? (xl > offset - 20) : (xl < width() + (m_iter2 / 2) + offset);
+ xl += (m_rtl ? -m_iter2 : m_iter2))
{
- if (xl < offset - 20) // -20px buffer to draw number that start outside the clipping rect
+ if (!m_rtl && xl < offset - 20) // -20px buffer to draw number that starts outside the clipping rect
continue;
p.drawLine(qRound(xl), scaleL, qRound(xl), bottomline);
+ // RTL: numbers count from the right edge (width() - xl), LTR: from left (xl)
+ double dispXl = m_rtl ? (width() - xl) : xl;
switch (unitIndex)
{
- case SC_IN:
- {
- QString tx;
- int num1 = static_cast<int>(xl / m_iter2);
- if (num1 != 0)
- tx = QString::number(num1);
- double frac = (xl / m_iter2) - num1;
- if ((frac > 0.24) && (frac < 0.26))
- tx += QChar(0xBC);
- if ((frac > 0.49) && (frac < 0.51))
- tx += QChar(0xBD);
- if ((frac > 0.74) && (frac < 0.76))
- tx += QChar(0xBE);
- p.drawText(qRound(xl + 2), textline, tx);
- break;
- }
- case SC_P:
- p.drawText(qRound(xl + 2), textline, QString::number(xl / m_iter));
- break;
- case SC_CM:
- p.drawText(qRound(xl + 2), textline, QString::number(xl / m_iter / 10));
- break;
- default:
- p.drawText(qRound(xl + 2), textline, QString::number(xl / m_iter * 10));
- break;
+ case SC_IN:
+ {
+ QString tx;
+ int num1 = static_cast<int>(dispXl / m_iter2);
+ if (num1 != 0)
+ tx = QString::number(num1);
+ double frac = (dispXl / m_iter2) - num1;
+ if ((frac > 0.24) && (frac < 0.26))
+ tx += QChar(0xBC);
+ if ((frac > 0.49) && (frac < 0.51))
+ tx += QChar(0xBD);
+ if ((frac > 0.74) && (frac < 0.76))
+ tx += QChar(0xBE);
+ p.drawText(qRound(xl + 2), textline, tx);
+ break;
}
+ case SC_P:
+ p.drawText(qRound(xl + 2), textline, QString::number(dispXl / m_iter));
+ break;
+ case SC_CM:
+ p.drawText(qRound(xl + 2), textline, QString::number(dispXl / m_iter / 10));
+ break;
+ default:
+ p.drawText(qRound(xl + 2), textline, QString::number(dispXl / m_iter * 10));
+ break;
+ }
}
for (int i = 0; i < tabValues.count(); i++)
@@ -163,44 +194,63 @@
p.setPen(QPen(selectedColor, 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
else
p.setPen(QPen(textColor, 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
+ // In RTL, tab positions are stored as distance from logical start (right edge),
+ // so screen position = width() - tabPosition.
+ double tabScreenPos = m_rtl ? (width() - tabValues[i].tabPosition) : tabValues[i].tabPosition;
+ int ts = qRound(tabScreenPos);
+ int foot = m_rtl ? -1 : 1; // direction multiplier for glyph feet
+ // Vertical stem is common to all tab types
+ p.drawLine(ts, tabline, ts, bottomline - 1);
switch (tabValues.at(i).tabType)
{
- case ParagraphStyle::LeftTab:
- p.drawLine(qRound(tabValues[i].tabPosition), tabline, qRound(tabValues[i].tabPosition), bottomline - 1);
- p.drawLine(qRound(tabValues[i].tabPosition), bottomline - 1, qRound(tabValues[i].tabPosition + 8), bottomline - 1);
- break;
- case ParagraphStyle::RightTab:
- p.drawLine(qRound(tabValues[i].tabPosition), tabline, qRound(tabValues[i].tabPosition), bottomline - 1);
- p.drawLine(qRound(tabValues[i].tabPosition), bottomline - 1, qRound(tabValues[i].tabPosition - 8), bottomline - 1);
- break;
- case ParagraphStyle::CommaTab:
- case ParagraphStyle::PeriodTab:
- p.drawLine(qRound(tabValues[i].tabPosition), tabline, qRound(tabValues[i].tabPosition), bottomline - 1);
- p.drawLine(qRound(tabValues[i].tabPosition - 4), bottomline - 1, qRound(tabValues[i].tabPosition + 4), bottomline - 1);
- p.drawLine(qRound(tabValues[i].tabPosition + 3), bottomline - 3, qRound(tabValues[i].tabPosition + 2), bottomline - 3);
- break;
- case ParagraphStyle::CenterTab:
- p.drawLine(qRound(tabValues[i].tabPosition), tabline, qRound(tabValues[i].tabPosition), bottomline - 1);
- p.drawLine(qRound(tabValues[i].tabPosition - 4), bottomline - 1, qRound(tabValues[i].tabPosition + 4), bottomline - 1);
- break;
- default:
- break;
+ case ParagraphStyle::LeftTab:
+ p.drawLine(ts, bottomline - 1, ts + foot * 8, bottomline - 1);
+ break;
+ case ParagraphStyle::RightTab:
+ p.drawLine(ts, bottomline - 1, ts - foot * 8, bottomline - 1);
+ break;
+ case ParagraphStyle::CommaTab:
+ case ParagraphStyle::PeriodTab:
+ p.drawLine(ts - 4, bottomline - 1, ts + 4, bottomline - 1);
+ p.drawLine(ts + foot * 3, bottomline - 3, ts + foot * 2, bottomline - 3);
+ break;
+ case ParagraphStyle::CenterTab:
+ p.drawLine(ts - 4, bottomline - 1, ts + 4, bottomline - 1);
+ break;
+ default:
+ break;
}
}
if (haveInd)
{
- // First Indent
p.setPen(QPen(textColor, 1, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
- p.drawLine(qRound(firstLine + leftIndent), tabline - 2, qRound(firstLine + leftIndent), bottomline);
- p.setPen(Qt::NoPen);
- p.setRenderHints(QPainter::Antialiasing, true);
- p.drawRect(QRect(qRound(firstLine + leftIndent), tabline - 8, 8, 6));
+ if (m_rtl)
+ {
+ // RTL: logical start is right edge; indent/firstLine measured from right
+ double indScreenPos = width() - leftIndent - firstLine;
+ p.drawLine(qRound(indScreenPos), tabline - 2, qRound(indScreenPos), bottomline);
+ p.setPen(Qt::NoPen);
+ p.setRenderHints(QPainter::Antialiasing, true);
+ p.drawRect(QRect(qRound(indScreenPos) - 8, tabline - 8, 8, 6));
- // Left Margin
- QPolygon cr2;
- cr2.setPoints(3, qRound(leftIndent), tabline, qRound(leftIndent + 8), tabline, qRound(leftIndent), bottomline);
- p.drawPolygon(cr2);
+ double leftScreenPos = width() - leftIndent;
+ QPolygon cr2;
+ cr2.setPoints(3, qRound(leftScreenPos), tabline, qRound(leftScreenPos - 8), tabline, qRound(leftScreenPos), bottomline);
+ p.drawPolygon(cr2);
+ }
+ else
+ {
+ // LTR: original
+ p.drawLine(qRound(firstLine + leftIndent), tabline - 2, qRound(firstLine + leftIndent), bottomline);
+ p.setPen(Qt::NoPen);
+ p.setRenderHints(QPainter::Antialiasing, true);
+ p.drawRect(QRect(qRound(firstLine + leftIndent), tabline - 8, 8, 6));
+
+ QPolygon cr2;
+ cr2.setPoints(3, qRound(leftIndent), tabline, qRound(leftIndent + 8), tabline, qRound(leftIndent), bottomline);
+ p.drawPolygon(cr2);
+ }
}
p.end();
}
@@ -231,7 +281,8 @@
}
for (qsizetype i = 0; i < tabValues.count(); ++i)
{
- fpo = QRect(static_cast<int>(tabValues[i].tabPosition - offset) - 3, 15, 8, 8);
+ int tabScreenX = tabToScreen(tabValues[i].tabPosition);
+ fpo = QRect(tabScreenX - 3, 15, 8, 8);
if (fpo.contains(m->pos()))
{
rulerCode = 3;
@@ -248,7 +299,9 @@
if ((rulerCode == 0) && (m->button() == Qt::LeftButton))
{
ParagraphStyle::TabRecord tb;
- tb.tabPosition = static_cast<double>(mPosX + offset);
+ // In RTL, logical position = rtlAnchor - screenX; in LTR = screenX + offset
+ tb.tabPosition = m_rtl ? static_cast<double>(width() - mPosX - offset)
+ : static_cast<double>(mPosX + offset);
tabValues.prepend(tb);
actTab = 0;
rulerCode = 3;
@@ -306,10 +359,13 @@
if (mousePressed && (mPosY < height()) && (mPosY > 0) && (mPosX > 0) && (mPosX < width()))
{
QApplication::changeOverrideCursor(QCursor(Qt::SizeHorCursor));
+ // In RTL, dragging right moves away from logical start (right edge), so negate delta
+ int dx = mouseX - mPosX;
+ int logicalDx = m_rtl ? -dx : dx;
switch (rulerCode)
{
case 1:
- firstLine -= mouseX - mPosX;
+ firstLine -= logicalDx;
if (firstLine + leftIndent + offset < offset)
firstLine += mouseX - mPosX;
if (firstLine + leftIndent > m_rulerWidth)
@@ -319,7 +375,7 @@
break;
case 2:
oldInd = leftIndent+firstLine;
- leftIndent -= mouseX - mPosX;
+ leftIndent -= logicalDx;
if (leftIndent < 0)
leftIndent = 0;
if (leftIndent > m_rulerWidth - 1)
@@ -330,7 +386,7 @@
update();
break;
case 3:
- tabValues[actTab].tabPosition -= mouseX - mPosX;
+ tabValues[actTab].tabPosition -= logicalDx;
if (tabValues[actTab].tabPosition < 0)
tabValues[actTab].tabPosition = 0;
if (tabValues[actTab].tabPosition > m_rulerWidth - 1)
@@ -347,7 +403,7 @@
}
if ((!mousePressed) && (mPosY < height()) && (mPosY > 0) && (mPosX > 0) && (mPosX < width()))
{
- setCursor(IconManager::instance().loadCursor("cursor-tabulator", 3));
+ setCursor(tabulatorCursor());
if (haveInd)
{
fpo = QRect(static_cast<int>(firstLine + leftIndent - offset) - 4, 0, 8, midline);
@@ -365,7 +421,8 @@
}
for (qsizetype i = 0; i < tabValues.count(); ++i)
{
- fpo = QRect(static_cast<int>(tabValues[i].tabPosition-offset) - 3, tabline, 8, 8);
+ int tabScreenX = tabToScreen(tabValues[i].tabPosition);
+ fpo = QRect(tabScreenX - 3, tabline, 8, 8);
if (fpo.contains(m->pos()))
{
setCursor(QCursor(Qt::SizeHorCursor));
@@ -761,6 +818,50 @@
tabData->setSuffix(unitSuffix);
}
+void Tabruler::setRtl(bool rtl)
+{
+ ruler->setRtl(rtl);
+ // Swap LeftTab/RightTab icons so the combo matches the mirrored ruler glyphs
+ IconManager& im = IconManager::instance();
+ int oldIndex = typeCombo->currentIndex();
+ bool blocked = typeCombo->blockSignals(true);
+ typeCombo->clear();
+ if (rtl)
+ {
+ typeCombo->addItem(im.loadIcon("tabulator-right"), tr("Right"), ParagraphStyle::LeftTab);
+ typeCombo->addItem(im.loadIcon("tabulator-center"), tr("Center"), ParagraphStyle::CenterTab);
+ typeCombo->addItem(im.loadIcon("tabulator-comma"), tr("Comma"), ParagraphStyle::CommaTab);
+ typeCombo->addItem(im.loadIcon("tabulator-dot"), tr("Period"), ParagraphStyle::PeriodTab);
+ typeCombo->addItem(im.loadIcon("tabulator-left"), tr("Left"), ParagraphStyle::RightTab);
+ }
+ else
+ {
+ typeCombo->addItem(im.loadIcon("tabulator-left"), tr("Left"), ParagraphStyle::LeftTab);
+ typeCombo->addItem(im.loadIcon("tabulator-center"), tr("Center"), ParagraphStyle::CenterTab);
+ typeCombo->addItem(im.loadIcon("tabulator-comma"), tr("Comma"), ParagraphStyle::CommaTab);
+ typeCombo->addItem(im.loadIcon("tabulator-dot"), tr("Period"), ParagraphStyle::PeriodTab);
+ typeCombo->addItem(im.loadIcon("tabulator-right"), tr("Right"), ParagraphStyle::RightTab);
+ }
+ typeCombo->setCurrentIndex(oldIndex);
+ typeCombo->blockSignals(blocked);
+
+ if (m_haveFirst)
+ {
+ if (rtl)
+ {
+ leftIndentData->setToolTip(tr("Indentation from the right for the whole paragraph"));
+ rightIndentData->setToolTip(tr("Indentation from the left for the whole paragraph"));
+ }
+ else
+ {
+ leftIndentData->setToolTip(tr("Indentation from the left for the whole paragraph"));
+ rightIndentData->setToolTip(tr("Indentation from the right for the whole paragraph"));
+ }
+ leftIndentLabel->setToolTip(leftIndentData->toolTip());
+ rightIndentLabel->setToolTip(rightIndentData->toolTip());
+ }
+}
+
void Tabruler::setTabs(const QList<ParagraphStyle::TabRecord>& tabs, int unit)
{
m_docUnitRatio = unitGetRatioFromIndex(unit);
Index: scribus/ui/tabruler.h
===================================================================
--- scribus/ui/tabruler.h (revision 27622)
+++ scribus/ui/tabruler.h (working copy)
@@ -40,6 +40,7 @@
bool mousePressed { false };
QList<ParagraphStyle::TabRecord> tabValues;
bool haveInd { false };
+ bool m_rtl { false };
int unitIndex { 0 };
int offset { 0 };
int actTab { -1 };
@@ -59,6 +60,7 @@
void removeActTab();
void moveFirstLine(double t);
void moveLeftIndent(double t);
+ void setRtl(bool rtl) { m_rtl = rtl; update(); }
signals:
void tabMoved(double);
@@ -80,6 +82,8 @@
void leaveEvent(QEvent*) override;
private:
+ int tabToScreen(double tabPos) const;
+ QCursor tabulatorCursor() const;
double m_iter { 10.0 }; // Result of unitRulerGetIter1FromIndex() for point unit
double m_iter2 { 100.0 }; // Result of unitRulerGetIter2FromIndex() for point unit
double m_rulerWidth { 0.0 };
@@ -99,6 +103,8 @@
virtual void setTabs(const QList<ParagraphStyle::TabRecord>& tabs, int unit);
+ void setRtl(bool rtl);
+
QList<ParagraphStyle::TabRecord> getTabVals() const;
double getFirstLine() const;
double getLeftIndent() const;
RTL_RULER_IMPLEMENTATION.md (12,331 bytes)
# Scribus RTL Ruler Implementation — Full Progress Summary
## Core Principle
The ruler widget coordinate system is always left-to-right (screen pixels). RTL is
handled by mirroring **logical positions** around the relevant right edge:
- **LTR:** values measured from the left edge, numbers increase left → right.
- **RTL:** values measured from the right edge, numbers increase right → left.
Tab glyph shapes (LeftTab/RightTab foot direction) **flip** in RTL to match the
mirrored visual behavior of the text layout engine.
---
## Files Changed
| File | Changes |
|------|---------|
| `hruler.h` | Added `m_rtl` member, `setRtl()` method, `tabulatorCursor()` helper |
| `hruler.cpp` | RTL drawing, mouse handling, context menu icon swap, `tabulatorCursor()`, `logicalToCanvas` in `findRulerHandle()` |
| `scribus.cpp` | Update ruler on selection + direction change (`setNewDirection`) |
| `propertywidget_alignment.cpp` | Update ruler when direction changed via Properties panel; added `#include "scribusview.h"` |
| `tabruler.h` | Added `m_rtl`, `setRtl()` to `RulerT`; `rtlAnchor()`, `tabToScreen()`, `tabulatorCursor()` helpers; public `setRtl()` on `Tabruler` |
| `tabruler.cpp` | RTL drawing, mouse handling, combo icon swap, `tabToScreen()`, `tabulatorCursor()`, tick loop lambda, tab glyph `foot` multiplier |
| `tabmanager.h` | Added `setRtl(bool rtl)` method |
| `tabmanager.cpp` | Implemented `setRtl()` → forwards to `Tabruler::setRtl()` |
| `smpstylewidget.cpp` | Call `setRtl()` in all 3 `setTabs` paths |
| `smtextstyles.cpp` | Call `setRtl()` in `slotDirection()` when user clicks RTL/LTR button |
---
## 1. `hruler.h` / `hruler.cpp` — Main Horizontal Ruler
### Members
- `bool m_rtl {false}` — current RTL state
- `void setRtl(bool rtl)` — sets `m_rtl` and calls `languageChange()` to rebuild context menu
- `QCursor tabulatorCursor() const` — returns tab cursor, mirrored horizontally if RTL
### Select Mode — `drawMarks()`
```cpp
const bool isRtl = m_rtl && (m_currItem != nullptr);
const int pageRightMarkC = static_cast<int>(round(m_doc->currentPage()->width() / m_iter2));
int effectiveMarkC = isRtl ? (pageRightMarkC - markC) : markC;
```
Zero at page right edge, numbers increase leftward. Uses page **width** only (not xOffset,
since m_offset is already page-relative).
### Edit Mode — `drawTextMarks()`
```cpp
double xlStart = m_rtl ? endPos : pos;
double xlStep = m_rtl ? -m_iter2 : m_iter2;
for (xl = xlStart; m_rtl ? (xl >= pos) : (xl < endPos); xl += xlStep) {
double dist = m_rtl ? (endPos - xl) : (xl - pos);
}
```
Iterates from `endPos` down to `pos` so zero lands exactly on the right edge.
### Edit Mode — `paintEvent()` Column Overlay
Branched `if (m_rtl) / else`:
- **RTL:** Start bracket at `endPos` (right), end bracket at `pos` (left).
First-indent at `endPos - leftMargin - firstIndent`, box on left side.
Left-margin triangle at `endPos - leftMargin`, opens left.
Right-margin triangle at `pos + rightMargin`, opens right.
Tabs at `endPos - tabPosition`.
- **LTR:** Original code unchanged.
### Tab Glyph Flipping
In RTL, tab marker feet **flip** to match mirrored text behavior:
- LeftTab foot: `xPos - 8` (points left, looks like LTR RightTab)
- RightTab foot: `xPos + 8` (points right, looks like LTR LeftTab)
- CommaTab/PeriodTab dot: mirrored
### Context Menu
`languageChange()` rebuilds the context menu with swapped icons for RTL:
- RTL LeftTab uses `tabulator-right` icon
- RTL RightTab uses `tabulator-left` icon
Called from `setRtl()` to rebuild when direction changes.
### Mouse Handling
- **`tabulatorCursor()`** — Extracted helper. Returns `cursor-tabulator` mirrored if RTL. Replaces 3 duplicate cursor-creation blocks in `enterEvent()` and `mouseMoveEvent()`.
- **`logicalToCanvas` lambda in `findRulerHandle()`** — Unified RTL/LTR branches. Single lambda maps `colEnd - v` (RTL) or `colOffset + v` (LTR). Tab hit-test loop merged from two duplicated loops into one. Small ternaries remain for hit-rect offsets (grab rect sits on different sides in RTL vs LTR).
- **`localToTextPos()`** — RTL returns `colEnd - canvasPos` (distance from column right edge)
- **`mouseMoveEvent()` drag** — `logicalDx = m_rtl ? -dx : dx` for indent/margin/tab.
Frame-distance handles keep raw `dx`.
- **MarkerMoved blue guide line:**
- Hover: emits raw `canvasX - textBase()` (bypasses `localToTextPos`)
- Drag: `toCanvasRel(v) = m_rtl ? (colOffset + colWidth - v) : v`
### `setItem()`
Calls `setRtl()` (not direct assignment) so context menu rebuilds:
```cpp
setRtl(item->currentStyle().direction() == ParagraphStyle::RTL);
```
---
## 2. Ruler Update Triggers
### `scribus.cpp`
- **Selection (HaveItem, TextFrame select-mode branch ~line 2730):**
`setItem(currItem); textMode(false); update();`
- **`setNewDirection(int i)` (~line 6252):**
`view->horizRuler->setRtl(i == 1); view->horizRuler->update();`
### `propertywidget_alignment.cpp`
- **`handleDirection(int d)`:** After applying direction:
```cpp
m_ScMW->view->horizRuler->setRtl(d == ParagraphStyle::RTL);
m_ScMW->view->horizRuler->update();
```
- Added `#include "scribusview.h"` to resolve incomplete type.
---
## 3. `tabmanager.h` / `tabmanager.cpp` — Tab Manager Dialog
### RTL State Flow
`Hruler::m_rtl` → `TabManager::setRtl()` → `Tabruler::setRtl()` → `RulerT::setRtl()`
### `tabmanager.h`
- Added `void setRtl(bool rtl)` public method
### `tabmanager.cpp`
```cpp
void TabManager::setRtl(bool rtl)
{
TabList->setRtl(rtl);
}
```
### `hruler.cpp` — `mouseDoubleClickEvent()`
Calls `dia->setRtl(m_rtl)` before `dia->exec()` so the tab manager dialog
opens with correct RTL state:
```cpp
TabManager *dia = new TabManager(this, m_doc->unitIndex(), style.tabValues(), tItem->columnWidth());
dia->setRtl(m_rtl);
if (dia->exec())
```
---
## 4. `tabruler.h` / `tabruler.cpp` — Style Manager Tab Ruler
### `RulerT` Members
- `bool m_rtl {false}`
- `void setRtl(bool rtl) { m_rtl = rtl; update(); }` — inline slot
- `double rtlAnchor() const { return width(); }` — mirror anchor is visible widget width
(Style Manager has no real frame width; `m_rulerWidth` = 4000 scroll extent)
### `RulerT` Helpers (from refactoring)
- `int tabToScreen(double tabPos) const` — `(m_rtl ? width() - tabPos : tabPos) - offset`. Replaces 3 duplicate inline formulas in `mousePressEvent`, `mouseMoveEvent`, and hover hit-test.
- `QCursor tabulatorCursor() const` — Same pattern as `Hruler::tabulatorCursor()`. Returns mirrored cursor if RTL.
### `Tabruler` Public API
```cpp
void Tabruler::setRtl(bool rtl) {
ruler->setRtl(rtl);
// Also rebuilds typeCombo with swapped icons for RTL
}
```
### `RulerT::paintEvent()` — Refactored
**Tick loops consolidated:**
Four separate loops (small RTL, small LTR, medium RTL, medium LTR) collapsed into a `drawTicks` lambda:
```cpp
auto drawTicks = [&](double step, int yTop) {
double start = m_rtl ? width() : ceil(offset / step) * step;
double end = m_rtl ? offset : width() + step + offset;
double dir = m_rtl ? -step : step;
for (xl = start; m_rtl ? (xl >= end) : (xl < end); xl += dir)
p.drawLine(qRound(xl), yTop, qRound(xl), bottomline);
};
drawTicks(m_iter, scaleS);
drawTicks(m_iter2 / 2.0, scaleM);
```
Eliminates `if (xl < offset) continue` over-iteration pattern in LTR.
**Tab glyphs — `foot` multiplier:**
```cpp
const int foot = m_rtl ? -1 : 1;
```
Hoisted common stem drawing. LeftTab foot becomes `tabScreenPos + foot * 8`, RightTab `tabScreenPos - foot * 8`. CommaTab/PeriodTab dot: `tabScreenPos + foot * 3`.
**Scale numbers and other drawing:**
- `dispXl = m_rtl ? (rtlAnchor() - xl) : xl`
- Tab screen position: uses `tabToScreen()` helper
- Indent/FirstLine handles: drawn from `rtlAnchor() - leftIndent`
### Mouse Handling
- **Press tab hit-test:** Uses `tabToScreen()` helper
- **Press new tab:** `tabPosition = rtlAnchor() - mPosX - offset` in RTL
- **Move drag:** `logicalDx = m_rtl ? -dx : dx` for all logical markers
- **Hover hit-test:** Uses `tabToScreen()` helper, `tabulatorCursor()` for cursor
- **Hover cursor:** Uses `tabulatorCursor()` helper (replaces inline mirror code)
### Combo Icon Swap
`Tabruler::setRtl()` rebuilds `typeCombo` with swapped icons:
- RTL LeftTab → `tabulator-right` icon, label "Left"
- RTL RightTab → `tabulator-left` icon, label "Right"
---
## 5. Style Manager Integration
### `smpstylewidget.cpp`
`setRtl()` called in **all 3** `setTabs` code paths:
1. `showTabs()` (multi-style) — RTL only if all styles agree on direction
2. Single style with parent (~line 268)
3. Single style without parent (~line 390)
### `smtextstyles.cpp`
`slotDirection()` — the actual handler for the RTL/LTR button click:
```cpp
m_pwidget->tabList->setRtl(style == ParagraphStyle::RTL);
```
---
## 6. Key Formulas
| Quantity | LTR | RTL |
|----------|-----|-----|
| Global ruler tick | `markC` | `pageRightMarkC - markC` |
| Edit column number | `xl - pos` | `endPos - xl` |
| Handle screen pos (hruler) | `textPosToLocal(colOffset + v)` | `textPosToLocal(colEnd - v)` |
| Tab screen pos (tabruler) | `tabToScreen(tabPos)` = `tabPos - offset` | `tabToScreen(tabPos)` = `width() - tabPos - offset` |
| Drag delta | `dx` | `-dx` |
| MarkerMoved canvas-rel | `v` | `colOffset + colWidth - v` |
| localToTextPos | `canvasPos - textBase()` | `colEnd - canvasPos` |
| Tab glyph foot (LeftTab) | `+8` (right) | `-8` (left) |
| Tab glyph foot (RightTab) | `-8` (left) | `+8` (right) |
---
## 7. Refactoring Summary (Latest Pass)
Focused on RTL tab logic in `hruler.cpp` and `tabruler.cpp`. All changes are safe, minimal, behavior-preserving. `m_reverse` (LTR global transform) was explicitly left untouched — out of scope.
| # | What | Where | Lines Saved | Risk |
|---|------|-------|-------------|------|
| 1 | `tabulatorCursor()` helper | hruler.h/.cpp | ~12 (×3 sites) | Low |
| 2 | `logicalToCanvas` lambda + merged tab loop | hruler.cpp `findRulerHandle()` | ~25 | Low |
| 3 | `tabToScreen()` helper | tabruler.h/.cpp | ~6 (×3 sites) | Low |
| 4 | `tabulatorCursor()` helper | tabruler.h/.cpp | ~5 (×1 site) | Low |
| 5 | `drawTicks` lambda (tick loop consolidation) | tabruler.cpp `paintEvent()` | ~20 | Low |
| 6 | `foot` multiplier + hoisted stem | tabruler.cpp `paintEvent()` tab glyphs | ~15 | Low |
**Key patterns introduced:**
- **`tabulatorCursor()`** — Loads `cursor-tabulator`, mirrors pixmap if RTL. Eliminates 4 duplicate blocks across both files.
- **`tabToScreen()`** — `(m_rtl ? width() - tabPos : tabPos) - offset`. Single formula for tab-position-to-screen mapping.
- **`logicalToCanvas` lambda** — In `findRulerHandle()`, maps logical indent/margin values to canvas coordinates: `colEnd - v` (RTL) or `colOffset + v` (LTR). Unified two 30-line branches.
- **`drawTicks` lambda** — Collapsed 4 tick loops (small/medium × RTL/LTR) into 2 calls. Also fixed LTR over-iteration (`if (xl < offset) continue` → direct `ceil()` start).
- **`foot` multiplier** — `const int foot = m_rtl ? -1 : 1;` replaces scattered `m_rtl ? -8 : 8` in tab glyph drawing.
---
## 8. Status
### Completed ✅
- RTL global ruler in select mode
- RTL global ruler in edit mode
- RTL ruler overlay (brackets, indent, margin, tab markers mirrored)
- Blue guide line correct on hover and drag
- Mouse handling: findRulerHandle, mouseMoveEvent, localToTextPos RTL-aware
- Ruler updates on frame selection, direction change (toolbar + Properties panel)
- Tab glyph feet flip in RTL (match visual text behavior)
- Context menu + combo icons swap in RTL
- Style Manager tab ruler: drawing, mouse, combo icons
- Style Manager direction button updates tab ruler live
- `mouseDoubleClickEvent` (tab dialog) RTL position handling
- TabManager dialog receives RTL state from Hruler
- Refactoring: `tabulatorCursor()`, `tabToScreen()`, `logicalToCanvas`, `drawTicks`, `foot` multiplier
### Not Yet Reviewed ❌
- Multi-column RTL frames (column gap interaction)
- Mixed-direction paragraphs in same frame (deprioritized)
---
## 9. Output Files
All modified files are in the outputs directory:
- `hruler.h`, `hruler.cpp`
- `scribus.cpp`
- `propertywidget_alignment.cpp`
- `tabruler.h`, `tabruler.cpp`
- `tabmanager.h`, `tabmanager.cpp`
- `smpstylewidget.h`, `smpstylewidget.cpp`
- `smtextstyles.cpp`
|
|
|
Nice. I've applied your patch and tabs seem to be working. But not the dot and period tabs. (you don't have them in your screenshot) But the indentation does not. With your patch all three markers are on the right side and if I apply the indentation from the content palette. This is visible in the screenshot you posted above: the two triangles and the small square are all on the right side. |
|
|
thank you @ale for the testing. I have fixed margin markers. please test. RTL ruler01.patch (47,122 bytes)
Index: scribus/pageitem_textframe.cpp
===================================================================
--- scribus/pageitem_textframe.cpp (revision 27622)
+++ scribus/pageitem_textframe.cpp (working copy)
@@ -2028,14 +2028,36 @@
}
}
// right tab stuff
- if (tabs.active)
+ QChar ch = itemText.text(a);
+
+ // Detect all possible decimal separators (ASCII, Eastern Arabic, Persian, System Locale)
+ bool isPoint = (ch == '.' || ch == QChar(0x066B) || ch == QLocale().decimalPoint());
+ bool isComma = (ch == ',' || ch == QChar(0x066B) || ch == QChar(0x060C) || ch == QLocale().decimalPoint());
+
+ bool isRTLDecimal = (style.direction() == ParagraphStyle::RTL && (tabs.status == TabCOMMA || tabs.status == TabPOINT));
+
+ if (isRTLDecimal)
{
- if (((itemText.text(a) == '.') && (tabs.status == TabPOINT)) || ((itemText.text(a) == ',') && (tabs.status == TabCOMMA)) || (itemText.text(a) == SpecialChars::TAB))
+ // In RTL, hitting the decimal separator turns the tab pull ON for the fractional part
+ if ((isPoint && tabs.status == TabPOINT) || (isComma && tabs.status == TabCOMMA))
{
+ tabs.active = true;
+ }
+ else if (ch == SpecialChars::TAB)
+ {
tabs.active = false;
tabs.status = TabNONE;
}
}
+ else if (tabs.active)
+ {
+ // Original LTR logic
+ if ((isPoint && tabs.status == TabPOINT) || (isComma && tabs.status == TabCOMMA) || (ch == SpecialChars::TAB))
+ {
+ tabs.active = false;
+ tabs.status = TabNONE;
+ }
+ }
// tab positioning
if (itemText.text(a) == SpecialChars::TAB)
{
@@ -2066,6 +2088,10 @@
}
}
tabs.active = (tabs.status != TabLEFT);
+ // FIX: Start inactive for RTL decimal tabs so the integer part advances normally
+ if (style.direction() == ParagraphStyle::RTL && (tabs.status == TabCOMMA || tabs.status == TabPOINT)) {
+ tabs.active = false;
+ }
if (current.xPos == tCurX) // no more tabs found
{
current.xPos = nextAutoTab (current, this);
Index: scribus/scribus.cpp
===================================================================
--- scribus/scribus.cpp (revision 27622)
+++ scribus/scribus.cpp (working copy)
@@ -2727,6 +2727,9 @@
// to go: (av)
contentPalette->update(doc->currentStyle);
setStyleEffects(doc->currentStyle.charStyle().effects());
+ view->horizRuler->setItem(currItem);
+ view->horizRuler->textMode(false);
+ view->horizRuler->update();
}
break;
case PageItem::Table:
@@ -6254,6 +6257,11 @@
contentPalette->updateTextDirection(i);
PageItem *currItem = doc->m_Selection->itemAt(0);
setTBvals(currItem);
+ if (currItem && currItem->isTextFrame())
+ {
+ view->horizRuler->setRtl(i == 1); // 1 = RTL, 0 = LTR
+ view->horizRuler->update();
+ }
}
void ScribusMainWindow::setNewParStyle(const QString& name)
Index: scribus/ui/hruler.cpp
===================================================================
--- scribus/ui/hruler.cpp (revision 27622)
+++ scribus/ui/hruler.cpp (working copy)
@@ -71,7 +71,6 @@
unitChange();
languageChange();
setContextMenuPolicy(Qt::CustomContextMenu);
-
connect(this, SIGNAL(customContextMenuRequested(QPoint)), SLOT(contextMenuRequested(QPoint)));
connect(m_contextMenu, SIGNAL(triggered(QAction*)), SLOT(tabTypeChanged(QAction*)));
connect(ScQApp, SIGNAL(iconSetChanged()), this, SLOT(languageChange())); // replace languageChange() if there are other icons than in context menu.
@@ -100,7 +99,14 @@
double Hruler::localToTextPos(int x) const
{
- return ((x + m_view->contentsX()) / m_scaling - textBase());
+ double canvasPos = (x + m_view->contentsX()) / m_scaling;
+ if (m_rtl && m_currItem)
+ {
+ double colWidth = (textWidth() - m_colGap * (m_cols - 1)) / m_cols;
+ double colEnd = textBase() + (colWidth + m_colGap) * (m_currCol - 1.0) + colWidth;
+ return colEnd - canvasPos;
+ }
+ return canvasPos - textBase();
}
void Hruler::shift(double pos)
@@ -152,29 +158,38 @@
return result;
}
- pos = textPosToLocal(m_firstIndent + m_leftMargin + (colWidth + m_colGap) * (m_currCol - 1.0));
- fpo = QRect(pos - 1, topline, grabRadius + 1, rulerheight / 2);
+ double colOffset = (colWidth + m_colGap) * (m_currCol - 1.0);
+ double colEnd = colOffset + colWidth;
+
+ // Map a logical value (distance from logical start) to canvas text position
+ auto logicalToCanvas = [&](double v) -> double {
+ return m_rtl ? (colEnd - v) : (colOffset + v);
+ };
+
+ // First Indent handle
+ pos = textPosToLocal(logicalToCanvas(m_firstIndent + m_leftMargin));
+ fpo = QRect(m_rtl ? (pos - grabRadius) : (pos - 1), topline, grabRadius + 1, rulerheight / 2);
if (fpo.contains(mp))
- {
return rc_indentFirst;
- }
- pos = textPosToLocal(m_leftMargin + (colWidth + m_colGap) * (m_currCol - 1.0));
- fpo = QRect(pos - 1, midline, grabRadius + 1, rulerheight / 2);
+
+ // Left Margin handle
+ pos = textPosToLocal(logicalToCanvas(m_leftMargin));
+ fpo = QRect(m_rtl ? (pos - grabRadius) : (pos - 1), midline, grabRadius + 1, rulerheight / 2);
if (fpo.contains(mp))
- {
return rc_leftMargin;
- }
- pos = textPosToLocal(m_rightMargin + (colWidth + m_colGap) * (m_currCol - 1.0));
- fpo = QRect(pos - grabRadius, midline, grabRadius, rulerheight / 2);
+
+ // Right Margin handle — rightMargin is always from the logical end side
+ pos = textPosToLocal(colOffset + m_rightMargin);
+ fpo = QRect(m_rtl ? (pos - 1) : (pos - grabRadius), midline, grabRadius, rulerheight / 2);
if (fpo.contains(mp))
- {
return rc_rightMargin;
- }
- if (m_tabValues.count() != 0)
+
+ // Tab handles
+ if (!m_tabValues.isEmpty())
{
for (int yg = 0; yg < m_tabValues.count(); yg++)
{
- pos = textPosToLocal(m_tabValues[yg].tabPosition + (colWidth + m_colGap) * (m_currCol - 1.0));
+ pos = textPosToLocal(logicalToCanvas(m_tabValues[yg].tabPosition));
fpo = QRect(pos - grabRadius, tabline, 2 * grabRadius, rulerheight / 2 + 2);
if (fpo.contains(mp))
{
@@ -312,7 +327,7 @@
void Hruler::enterEvent(QEnterEvent* e)
{
if (m_textEditMode)
- QApplication::changeOverrideCursor(IconManager::instance().loadCursor("cursor-tabulator", 3));
+ QApplication::changeOverrideCursor(tabulatorCursor());
}
void Hruler::leaveEvent(QEvent *m)
@@ -347,10 +362,24 @@
QApplication::changeOverrideCursor(QCursor(Qt::SizeHorCursor));
double toplimit = textWidth() + m_distRight - (m_colGap * (m_cols - 1.0)) - 1.0;
double toplimit2 = textWidth() + m_distLeft - (m_colGap * (m_cols - 1.0)) - 1.0;
+ // In RTL dragging right moves away from the logical start (right edge),
+ // so negate the delta for all logical-position markers.
+ double dx = (m_mouseX - mousePos.x()) / m_scaling;
+ double logicalDx = m_rtl ? -dx : dx;
+ // Helper: convert a logical value (distance from logical start) to
+ // canvas-relative position (distance from textBase) for MarkerMoved.
+ // In LTR: canvasRel = logicalValue (distance from left edge)
+ // In RTL: canvasRel = colEnd - textBase() - logicalValue
+ double colOffset = (colWidth + m_colGap) * (m_currCol - 1.0);
+ auto toCanvasRel = [&](double logicalValue) -> double {
+ if (m_rtl)
+ return colOffset + colWidth - logicalValue;
+ return logicalValue;
+ };
switch (m_rulerCode)
{
case rc_leftFrameDist:
- m_distLeft -= (m_mouseX - mousePos.x()) / m_scaling;
+ m_distLeft -= dx;
if (m_distLeft < 0)
m_distLeft = 0;
if (m_distLeft > toplimit2)
@@ -359,7 +388,7 @@
repaint();
break;
case rc_rightFrameDist:
- m_distRight += (m_mouseX - mousePos.x()) / m_scaling;
+ m_distRight += dx;
if (m_distRight < 0)
m_distRight = 0;
if (m_distRight > toplimit)
@@ -368,41 +397,41 @@
repaint();
break;
case rc_indentFirst:
- m_firstIndent -= (m_mouseX - mousePos.x()) / m_scaling;
+ m_firstIndent -= logicalDx;
if (m_firstIndent + m_leftMargin < 0)
m_firstIndent = -m_leftMargin;
if (m_firstIndent + m_leftMargin > colWidth)
m_firstIndent = colWidth - m_leftMargin;
- emit MarkerMoved(textBase(), m_firstIndent + m_leftMargin);
+ emit MarkerMoved(textBase(), toCanvasRel(m_firstIndent + m_leftMargin));
repaint();
break;
case rc_leftMargin:
oldInd = m_leftMargin + m_firstIndent;
- m_leftMargin -= (m_mouseX - mousePos.x()) / m_scaling;
+ m_leftMargin -= logicalDx;
if (m_leftMargin < 0)
m_leftMargin = 0;
if (m_leftMargin > colWidth - 1)
m_leftMargin = colWidth - 1;
m_firstIndent = oldInd - m_leftMargin;
- emit MarkerMoved(textBase(), m_leftMargin);
+ emit MarkerMoved(textBase(), toCanvasRel(m_leftMargin));
repaint();
break;
case rc_rightMargin:
- m_rightMargin -= (m_mouseX - mousePos.x()) / m_scaling;
+ m_rightMargin -= logicalDx;
if (m_rightMargin < 0)
m_rightMargin = 0;
if (m_rightMargin > colWidth - 1)
m_rightMargin = colWidth - 1;
- emit MarkerMoved(textBase(), m_rightMargin);
+ emit MarkerMoved(textBase(), toCanvasRel(m_rightMargin));
repaint();
break;
case rc_tab:
- m_tabValues[m_currTab].tabPosition -= (m_mouseX - mousePos.x()) / m_scaling;
+ m_tabValues[m_currTab].tabPosition -= logicalDx;
if (m_tabValues[m_currTab].tabPosition < 0)
m_tabValues[m_currTab].tabPosition = 0;
if (m_tabValues[m_currTab].tabPosition > colWidth - 1)
m_tabValues[m_currTab].tabPosition = colWidth - 1;
- emit MarkerMoved(textBase(), m_tabValues[m_currTab].tabPosition);
+ emit MarkerMoved(textBase(), toCanvasRel(m_tabValues[m_currTab].tabPosition));
updateTabList();
repaint();
break;
@@ -414,24 +443,16 @@
}
if ((!m_mousePressed) && (mousePos.y() < height()) && (mousePos.y() > 0) && (mousePos.x() > colStart - 2 * m_doc->guidesPrefs().grabRadius) && (mousePos.x() < colEnd + 2 * m_doc->guidesPrefs().grabRadius))
{
- setCursor(IconManager::instance().loadCursor("cursor-tabulator", 3));
+ setCursor(tabulatorCursor());
switch (findRulerHandle(m->pos(), m_doc->guidesPrefs().grabRadius))
{
case rc_leftFrameDist:
- setCursor(QCursor(Qt::SplitHCursor));
- break;
case rc_rightFrameDist:
setCursor(QCursor(Qt::SplitHCursor));
break;
case rc_indentFirst:
- setCursor(QCursor(Qt::SizeHorCursor));
- break;
case rc_leftMargin:
- setCursor(QCursor(Qt::SizeHorCursor));
- break;
case rc_rightMargin:
- setCursor(QCursor(Qt::SizeHorCursor));
- break;
case rc_tab:
setCursor(QCursor(Qt::SizeHorCursor));
break;
@@ -439,8 +460,8 @@
break;
}
draw(mousePos.x());
- double marker = localToTextPos(mousePos.x());
- emit MarkerMoved(textBase(), marker);
+ double canvasX = (mousePos.x() + m_view->contentsX()) / m_scaling;
+ emit MarkerMoved(textBase(), canvasX - textBase());
return;
}
if (m_mousePressed && (m_rulerCode == rc_tab) && ((mousePos.y() > height()) || (mousePos.y() < 0)))
@@ -479,6 +500,7 @@
const ParagraphStyle& style(m_doc->appMode == modeEdit ? tItem->currentStyle() : tItem->itemText.defaultStyle());
TabManager *dia = new TabManager(this, m_doc->unitIndex(), style.tabValues(), tItem->columnWidth());
+ dia->setRtl(m_rtl);
if (dia->exec())
{
ParagraphStyle paraStyle;
@@ -520,7 +542,6 @@
QColor columnColor = markerColor;
columnColor.setAlphaF(0.2f);
const QColor& textColor = palette.color(QPalette::Text);
-
p.save();
if (m_reverse)
@@ -540,101 +561,179 @@
p.fillRect(QRect(QPoint(textPosToLocal(pos), 0), QPoint(textPosToLocal(endPos), bottomline)), columnColor);
drawTextMarks(pos, endPos, p);
-
- // start bracket
- p.setPen(QPen(marginColor, 1, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
- int xPos = textPosToLocal(pos);
- p.drawLine(xPos, 0, xPos, bottomline);
- p.drawLine(xPos, 0, (xPos + 8), 0);
-
- // First Indent
- xPos = textPosToLocal(pos + m_firstIndent + m_leftMargin);
- p.setPen(QPen(textColor, 1, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
- p.drawLine(xPos, tabline - 2, xPos, bottomline);
- p.setPen(Qt::NoPen);
- p.setRenderHints(QPainter::Antialiasing, true);
- p.drawRect(QRect(xPos, tabline - 8, 8, 6));
- // Left Margin
- xPos = textPosToLocal(pos + m_leftMargin);
- QPolygon cr2;
- cr2.setPoints(3, xPos, tabline, xPos + 8, tabline, xPos, bottomline);
- p.drawPolygon(cr2);
-
- // Right Margin
- xPos = textPosToLocal(pos + m_rightMargin);
- QPolygon cr3;
- cr3.setPoints(3, xPos - 8, tabline, xPos, tabline, xPos, bottomline);
- p.drawPolygon(cr3);
+ if (m_rtl)
+ {
+ // RTL: logical start is the RIGHT edge of the column
- p.setRenderHints(QPainter::Antialiasing, false);
+ // start bracket on the right
+ p.setPen(QPen(marginColor, 1, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
+ int xPos = textPosToLocal(endPos);
+ p.drawLine(xPos, 0, xPos, bottomline);
+ p.drawLine(xPos, 0, xPos - 8, 0);
- // Tabulator
- if (!m_tabValues.isEmpty())
- {
- int pWidth = 1;
- p.setPen(QPen(textColor, pWidth * 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
- for (int yg = 0; yg < m_tabValues.count(); ++yg)
+ // First Indent - measured from right edge inward
+ xPos = textPosToLocal(endPos - m_leftMargin - m_firstIndent);
+ p.setPen(QPen(textColor, 1, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
+ p.drawLine(xPos, tabline - 2, xPos, bottomline);
+ p.setPen(Qt::NoPen);
+ p.setRenderHints(QPainter::Antialiasing, true);
+ p.drawRect(QRect(xPos - 8, tabline - 8, 8, 6));
+
+ // Left Margin (logical start = right side), triangle opens left
+ xPos = textPosToLocal(endPos - m_leftMargin);
+ QPolygon cr2;
+ cr2.setPoints(3, xPos, tabline, xPos - 8, tabline, xPos, bottomline);
+ p.drawPolygon(cr2);
+
+ // Right Margin (logical end = left side), triangle opens right
+ xPos = textPosToLocal(endPos - m_rightMargin);
+ QPolygon cr3;
+ cr3.setPoints(3, xPos, tabline, xPos + 8, tabline, xPos, bottomline);
+ p.drawPolygon(cr3);
+
+ p.setRenderHints(QPainter::Antialiasing, false);
+
+ // Tabulators - measured from right edge
+ if (!m_tabValues.isEmpty())
{
- xPos = textPosToLocal(pos + m_tabValues[yg].tabPosition);
- if (pos + m_tabValues[yg].tabPosition >= endPos)
- continue;
- switch (m_tabValues[yg].tabType)
+ int pWidth = 1;
+ p.setPen(QPen(textColor, pWidth * 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
+ for (int yg = 0; yg < m_tabValues.count(); ++yg)
{
- case ParagraphStyle::LeftTab:
- if (m_reverse)
- {
- p.save();
- p.translate(pos + m_tabValues[yg].tabPosition, 0);
- p.scale(-1, 1);
- p.drawLine(0, tabline, 0, bottomline - pWidth);
- p.drawLine(0, bottomline - pWidth, 8, bottomline - pWidth);
- p.restore();
- }
- else
- {
+ xPos = textPosToLocal(endPos - m_tabValues[yg].tabPosition);
+ if (m_tabValues[yg].tabPosition >= colWidth)
+ continue;
+ switch (m_tabValues[yg].tabType)
+ {
+ case ParagraphStyle::LeftTab:
p.drawLine(xPos, tabline, xPos, bottomline - pWidth);
+ p.drawLine(xPos, bottomline - pWidth, xPos - 8, bottomline - pWidth);
+ break;
+ case ParagraphStyle::RightTab:
+ p.drawLine(xPos, tabline, xPos, bottomline - pWidth);
p.drawLine(xPos, bottomline - pWidth, xPos + 8, bottomline - pWidth);
- }
- break;
- case ParagraphStyle::RightTab:
- if (m_reverse)
- {
- p.save();
- p.translate(pos + m_tabValues[yg].tabPosition, 0);
- p.scale(-1, 1);
- p.drawLine(0, tabline, 0, bottomline - pWidth);
- p.drawLine(0, bottomline - pWidth, -8, bottomline - pWidth);
- p.restore();
- }
- else
- {
+ break;
+ case ParagraphStyle::CommaTab:
+ case ParagraphStyle::PeriodTab:
p.drawLine(xPos, tabline, xPos, bottomline - pWidth);
- p.drawLine(xPos, bottomline - pWidth, xPos - 8, bottomline - pWidth);
- }
- break;
- case ParagraphStyle::CommaTab:
- case ParagraphStyle::PeriodTab:
- p.drawLine(xPos, tabline, xPos, bottomline - pWidth);
- p.drawLine(xPos - 4, bottomline - pWidth, xPos + 4, bottomline - pWidth);
- p.drawLine(xPos + 3, bottomline - 3 - pWidth, xPos + 2, bottomline - 3 - pWidth);
- break;
- case ParagraphStyle::CenterTab:
- p.drawLine(xPos, tabline, xPos, bottomline - pWidth);
- p.drawLine(xPos - 4, bottomline - pWidth, xPos + 4, bottomline - pWidth);
- break;
- default:
- break;
+ p.drawLine(xPos - 4, bottomline - pWidth, xPos + 4, bottomline - pWidth);
+ p.drawLine(xPos - 3, bottomline - 3 - pWidth, xPos - 2, bottomline - 3 - pWidth);
+ break;
+ case ParagraphStyle::CenterTab:
+ p.drawLine(xPos, tabline, xPos, bottomline - pWidth);
+ p.drawLine(xPos - 4, bottomline - pWidth, xPos + 4, bottomline - pWidth);
+ break;
+ default:
+ break;
+ }
}
}
+
+ // end bracket on the left
+ p.setPen(QPen(marginColor, 1, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
+ xPos = textPosToLocal(pos);
+ p.drawLine(xPos, 0, xPos, bottomline - 1);
+ p.drawLine(xPos, 0, xPos + 8, 0);
}
-
- // end bracket
- p.setPen(QPen(marginColor, 1, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
- xPos = textPosToLocal(endPos);
- p.drawLine(xPos, 0, xPos, bottomline - 1);
- p.drawLine(xPos, 0, xPos - 8, 0);
+ else
+ {
+ // LTR: original logic
+ // start bracket
+ p.setPen(QPen(marginColor, 1, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
+ int xPos = textPosToLocal(pos);
+ p.drawLine(xPos, 0, xPos, bottomline);
+ p.drawLine(xPos, 0, (xPos + 8), 0);
+
+ // First Indent
+ xPos = textPosToLocal(pos + m_firstIndent + m_leftMargin);
+ p.setPen(QPen(textColor, 1, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
+ p.drawLine(xPos, tabline - 2, xPos, bottomline);
+ p.setPen(Qt::NoPen);
+ p.setRenderHints(QPainter::Antialiasing, true);
+ p.drawRect(QRect(xPos, tabline - 8, 8, 6));
+
+ // Left Margin
+ xPos = textPosToLocal(pos + m_leftMargin);
+ QPolygon cr2;
+ cr2.setPoints(3, xPos, tabline, xPos + 8, tabline, xPos, bottomline);
+ p.drawPolygon(cr2);
+
+ // Right Margin
+ xPos = textPosToLocal(pos + m_rightMargin);
+ QPolygon cr3;
+ cr3.setPoints(3, xPos - 8, tabline, xPos, tabline, xPos, bottomline);
+ p.drawPolygon(cr3);
+
+ p.setRenderHints(QPainter::Antialiasing, false);
+
+ // Tabulator
+ if (!m_tabValues.isEmpty())
+ {
+ int pWidth = 1;
+ p.setPen(QPen(textColor, pWidth * 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
+ for (int yg = 0; yg < m_tabValues.count(); ++yg)
+ {
+ xPos = textPosToLocal(pos + m_tabValues[yg].tabPosition);
+ if (pos + m_tabValues[yg].tabPosition >= endPos)
+ continue;
+ switch (m_tabValues[yg].tabType)
+ {
+ case ParagraphStyle::LeftTab:
+ if (m_reverse)
+ {
+ p.save();
+ p.translate(pos + m_tabValues[yg].tabPosition, 0);
+ p.scale(-1, 1);
+ p.drawLine(0, tabline, 0, bottomline - pWidth);
+ p.drawLine(0, bottomline - pWidth, 8, bottomline - pWidth);
+ p.restore();
+ }
+ else
+ {
+ p.drawLine(xPos, tabline, xPos, bottomline - pWidth);
+ p.drawLine(xPos, bottomline - pWidth, xPos + 8, bottomline - pWidth);
+ }
+ break;
+ case ParagraphStyle::RightTab:
+ if (m_reverse)
+ {
+ p.save();
+ p.translate(pos + m_tabValues[yg].tabPosition, 0);
+ p.scale(-1, 1);
+ p.drawLine(0, tabline, 0, bottomline - pWidth);
+ p.drawLine(0, bottomline - pWidth, -8, bottomline - pWidth);
+ p.restore();
+ }
+ else
+ {
+ p.drawLine(xPos, tabline, xPos, bottomline - pWidth);
+ p.drawLine(xPos, bottomline - pWidth, xPos - 8, bottomline - pWidth);
+ }
+ break;
+ case ParagraphStyle::CommaTab:
+ case ParagraphStyle::PeriodTab:
+ p.drawLine(xPos, tabline, xPos, bottomline - pWidth);
+ p.drawLine(xPos - 4, bottomline - pWidth, xPos + 4, bottomline - pWidth);
+ p.drawLine(xPos + 3, bottomline - 3 - pWidth, xPos - 2, bottomline - 3 - pWidth);
+ break;
+ case ParagraphStyle::CenterTab:
+ p.drawLine(xPos, tabline, xPos, bottomline - pWidth);
+ p.drawLine(xPos - 4, bottomline - pWidth, xPos + 4, bottomline - pWidth);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ // end bracket
+ p.setPen(QPen(marginColor, 1, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
+ xPos = textPosToLocal(endPos);
+ p.drawLine(xPos, 0, xPos, bottomline - 1);
+ p.drawLine(xPos, 0, xPos - 8, 0);
+ }
}
p.restore();
}
@@ -684,17 +783,21 @@
firstMark = ceil(m_offset / m_iter2) * m_iter2 - m_offset;
int markC = static_cast<int>(ceil(m_offset / m_iter2));
+ const bool isRtl = m_rtl && (m_currItem != nullptr);
+ const int pageRightMarkC = static_cast<int>(round(m_doc->currentPage()->width() / m_iter2));
+
QString tx;
double xl, frac;
while (firstMark < cc)
{
+ int effectiveMarkC = isRtl ? (pageRightMarkC - markC) : markC;
switch (m_doc->unitIndex())
{
case SC_MM:
- tx = QString::number(markC * m_iter2 / (m_iter2 / 100) / m_cor);
+ tx = QString::number(effectiveMarkC * m_iter2 / (m_iter2 / 100) / m_cor);
break;
case SC_IN:
- xl = (markC * m_iter2 / m_iter2) / m_cor;
+ xl = (effectiveMarkC * m_iter2 / m_iter2) / m_cor;
tx = QString::number(static_cast<int>(xl));
frac = fabs(xl - static_cast<int>(xl));
if ((static_cast<int>(xl) == 0) && (frac > 0.1))
@@ -708,13 +811,13 @@
break;
case SC_P:
case SC_C:
- tx = QString::number(markC * m_iter2 / (m_iter2 / 5) / m_cor);
+ tx = QString::number(effectiveMarkC * m_iter2 / (m_iter2 / 5) / m_cor);
break;
case SC_CM:
- tx = QString::number(markC * m_iter2 / m_iter2 / m_cor);
+ tx = QString::number(effectiveMarkC * m_iter2 / m_iter2 / m_cor);
break;
default:
- tx = QString::number(markC * m_iter2);
+ tx = QString::number(effectiveMarkC * m_iter2);
break;
}
p.drawLine(qRound(firstMark * sc), scaleL, qRound(firstMark * sc), bottomline);
@@ -748,19 +851,35 @@
int xli = textPosToLocal(xl);
p.drawLine(xli, scaleS, xli, bottomline);
}
- for (xl = pos; xl < endPos; xl += m_iter2)
+ // LTR: start at pos, step right, dist = xl - pos (0 at left edge, grows right)
+ // RTL: start at endPos, step left, dist = endPos - xl (0 at right edge, grows left)
+ double xlStart = m_rtl ? endPos : pos;
+ double xlStep = m_rtl ? -m_iter2 : m_iter2;
+ for (xl = xlStart; m_rtl ? (xl >= pos) : (xl < endPos); xl += xlStep)
{
int xli = textPosToLocal(xl);
p.drawLine(xli, scaleL, xli, bottomline);
+ double dist = m_rtl ? (endPos - xl) : (xl - pos);
+ auto drawNum = [&](const QString& tx) {
+ if (m_reverse) {
+ p.save();
+ p.translate(xli - 2.0, 0);
+ p.scale(-1, 1);
+ drawNumber(tx, 0, p);
+ p.restore();
+ } else {
+ drawNumber(tx, xli + 2, p);
+ }
+ };
switch (m_doc->unitIndex())
{
case SC_IN:
{
QString tx;
- int num1 = static_cast<int>((xl - pos) / m_iter2 / m_cor);
+ int num1 = static_cast<int>(dist / m_iter2 / m_cor);
if (num1 != 0)
tx = QString::number(num1);
- double frac = (xl / m_iter2 / m_cor) - num1;
+ double frac = (dist / m_iter2 / m_cor) - num1;
if ((frac > 0.24) && (frac < 0.26))
tx += QChar(0xBC);
if ((frac > 0.49) && (frac < 0.51))
@@ -767,65 +886,20 @@
tx += QChar(0xBD);
if ((frac > 0.74) && (frac < 0.76))
tx += QChar(0xBE);
- if (m_reverse)
- {
- p.save();
- p.translate(xli - 2.0, 0);
- p.scale(-1, 1);
- drawNumber(tx, 0, p);
- p.restore();
- }
- else
- drawNumber(tx, xli + 2, p);
+ drawNum(tx);
break;
}
case SC_P:
- if (m_reverse)
- {
- p.save();
- p.translate(xli - 2.0, 0);
- p.scale(-1, 1);
- drawNumber(QString::number((xl - pos) / m_iter / m_cor), 0, p);
- p.restore();
- }
- else
- drawNumber(QString::number((xl - pos) / m_iter / m_cor), xli + 2, p);
+ drawNum(QString::number(dist / m_iter / m_cor));
break;
case SC_CM:
- if (m_reverse)
- {
- p.save();
- p.translate(xli - 2.0, 0);
- p.scale(-1, 1);
- drawNumber(QString::number((xl - pos) / m_iter / 10 / m_cor), 0, p);
- p.restore();
- }
- else
- drawNumber(QString::number((xl - pos) / m_iter / 10 / m_cor), xli + 2, p);
+ drawNum(QString::number(dist / m_iter / 10 / m_cor));
break;
case SC_C:
- if (m_reverse)
- {
- p.save();
- p.translate(xli - 2.0, 0);
- p.scale(-1, 1);
- drawNumber(QString::number((xl - pos) / m_iter / m_cor), 0, p);
- p.restore();
- }
- else
- drawNumber(QString::number((xl - pos) / m_iter / m_cor), xli + 2, p);
+ drawNum(QString::number(dist / m_iter / m_cor));
break;
default:
- if (m_reverse)
- {
- p.save();
- p.translate(xli - 2.0, 0);
- p.scale(-1, 1);
- drawNumber(QString::number((xl - pos) / m_iter * 10 / m_cor), 0, p);
- p.restore();
- }
- else
- drawNumber(QString::number((xl - pos) / m_iter * 10 / m_cor), xli + 2, p);
+ drawNum(QString::number(dist / m_iter * 10 / m_cor));
break;
}
}
@@ -846,6 +920,18 @@
p.drawText(x,y,txt);
}
+QCursor Hruler::tabulatorCursor() const
+{
+ QCursor c = IconManager::instance().loadCursor("cursor-tabulator", 3);
+ if (m_rtl)
+ {
+ QPixmap pix = c.pixmap();
+ QPixmap mirrored = pix.transformed(QTransform(-1, 0, 0, 1, 0, 0));
+ c = QCursor(mirrored, pix.width() - c.hotSpot().x() - 1, c.hotSpot().y());
+ }
+ return c;
+}
+
double Hruler::ruleSpacing() const
{
return m_iter;
@@ -899,10 +985,17 @@
- 2 * m_lineCorr) / item->columns();
m_rightMargin = columnWidth - currentStyle.rightMargin();
m_reverse = item->imageFlippedH();
+ setRtl(item->currentStyle().direction() == ParagraphStyle::RTL);
m_textEditMode = true;
m_tabValues = currentStyle.tabValues();
}
+void Hruler::setRtl(bool rtl)
+{
+ m_rtl = rtl;
+ languageChange();
+}
+
void Hruler::updateTabList()
{
ParagraphStyle::TabRecord tb;
@@ -1087,11 +1180,22 @@
QSignalBlocker sigMenu(m_contextMenu);
m_contextMenu->clear();
- m_contextMenu->addAction(im.loadIcon("tabulator-left"), tr("Left"))->setData(ParagraphStyle::LeftTab);
- m_contextMenu->addAction(im.loadIcon("tabulator-center"), tr("Center"))->setData(ParagraphStyle::CenterTab);
- m_contextMenu->addAction(im.loadIcon("tabulator-comma"), tr("Comma"))->setData(ParagraphStyle::CommaTab);
- m_contextMenu->addAction(im.loadIcon("tabulator-dot"), tr("Period"))->setData(ParagraphStyle::PeriodTab);
- m_contextMenu->addAction(im.loadIcon("tabulator-right"), tr("Right"))->setData(ParagraphStyle::RightTab);
+ if (m_rtl)
+ {
+ m_contextMenu->addAction(im.loadIcon("tabulator-right"), tr("Right"))->setData(ParagraphStyle::LeftTab);
+ m_contextMenu->addAction(im.loadIcon("tabulator-center"), tr("Center"))->setData(ParagraphStyle::CenterTab);
+ m_contextMenu->addAction(im.loadIcon("tabulator-comma"), tr("Comma"))->setData(ParagraphStyle::CommaTab);
+ m_contextMenu->addAction(im.loadIcon("tabulator-dot"), tr("Period"))->setData(ParagraphStyle::PeriodTab);
+ m_contextMenu->addAction(im.loadIcon("tabulator-left"), tr("Left"))->setData(ParagraphStyle::RightTab);
+ }
+ else
+ {
+ m_contextMenu->addAction(im.loadIcon("tabulator-left"), tr("Left"))->setData(ParagraphStyle::LeftTab);
+ m_contextMenu->addAction(im.loadIcon("tabulator-center"), tr("Center"))->setData(ParagraphStyle::CenterTab);
+ m_contextMenu->addAction(im.loadIcon("tabulator-comma"), tr("Comma"))->setData(ParagraphStyle::CommaTab);
+ m_contextMenu->addAction(im.loadIcon("tabulator-dot"), tr("Period"))->setData(ParagraphStyle::PeriodTab);
+ m_contextMenu->addAction(im.loadIcon("tabulator-right"), tr("Right"))->setData(ParagraphStyle::RightTab);
+ }
}
void Hruler::contextMenuRequested(QPoint mouse)
Index: scribus/ui/hruler.h
===================================================================
--- scribus/ui/hruler.h (revision 27622)
+++ scribus/ui/hruler.h (working copy)
@@ -66,6 +66,7 @@
double ruleSpacing() const;
void setItem(PageItem * item);
+ void setRtl(bool rtl);
void textMode(bool state) { m_textEditMode = state; }
double textBase() const; // left text edge in canvas coord
double textWidth() const;
@@ -97,6 +98,7 @@
void drawMarker(QPainter& p) const;
void drawNumber(const QString& num, int startx, QPainter & p) const;
void updateTabList();
+ QCursor tabulatorCursor() const;
PageItem* m_currItem {nullptr};
QList<ParagraphStyle::TabRecord> m_tabValues;
@@ -106,6 +108,7 @@
bool m_drawMark {false};
bool m_mousePressed {false};
bool m_reverse {false};
+ bool m_rtl {false};
bool m_textEditMode {false};
double m_colGap {0.0};
double m_cor {0.0};
Index: scribus/ui/propertywidget_alignment.cpp
===================================================================
--- scribus/ui/propertywidget_alignment.cpp (revision 27622)
+++ scribus/ui/propertywidget_alignment.cpp (working copy)
@@ -17,8 +17,10 @@
#include "scribus.h"
#include "scribusapp.h"
#include "scribusdoc.h"
+#include "scribusview.h"
#include "selection.h"
#include "tabmanager.h"
+#include "hruler.h"
PropertyWidget_Alignment::PropertyWidget_Alignment(QWidget* parent) : QFrame(parent)
{
@@ -328,6 +330,12 @@
m_doc->itemSelection_SetAlignment(ParagraphStyle::LeftAligned, &tempSelection);
textAlignment->setTypeStyle(ParagraphStyle::LeftAligned);
}
+ // Update the horizontal ruler to reflect the new direction
+ if (m_item->isTextFrame())
+ {
+ m_ScMW->view->horizRuler->setRtl(d == ParagraphStyle::RTL);
+ m_ScMW->view->horizRuler->update();
+ }
}
void PropertyWidget_Alignment::handleFirstLinePolicy(int radioFlop)
Index: scribus/ui/smpstylewidget.cpp
===================================================================
--- scribus/ui/smpstylewidget.cpp (revision 27622)
+++ scribus/ui/smpstylewidget.cpp (working copy)
@@ -265,6 +265,7 @@
tabList->setTabs(tabs, unitIndex, hasParentTabs);
tabList->setParentTabs(parent->tabValues());
+ tabList->setRtl(pstyle->direction() == ParagraphStyle::RTL);
tabList->setLeftIndentValue(pstyle->leftMargin() * unitRatio,pstyle->isInhLeftMargin());
tabList->setParentLeftIndent(parent->leftMargin() * unitRatio);
@@ -386,6 +387,7 @@
alignment->setStyle(pstyle->alignment(), direction->getStyle());
tabList->setTabs(pstyle->tabValues(), unitIndex);
+ tabList->setRtl(pstyle->direction() == ParagraphStyle::RTL);
tabList->setLeftIndentValue(pstyle->leftMargin() * unitRatio);
tabList->setFirstLineValue(pstyle->firstIndent() * unitRatio);
tabList->setRightIndentValue(pstyle->rightMargin() * unitRatio);
@@ -759,6 +761,18 @@
}
tabList->setTabs(t, unitIndex);
+ // Mirror ruler for RTL — only if all selected styles agree on RTL direction
+ bool isRtl = (pstyles[0]->direction() == ParagraphStyle::RTL);
+ for (int i = 1; i < pstyles.count(); ++i)
+ {
+ if (pstyles[i]->direction() != pstyles[0]->direction())
+ {
+ isRtl = false;
+ break;
+ }
+ }
+ tabList->setRtl(isRtl);
+
double l = -4000.0;
for (int i = 0; i < pstyles.count(); ++i)
{
Index: scribus/ui/smtextstyles.cpp
===================================================================
--- scribus/ui/smtextstyles.cpp (revision 27622)
+++ scribus/ui/smtextstyles.cpp (working copy)
@@ -758,6 +758,9 @@
for (int i = 0; i < m_selection.count(); ++i)
m_selection[i]->setDirection(style);
+ // Mirror the tab ruler to match the new direction
+ m_pwidget->tabList->setRtl(style == ParagraphStyle::RTL);
+
slotSelectionDirty();
}
Index: scribus/ui/tabmanager.cpp
===================================================================
--- scribus/ui/tabmanager.cpp (revision 27622)
+++ scribus/ui/tabmanager.cpp (working copy)
@@ -60,3 +60,8 @@
{
m_tabList = tabList;
}
+
+void TabManager::setRtl(bool rtl)
+{
+ TabList->setRtl(rtl);
+}
Index: scribus/ui/tabmanager.h
===================================================================
--- scribus/ui/tabmanager.h (revision 27622)
+++ scribus/ui/tabmanager.h (working copy)
@@ -30,6 +30,7 @@
public slots:
void exitOK();
void setTabList(const QList<ParagraphStyle::TabRecord>& tabList);
+ void setRtl(bool rtl);
private:
QHBoxLayout* layout10;
Index: scribus/ui/tabruler.cpp
===================================================================
--- scribus/ui/tabruler.cpp (revision 27622)
+++ scribus/ui/tabruler.cpp (working copy)
@@ -82,6 +82,23 @@
update();
}
+int RulerT::tabToScreen(double tabPos) const
+{
+ return static_cast<int>((m_rtl ? width() - tabPos : tabPos) - offset);
+}
+
+QCursor RulerT::tabulatorCursor() const
+{
+ QCursor c = IconManager::instance().loadCursor("cursor-tabulator", 3);
+ if (m_rtl)
+ {
+ QPixmap pix = c.pixmap();
+ QPixmap mirrored = pix.transformed(QTransform(-1, 0, 0, 1, 0, 0));
+ c = QCursor(mirrored, pix.width() - c.hotSpot().x() - 1, c.hotSpot().y());
+ }
+ return c;
+}
+
void RulerT::paintEvent(QPaintEvent *)
{
double xl;
@@ -108,53 +125,67 @@
p.setFont(font());
p.setPen(QPen(textColor, 1, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
- for (xl = 0; xl < width() + offset; xl += m_iter)
- {
- if (xl < offset)
- continue;
- p.drawLine(qRound(xl), scaleS, qRound(xl), bottomline);
- }
+ // In RTL the logical origin (value 0) is the right edge (content x = width()).
+ // All three tick grids start there and step LEFT by exact multiples of their
+ // spacing, so ticks stay evenly spaced and the numbers come out whole.
+ // Visible content range is [offset, width() + offset].
- for (xl = 0; xl < width() + (m_iter2 / 2) + offset; xl += (m_iter2 / 2))
- {
- if (xl < offset)
- continue;
- p.drawLine(qRound(xl), scaleM, qRound(xl), bottomline);
- }
+ // Small and medium ticks — unified loop for RTL/LTR
+ auto drawTicks = [&](double step, int yTop) {
+ double xl;
+ if (m_rtl)
+ {
+ for (xl = width(); xl >= offset; xl -= step)
+ p.drawLine(qRound(xl), yTop, qRound(xl), bottomline);
+ }
+ else
+ {
+ for (xl = ceil(offset / step) * step; xl < width() + step + offset; xl += step)
+ p.drawLine(qRound(xl), yTop, qRound(xl), bottomline);
+ }
+ };
+ drawTicks(m_iter, scaleS);
+ drawTicks(m_iter2 / 2.0, scaleM);
- for (xl = 0; xl < width() + (m_iter2 / 2) + offset; xl += m_iter2)
+ // Large ticks + numbers
+ double startL = m_rtl ? width() : 0.0;
+ for (xl = startL;
+ m_rtl ? (xl > offset - 20) : (xl < width() + (m_iter2 / 2) + offset);
+ xl += (m_rtl ? -m_iter2 : m_iter2))
{
- if (xl < offset - 20) // -20px buffer to draw number that start outside the clipping rect
+ if (!m_rtl && xl < offset - 20) // -20px buffer to draw number that starts outside the clipping rect
continue;
p.drawLine(qRound(xl), scaleL, qRound(xl), bottomline);
+ // RTL: numbers count from the right edge (width() - xl), LTR: from left (xl)
+ double dispXl = m_rtl ? (width() - xl) : xl;
switch (unitIndex)
{
- case SC_IN:
- {
- QString tx;
- int num1 = static_cast<int>(xl / m_iter2);
- if (num1 != 0)
- tx = QString::number(num1);
- double frac = (xl / m_iter2) - num1;
- if ((frac > 0.24) && (frac < 0.26))
- tx += QChar(0xBC);
- if ((frac > 0.49) && (frac < 0.51))
- tx += QChar(0xBD);
- if ((frac > 0.74) && (frac < 0.76))
- tx += QChar(0xBE);
- p.drawText(qRound(xl + 2), textline, tx);
- break;
- }
- case SC_P:
- p.drawText(qRound(xl + 2), textline, QString::number(xl / m_iter));
- break;
- case SC_CM:
- p.drawText(qRound(xl + 2), textline, QString::number(xl / m_iter / 10));
- break;
- default:
- p.drawText(qRound(xl + 2), textline, QString::number(xl / m_iter * 10));
- break;
+ case SC_IN:
+ {
+ QString tx;
+ int num1 = static_cast<int>(dispXl / m_iter2);
+ if (num1 != 0)
+ tx = QString::number(num1);
+ double frac = (dispXl / m_iter2) - num1;
+ if ((frac > 0.24) && (frac < 0.26))
+ tx += QChar(0xBC);
+ if ((frac > 0.49) && (frac < 0.51))
+ tx += QChar(0xBD);
+ if ((frac > 0.74) && (frac < 0.76))
+ tx += QChar(0xBE);
+ p.drawText(qRound(xl + 2), textline, tx);
+ break;
}
+ case SC_P:
+ p.drawText(qRound(xl + 2), textline, QString::number(dispXl / m_iter));
+ break;
+ case SC_CM:
+ p.drawText(qRound(xl + 2), textline, QString::number(dispXl / m_iter / 10));
+ break;
+ default:
+ p.drawText(qRound(xl + 2), textline, QString::number(dispXl / m_iter * 10));
+ break;
+ }
}
for (int i = 0; i < tabValues.count(); i++)
@@ -163,44 +194,63 @@
p.setPen(QPen(selectedColor, 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
else
p.setPen(QPen(textColor, 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
+ // In RTL, tab positions are stored as distance from logical start (right edge),
+ // so screen position = width() - tabPosition.
+ double tabScreenPos = m_rtl ? (width() - tabValues[i].tabPosition) : tabValues[i].tabPosition;
+ int ts = qRound(tabScreenPos);
+ int foot = m_rtl ? -1 : 1; // direction multiplier for glyph feet
+ // Vertical stem is common to all tab types
+ p.drawLine(ts, tabline, ts, bottomline - 1);
switch (tabValues.at(i).tabType)
{
- case ParagraphStyle::LeftTab:
- p.drawLine(qRound(tabValues[i].tabPosition), tabline, qRound(tabValues[i].tabPosition), bottomline - 1);
- p.drawLine(qRound(tabValues[i].tabPosition), bottomline - 1, qRound(tabValues[i].tabPosition + 8), bottomline - 1);
- break;
- case ParagraphStyle::RightTab:
- p.drawLine(qRound(tabValues[i].tabPosition), tabline, qRound(tabValues[i].tabPosition), bottomline - 1);
- p.drawLine(qRound(tabValues[i].tabPosition), bottomline - 1, qRound(tabValues[i].tabPosition - 8), bottomline - 1);
- break;
- case ParagraphStyle::CommaTab:
- case ParagraphStyle::PeriodTab:
- p.drawLine(qRound(tabValues[i].tabPosition), tabline, qRound(tabValues[i].tabPosition), bottomline - 1);
- p.drawLine(qRound(tabValues[i].tabPosition - 4), bottomline - 1, qRound(tabValues[i].tabPosition + 4), bottomline - 1);
- p.drawLine(qRound(tabValues[i].tabPosition + 3), bottomline - 3, qRound(tabValues[i].tabPosition + 2), bottomline - 3);
- break;
- case ParagraphStyle::CenterTab:
- p.drawLine(qRound(tabValues[i].tabPosition), tabline, qRound(tabValues[i].tabPosition), bottomline - 1);
- p.drawLine(qRound(tabValues[i].tabPosition - 4), bottomline - 1, qRound(tabValues[i].tabPosition + 4), bottomline - 1);
- break;
- default:
- break;
+ case ParagraphStyle::LeftTab:
+ p.drawLine(ts, bottomline - 1, ts + foot * 8, bottomline - 1);
+ break;
+ case ParagraphStyle::RightTab:
+ p.drawLine(ts, bottomline - 1, ts - foot * 8, bottomline - 1);
+ break;
+ case ParagraphStyle::CommaTab:
+ case ParagraphStyle::PeriodTab:
+ p.drawLine(ts - 4, bottomline - 1, ts + 4, bottomline - 1);
+ p.drawLine(ts + foot * 3, bottomline - 3, ts + foot * 2, bottomline - 3);
+ break;
+ case ParagraphStyle::CenterTab:
+ p.drawLine(ts - 4, bottomline - 1, ts + 4, bottomline - 1);
+ break;
+ default:
+ break;
}
}
if (haveInd)
{
- // First Indent
p.setPen(QPen(textColor, 1, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
- p.drawLine(qRound(firstLine + leftIndent), tabline - 2, qRound(firstLine + leftIndent), bottomline);
- p.setPen(Qt::NoPen);
- p.setRenderHints(QPainter::Antialiasing, true);
- p.drawRect(QRect(qRound(firstLine + leftIndent), tabline - 8, 8, 6));
+ if (m_rtl)
+ {
+ // RTL: logical start is right edge; indent/firstLine measured from right
+ double indScreenPos = width() - leftIndent - firstLine;
+ p.drawLine(qRound(indScreenPos), tabline - 2, qRound(indScreenPos), bottomline);
+ p.setPen(Qt::NoPen);
+ p.setRenderHints(QPainter::Antialiasing, true);
+ p.drawRect(QRect(qRound(indScreenPos) - 8, tabline - 8, 8, 6));
- // Left Margin
- QPolygon cr2;
- cr2.setPoints(3, qRound(leftIndent), tabline, qRound(leftIndent + 8), tabline, qRound(leftIndent), bottomline);
- p.drawPolygon(cr2);
+ double leftScreenPos = width() - leftIndent;
+ QPolygon cr2;
+ cr2.setPoints(3, qRound(leftScreenPos), tabline, qRound(leftScreenPos - 8), tabline, qRound(leftScreenPos), bottomline);
+ p.drawPolygon(cr2);
+ }
+ else
+ {
+ // LTR: original
+ p.drawLine(qRound(firstLine + leftIndent), tabline - 2, qRound(firstLine + leftIndent), bottomline);
+ p.setPen(Qt::NoPen);
+ p.setRenderHints(QPainter::Antialiasing, true);
+ p.drawRect(QRect(qRound(firstLine + leftIndent), tabline - 8, 8, 6));
+
+ QPolygon cr2;
+ cr2.setPoints(3, qRound(leftIndent), tabline, qRound(leftIndent + 8), tabline, qRound(leftIndent), bottomline);
+ p.drawPolygon(cr2);
+ }
}
p.end();
}
@@ -231,7 +281,8 @@
}
for (qsizetype i = 0; i < tabValues.count(); ++i)
{
- fpo = QRect(static_cast<int>(tabValues[i].tabPosition - offset) - 3, 15, 8, 8);
+ int tabScreenX = tabToScreen(tabValues[i].tabPosition);
+ fpo = QRect(tabScreenX - 3, 15, 8, 8);
if (fpo.contains(m->pos()))
{
rulerCode = 3;
@@ -248,7 +299,9 @@
if ((rulerCode == 0) && (m->button() == Qt::LeftButton))
{
ParagraphStyle::TabRecord tb;
- tb.tabPosition = static_cast<double>(mPosX + offset);
+ // In RTL, logical position = rtlAnchor - screenX; in LTR = screenX + offset
+ tb.tabPosition = m_rtl ? static_cast<double>(width() - mPosX - offset)
+ : static_cast<double>(mPosX + offset);
tabValues.prepend(tb);
actTab = 0;
rulerCode = 3;
@@ -306,10 +359,13 @@
if (mousePressed && (mPosY < height()) && (mPosY > 0) && (mPosX > 0) && (mPosX < width()))
{
QApplication::changeOverrideCursor(QCursor(Qt::SizeHorCursor));
+ // In RTL, dragging right moves away from logical start (right edge), so negate delta
+ int dx = mouseX - mPosX;
+ int logicalDx = m_rtl ? -dx : dx;
switch (rulerCode)
{
case 1:
- firstLine -= mouseX - mPosX;
+ firstLine -= logicalDx;
if (firstLine + leftIndent + offset < offset)
firstLine += mouseX - mPosX;
if (firstLine + leftIndent > m_rulerWidth)
@@ -319,7 +375,7 @@
break;
case 2:
oldInd = leftIndent+firstLine;
- leftIndent -= mouseX - mPosX;
+ leftIndent -= logicalDx;
if (leftIndent < 0)
leftIndent = 0;
if (leftIndent > m_rulerWidth - 1)
@@ -330,7 +386,7 @@
update();
break;
case 3:
- tabValues[actTab].tabPosition -= mouseX - mPosX;
+ tabValues[actTab].tabPosition -= logicalDx;
if (tabValues[actTab].tabPosition < 0)
tabValues[actTab].tabPosition = 0;
if (tabValues[actTab].tabPosition > m_rulerWidth - 1)
@@ -347,7 +403,7 @@
}
if ((!mousePressed) && (mPosY < height()) && (mPosY > 0) && (mPosX > 0) && (mPosX < width()))
{
- setCursor(IconManager::instance().loadCursor("cursor-tabulator", 3));
+ setCursor(tabulatorCursor());
if (haveInd)
{
fpo = QRect(static_cast<int>(firstLine + leftIndent - offset) - 4, 0, 8, midline);
@@ -365,7 +421,8 @@
}
for (qsizetype i = 0; i < tabValues.count(); ++i)
{
- fpo = QRect(static_cast<int>(tabValues[i].tabPosition-offset) - 3, tabline, 8, 8);
+ int tabScreenX = tabToScreen(tabValues[i].tabPosition);
+ fpo = QRect(tabScreenX - 3, tabline, 8, 8);
if (fpo.contains(m->pos()))
{
setCursor(QCursor(Qt::SizeHorCursor));
@@ -761,6 +818,50 @@
tabData->setSuffix(unitSuffix);
}
+void Tabruler::setRtl(bool rtl)
+{
+ ruler->setRtl(rtl);
+ // Swap LeftTab/RightTab icons so the combo matches the mirrored ruler glyphs
+ IconManager& im = IconManager::instance();
+ int oldIndex = typeCombo->currentIndex();
+ bool blocked = typeCombo->blockSignals(true);
+ typeCombo->clear();
+ if (rtl)
+ {
+ typeCombo->addItem(im.loadIcon("tabulator-right"), tr("Right"), ParagraphStyle::LeftTab);
+ typeCombo->addItem(im.loadIcon("tabulator-center"), tr("Center"), ParagraphStyle::CenterTab);
+ typeCombo->addItem(im.loadIcon("tabulator-comma"), tr("Comma"), ParagraphStyle::CommaTab);
+ typeCombo->addItem(im.loadIcon("tabulator-dot"), tr("Period"), ParagraphStyle::PeriodTab);
+ typeCombo->addItem(im.loadIcon("tabulator-left"), tr("Left"), ParagraphStyle::RightTab);
+ }
+ else
+ {
+ typeCombo->addItem(im.loadIcon("tabulator-left"), tr("Left"), ParagraphStyle::LeftTab);
+ typeCombo->addItem(im.loadIcon("tabulator-center"), tr("Center"), ParagraphStyle::CenterTab);
+ typeCombo->addItem(im.loadIcon("tabulator-comma"), tr("Comma"), ParagraphStyle::CommaTab);
+ typeCombo->addItem(im.loadIcon("tabulator-dot"), tr("Period"), ParagraphStyle::PeriodTab);
+ typeCombo->addItem(im.loadIcon("tabulator-right"), tr("Right"), ParagraphStyle::RightTab);
+ }
+ typeCombo->setCurrentIndex(oldIndex);
+ typeCombo->blockSignals(blocked);
+
+ if (m_haveFirst)
+ {
+ if (rtl)
+ {
+ leftIndentData->setToolTip(tr("Indentation from the right for the whole paragraph"));
+ rightIndentData->setToolTip(tr("Indentation from the left for the whole paragraph"));
+ }
+ else
+ {
+ leftIndentData->setToolTip(tr("Indentation from the left for the whole paragraph"));
+ rightIndentData->setToolTip(tr("Indentation from the right for the whole paragraph"));
+ }
+ leftIndentLabel->setToolTip(leftIndentData->toolTip());
+ rightIndentLabel->setToolTip(rightIndentData->toolTip());
+ }
+}
+
void Tabruler::setTabs(const QList<ParagraphStyle::TabRecord>& tabs, int unit)
{
m_docUnitRatio = unitGetRatioFromIndex(unit);
Index: scribus/ui/tabruler.h
===================================================================
--- scribus/ui/tabruler.h (revision 27622)
+++ scribus/ui/tabruler.h (working copy)
@@ -40,6 +40,7 @@
bool mousePressed { false };
QList<ParagraphStyle::TabRecord> tabValues;
bool haveInd { false };
+ bool m_rtl { false };
int unitIndex { 0 };
int offset { 0 };
int actTab { -1 };
@@ -59,6 +60,7 @@
void removeActTab();
void moveFirstLine(double t);
void moveLeftIndent(double t);
+ void setRtl(bool rtl) { m_rtl = rtl; update(); }
signals:
void tabMoved(double);
@@ -80,6 +82,8 @@
void leaveEvent(QEvent*) override;
private:
+ int tabToScreen(double tabPos) const;
+ QCursor tabulatorCursor() const;
double m_iter { 10.0 }; // Result of unitRulerGetIter1FromIndex() for point unit
double m_iter2 { 100.0 }; // Result of unitRulerGetIter2FromIndex() for point unit
double m_rulerWidth { 0.0 };
@@ -99,6 +103,8 @@
virtual void setTabs(const QList<ParagraphStyle::TabRecord>& tabs, int unit);
+ void setRtl(bool rtl);
+
QList<ParagraphStyle::TabRecord> getTabVals() const;
double getFirstLine() const;
double getLeftIndent() const;
|
|
|
The left (end mark) is now shown on the left side. That's much better. But, when the paragraph is RTL, it's not possible to move the left mark: Scribus always moves the column indentation (the light blue area) instead of the paragraph's one. Also, the hanging indent on the right does not respect the values defined. P.S.: LTR seems to still work correclty. |
|
|
|
|
|
Here we go. rtl indent is fixed. As always please test. RTL ruler02.patch (50,143 bytes)
Index: scribus/pageitem_textframe.cpp
===================================================================
--- scribus/pageitem_textframe.cpp (revision 27622)
+++ scribus/pageitem_textframe.cpp (working copy)
@@ -1508,15 +1508,16 @@
}
if (current.isEmpty)
{
- if (style.rightMargin() == 0)
+ // In RTL, the logical left margin acts as the visual right margin
+ double visualRightMargin = (style.direction() == ParagraphStyle::RTL) ? style.leftMargin() : style.rightMargin();
+ if (visualRightMargin == 0)
{
- //addLine = true;
- current.rightMargin = 0.0;
+ current.rightMargin = 0.0 ;
}
else
{
if (current.lastInRowLine)
- current.rightMargin = style.rightMargin();
+ current.rightMargin = visualRightMargin;
else
current.rightMargin = 0.0;
}
@@ -1765,30 +1766,51 @@
current.leftIndent = 0.0;
if (current.addLeftIndent && ((maxDX == 0) || DropCmode || BulNumMode))
{
- current.leftIndent = style.leftMargin() + autoLeftIndent;
- if (itemText.isBlockStart(a))
+ if (style.direction() == ParagraphStyle::RTL)
{
- if (style.direction() == ParagraphStyle::RTL)
+ // RTL: logical left margin is on the visual right side
+ // logical right margin is on the visual left side
+ current.leftIndent = style.rightMargin();
+ current.rightIndent = style.leftMargin();
+
+ if (itemText.isBlockStart(a))
{
- // use rightIndent to not mess with old behavior
- current.rightIndent = style.firstIndent();
- // line width should consider RTL indent when it breaks the line.
- current.mustLineEnd = current.colRight - current.rightIndent;
+ current.rightIndent += style.firstIndent();
}
- else
+ // line width should consider RTL indent when it breaks the line.
+ current.mustLineEnd = current.colRight - current.rightIndent;
+ }
+ else
+ {
+ // LTR: Original behavior
+ current.leftIndent = style.leftMargin() + autoLeftIndent;
+ if (itemText.isBlockStart(a))
+ {
current.leftIndent += style.firstIndent();
- if (BulNumMode || DropCmode)
+ }
+ }
+
+ if (BulNumMode || DropCmode)
+ {
+ if (style.parEffectIndent())
{
- if (style.parEffectIndent())
+ double effectWidth = 0.0;
+ for (int j = i; shapedText.haveMoreText(j, glyphClusters); ++j)
{
- double effectWidth = 0.0;
- for (int j = i; shapedText.haveMoreText(j, glyphClusters); ++j)
- {
- const auto& glyph = glyphClusters[j];
- if (glyph.firstChar() != a)
- break;
- effectWidth += glyph.width();
- }
+ const auto & glyph = glyphClusters[j];
+ if (glyph.firstChar() != a)
+ break;
+ effectWidth += glyph.width();
+ }
+
+ if (style.direction() == ParagraphStyle::RTL)
+ {
+ current.rightIndent -= style.parEffectOffset() + effectWidth;
+ if (current.rightIndent < 0.0)
+ current.rightIndent = 0.0;
+ }
+ else
+ {
current.leftIndent -= style.parEffectOffset() + effectWidth;
if (current.leftIndent < 0.0)
{
@@ -2028,14 +2050,36 @@
}
}
// right tab stuff
- if (tabs.active)
+ QChar ch = itemText.text(a);
+
+ // Detect all possible decimal separators (ASCII, Eastern Arabic, Persian, System Locale)
+ bool isPoint = (ch == '.' || ch == QChar(0x066B) || ch == QLocale().decimalPoint());
+ bool isComma = (ch == ',' || ch == QChar(0x066B) || ch == QChar(0x060C) || ch == QLocale().decimalPoint());
+
+ bool isRTLDecimal = (style.direction() == ParagraphStyle::RTL && (tabs.status == TabCOMMA || tabs.status == TabPOINT));
+
+ if (isRTLDecimal)
{
- if (((itemText.text(a) == '.') && (tabs.status == TabPOINT)) || ((itemText.text(a) == ',') && (tabs.status == TabCOMMA)) || (itemText.text(a) == SpecialChars::TAB))
+ // In RTL, hitting the decimal separator turns the tab pull ON for the fractional part
+ if ((isPoint && tabs.status == TabPOINT) || (isComma && tabs.status == TabCOMMA))
{
+ tabs.active = true;
+ }
+ else if (ch == SpecialChars::TAB)
+ {
tabs.active = false;
tabs.status = TabNONE;
}
}
+ else if (tabs.active)
+ {
+ // Original LTR logic
+ if ((isPoint && tabs.status == TabPOINT) || (isComma && tabs.status == TabCOMMA) || (ch == SpecialChars::TAB))
+ {
+ tabs.active = false;
+ tabs.status = TabNONE;
+ }
+ }
// tab positioning
if (itemText.text(a) == SpecialChars::TAB)
{
@@ -2066,6 +2110,10 @@
}
}
tabs.active = (tabs.status != TabLEFT);
+ // FIX: Start inactive for RTL decimal tabs so the integer part advances normally
+ if (style.direction() == ParagraphStyle::RTL && (tabs.status == TabCOMMA || tabs.status == TabPOINT)) {
+ tabs.active = false;
+ }
if (current.xPos == tCurX) // no more tabs found
{
current.xPos = nextAutoTab (current, this);
Index: scribus/scribus.cpp
===================================================================
--- scribus/scribus.cpp (revision 27622)
+++ scribus/scribus.cpp (working copy)
@@ -2727,6 +2727,9 @@
// to go: (av)
contentPalette->update(doc->currentStyle);
setStyleEffects(doc->currentStyle.charStyle().effects());
+ view->horizRuler->setItem(currItem);
+ view->horizRuler->textMode(false);
+ view->horizRuler->update();
}
break;
case PageItem::Table:
@@ -6254,6 +6257,11 @@
contentPalette->updateTextDirection(i);
PageItem *currItem = doc->m_Selection->itemAt(0);
setTBvals(currItem);
+ if (currItem && currItem->isTextFrame())
+ {
+ view->horizRuler->setRtl(i == 1); // 1 = RTL, 0 = LTR
+ view->horizRuler->update();
+ }
}
void ScribusMainWindow::setNewParStyle(const QString& name)
Index: scribus/ui/hruler.cpp
===================================================================
--- scribus/ui/hruler.cpp (revision 27622)
+++ scribus/ui/hruler.cpp (working copy)
@@ -71,7 +71,6 @@
unitChange();
languageChange();
setContextMenuPolicy(Qt::CustomContextMenu);
-
connect(this, SIGNAL(customContextMenuRequested(QPoint)), SLOT(contextMenuRequested(QPoint)));
connect(m_contextMenu, SIGNAL(triggered(QAction*)), SLOT(tabTypeChanged(QAction*)));
connect(ScQApp, SIGNAL(iconSetChanged()), this, SLOT(languageChange())); // replace languageChange() if there are other icons than in context menu.
@@ -100,7 +99,14 @@
double Hruler::localToTextPos(int x) const
{
- return ((x + m_view->contentsX()) / m_scaling - textBase());
+ double canvasPos = (x + m_view->contentsX()) / m_scaling;
+ if (m_rtl && m_currItem)
+ {
+ double colWidth = (textWidth() - m_colGap * (m_cols - 1)) / m_cols;
+ double colEnd = textBase() + (colWidth + m_colGap) * (m_currCol - 1.0) + colWidth;
+ return colEnd - canvasPos;
+ }
+ return canvasPos - textBase();
}
void Hruler::shift(double pos)
@@ -152,29 +158,38 @@
return result;
}
- pos = textPosToLocal(m_firstIndent + m_leftMargin + (colWidth + m_colGap) * (m_currCol - 1.0));
- fpo = QRect(pos - 1, topline, grabRadius + 1, rulerheight / 2);
+ double colOffset = (colWidth + m_colGap) * (m_currCol - 1.0);
+ double colEnd = colOffset + colWidth;
+
+ // Map a logical value (distance from logical start) to canvas text position
+ auto logicalToCanvas = [&](double v) -> double {
+ return m_rtl ? (colEnd - v) : (colOffset + v);
+ };
+
+ // First Indent handle
+ pos = textPosToLocal(logicalToCanvas(m_firstIndent + m_leftMargin));
+ fpo = QRect(m_rtl ? (pos - grabRadius) : (pos - 1), topline, grabRadius + 1, rulerheight / 2);
if (fpo.contains(mp))
- {
return rc_indentFirst;
- }
- pos = textPosToLocal(m_leftMargin + (colWidth + m_colGap) * (m_currCol - 1.0));
- fpo = QRect(pos - 1, midline, grabRadius + 1, rulerheight / 2);
+
+ // Left Margin handle
+ pos = textPosToLocal(logicalToCanvas(m_leftMargin));
+ fpo = QRect(m_rtl ? (pos - grabRadius) : (pos - 1), midline, grabRadius + 1, rulerheight / 2);
if (fpo.contains(mp))
- {
return rc_leftMargin;
- }
- pos = textPosToLocal(m_rightMargin + (colWidth + m_colGap) * (m_currCol - 1.0));
- fpo = QRect(pos - grabRadius, midline, grabRadius, rulerheight / 2);
+
+ // Right Margin handle — rightMargin is always from the logical end side
+ pos = textPosToLocal(colOffset + m_rightMargin);
+ fpo = QRect(m_rtl ? (pos - 1) : (pos - grabRadius), midline, grabRadius, rulerheight / 2);
if (fpo.contains(mp))
- {
return rc_rightMargin;
- }
- if (m_tabValues.count() != 0)
+
+ // Tab handles
+ if (!m_tabValues.isEmpty())
{
for (int yg = 0; yg < m_tabValues.count(); yg++)
{
- pos = textPosToLocal(m_tabValues[yg].tabPosition + (colWidth + m_colGap) * (m_currCol - 1.0));
+ pos = textPosToLocal(logicalToCanvas(m_tabValues[yg].tabPosition));
fpo = QRect(pos - grabRadius, tabline, 2 * grabRadius, rulerheight / 2 + 2);
if (fpo.contains(mp))
{
@@ -312,7 +327,7 @@
void Hruler::enterEvent(QEnterEvent* e)
{
if (m_textEditMode)
- QApplication::changeOverrideCursor(IconManager::instance().loadCursor("cursor-tabulator", 3));
+ QApplication::changeOverrideCursor(tabulatorCursor());
}
void Hruler::leaveEvent(QEvent *m)
@@ -347,10 +362,24 @@
QApplication::changeOverrideCursor(QCursor(Qt::SizeHorCursor));
double toplimit = textWidth() + m_distRight - (m_colGap * (m_cols - 1.0)) - 1.0;
double toplimit2 = textWidth() + m_distLeft - (m_colGap * (m_cols - 1.0)) - 1.0;
+ // In RTL dragging right moves away from the logical start (right edge),
+ // so negate the delta for all logical-position markers.
+ double dx = (m_mouseX - mousePos.x()) / m_scaling;
+ double logicalDx = m_rtl ? -dx : dx;
+ // Helper: convert a logical value (distance from logical start) to
+ // canvas-relative position (distance from textBase) for MarkerMoved.
+ // In LTR: canvasRel = logicalValue (distance from left edge)
+ // In RTL: canvasRel = colEnd - textBase() - logicalValue
+ double colOffset = (colWidth + m_colGap) * (m_currCol - 1.0);
+ auto toCanvasRel = [&](double logicalValue) -> double {
+ if (m_rtl)
+ return colOffset + colWidth - logicalValue;
+ return logicalValue;
+ };
switch (m_rulerCode)
{
case rc_leftFrameDist:
- m_distLeft -= (m_mouseX - mousePos.x()) / m_scaling;
+ m_distLeft -= dx;
if (m_distLeft < 0)
m_distLeft = 0;
if (m_distLeft > toplimit2)
@@ -359,7 +388,7 @@
repaint();
break;
case rc_rightFrameDist:
- m_distRight += (m_mouseX - mousePos.x()) / m_scaling;
+ m_distRight += dx;
if (m_distRight < 0)
m_distRight = 0;
if (m_distRight > toplimit)
@@ -368,41 +397,41 @@
repaint();
break;
case rc_indentFirst:
- m_firstIndent -= (m_mouseX - mousePos.x()) / m_scaling;
+ m_firstIndent -= logicalDx;
if (m_firstIndent + m_leftMargin < 0)
m_firstIndent = -m_leftMargin;
if (m_firstIndent + m_leftMargin > colWidth)
m_firstIndent = colWidth - m_leftMargin;
- emit MarkerMoved(textBase(), m_firstIndent + m_leftMargin);
+ emit MarkerMoved(textBase(), toCanvasRel(m_firstIndent + m_leftMargin));
repaint();
break;
case rc_leftMargin:
oldInd = m_leftMargin + m_firstIndent;
- m_leftMargin -= (m_mouseX - mousePos.x()) / m_scaling;
+ m_leftMargin -= logicalDx;
if (m_leftMargin < 0)
m_leftMargin = 0;
if (m_leftMargin > colWidth - 1)
m_leftMargin = colWidth - 1;
m_firstIndent = oldInd - m_leftMargin;
- emit MarkerMoved(textBase(), m_leftMargin);
+ emit MarkerMoved(textBase(), toCanvasRel(m_leftMargin));
repaint();
break;
case rc_rightMargin:
- m_rightMargin -= (m_mouseX - mousePos.x()) / m_scaling;
+ m_rightMargin -= logicalDx;
if (m_rightMargin < 0)
m_rightMargin = 0;
if (m_rightMargin > colWidth - 1)
m_rightMargin = colWidth - 1;
- emit MarkerMoved(textBase(), m_rightMargin);
+ emit MarkerMoved(textBase(), toCanvasRel(m_rightMargin));
repaint();
break;
case rc_tab:
- m_tabValues[m_currTab].tabPosition -= (m_mouseX - mousePos.x()) / m_scaling;
+ m_tabValues[m_currTab].tabPosition -= logicalDx;
if (m_tabValues[m_currTab].tabPosition < 0)
m_tabValues[m_currTab].tabPosition = 0;
if (m_tabValues[m_currTab].tabPosition > colWidth - 1)
m_tabValues[m_currTab].tabPosition = colWidth - 1;
- emit MarkerMoved(textBase(), m_tabValues[m_currTab].tabPosition);
+ emit MarkerMoved(textBase(), toCanvasRel(m_tabValues[m_currTab].tabPosition));
updateTabList();
repaint();
break;
@@ -414,24 +443,16 @@
}
if ((!m_mousePressed) && (mousePos.y() < height()) && (mousePos.y() > 0) && (mousePos.x() > colStart - 2 * m_doc->guidesPrefs().grabRadius) && (mousePos.x() < colEnd + 2 * m_doc->guidesPrefs().grabRadius))
{
- setCursor(IconManager::instance().loadCursor("cursor-tabulator", 3));
+ setCursor(tabulatorCursor());
switch (findRulerHandle(m->pos(), m_doc->guidesPrefs().grabRadius))
{
case rc_leftFrameDist:
- setCursor(QCursor(Qt::SplitHCursor));
- break;
case rc_rightFrameDist:
setCursor(QCursor(Qt::SplitHCursor));
break;
case rc_indentFirst:
- setCursor(QCursor(Qt::SizeHorCursor));
- break;
case rc_leftMargin:
- setCursor(QCursor(Qt::SizeHorCursor));
- break;
case rc_rightMargin:
- setCursor(QCursor(Qt::SizeHorCursor));
- break;
case rc_tab:
setCursor(QCursor(Qt::SizeHorCursor));
break;
@@ -439,8 +460,8 @@
break;
}
draw(mousePos.x());
- double marker = localToTextPos(mousePos.x());
- emit MarkerMoved(textBase(), marker);
+ double canvasX = (mousePos.x() + m_view->contentsX()) / m_scaling;
+ emit MarkerMoved(textBase(), canvasX - textBase());
return;
}
if (m_mousePressed && (m_rulerCode == rc_tab) && ((mousePos.y() > height()) || (mousePos.y() < 0)))
@@ -479,6 +500,7 @@
const ParagraphStyle& style(m_doc->appMode == modeEdit ? tItem->currentStyle() : tItem->itemText.defaultStyle());
TabManager *dia = new TabManager(this, m_doc->unitIndex(), style.tabValues(), tItem->columnWidth());
+ dia->setRtl(m_rtl);
if (dia->exec())
{
ParagraphStyle paraStyle;
@@ -520,7 +542,6 @@
QColor columnColor = markerColor;
columnColor.setAlphaF(0.2f);
const QColor& textColor = palette.color(QPalette::Text);
-
p.save();
if (m_reverse)
@@ -540,101 +561,179 @@
p.fillRect(QRect(QPoint(textPosToLocal(pos), 0), QPoint(textPosToLocal(endPos), bottomline)), columnColor);
drawTextMarks(pos, endPos, p);
-
- // start bracket
- p.setPen(QPen(marginColor, 1, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
- int xPos = textPosToLocal(pos);
- p.drawLine(xPos, 0, xPos, bottomline);
- p.drawLine(xPos, 0, (xPos + 8), 0);
-
- // First Indent
- xPos = textPosToLocal(pos + m_firstIndent + m_leftMargin);
- p.setPen(QPen(textColor, 1, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
- p.drawLine(xPos, tabline - 2, xPos, bottomline);
- p.setPen(Qt::NoPen);
- p.setRenderHints(QPainter::Antialiasing, true);
- p.drawRect(QRect(xPos, tabline - 8, 8, 6));
- // Left Margin
- xPos = textPosToLocal(pos + m_leftMargin);
- QPolygon cr2;
- cr2.setPoints(3, xPos, tabline, xPos + 8, tabline, xPos, bottomline);
- p.drawPolygon(cr2);
-
- // Right Margin
- xPos = textPosToLocal(pos + m_rightMargin);
- QPolygon cr3;
- cr3.setPoints(3, xPos - 8, tabline, xPos, tabline, xPos, bottomline);
- p.drawPolygon(cr3);
+ if (m_rtl)
+ {
+ // RTL: logical start is the RIGHT edge of the column
- p.setRenderHints(QPainter::Antialiasing, false);
+ // start bracket on the right
+ p.setPen(QPen(marginColor, 1, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
+ int xPos = textPosToLocal(endPos);
+ p.drawLine(xPos, 0, xPos, bottomline);
+ p.drawLine(xPos, 0, xPos - 8, 0);
- // Tabulator
- if (!m_tabValues.isEmpty())
- {
- int pWidth = 1;
- p.setPen(QPen(textColor, pWidth * 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
- for (int yg = 0; yg < m_tabValues.count(); ++yg)
+ // First Indent - measured from right edge inward
+ xPos = textPosToLocal(endPos - m_leftMargin - m_firstIndent);
+ p.setPen(QPen(textColor, 1, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
+ p.drawLine(xPos, tabline - 2, xPos, bottomline);
+ p.setPen(Qt::NoPen);
+ p.setRenderHints(QPainter::Antialiasing, true);
+ p.drawRect(QRect(xPos - 8, tabline - 8, 8, 6));
+
+ // Left Margin (logical start = right side), triangle opens left
+ xPos = textPosToLocal(endPos - m_leftMargin);
+ QPolygon cr2;
+ cr2.setPoints(3, xPos, tabline, xPos - 8, tabline, xPos, bottomline);
+ p.drawPolygon(cr2);
+
+ // Right Margin (logical end = left side), triangle opens right
+ xPos = textPosToLocal(endPos - m_rightMargin);
+ QPolygon cr3;
+ cr3.setPoints(3, xPos, tabline, xPos + 8, tabline, xPos, bottomline);
+ p.drawPolygon(cr3);
+
+ p.setRenderHints(QPainter::Antialiasing, false);
+
+ // Tabulators - measured from right edge
+ if (!m_tabValues.isEmpty())
{
- xPos = textPosToLocal(pos + m_tabValues[yg].tabPosition);
- if (pos + m_tabValues[yg].tabPosition >= endPos)
- continue;
- switch (m_tabValues[yg].tabType)
+ int pWidth = 1;
+ p.setPen(QPen(textColor, pWidth * 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
+ for (int yg = 0; yg < m_tabValues.count(); ++yg)
{
- case ParagraphStyle::LeftTab:
- if (m_reverse)
- {
- p.save();
- p.translate(pos + m_tabValues[yg].tabPosition, 0);
- p.scale(-1, 1);
- p.drawLine(0, tabline, 0, bottomline - pWidth);
- p.drawLine(0, bottomline - pWidth, 8, bottomline - pWidth);
- p.restore();
- }
- else
- {
+ xPos = textPosToLocal(endPos - m_tabValues[yg].tabPosition);
+ if (m_tabValues[yg].tabPosition >= colWidth)
+ continue;
+ switch (m_tabValues[yg].tabType)
+ {
+ case ParagraphStyle::LeftTab:
p.drawLine(xPos, tabline, xPos, bottomline - pWidth);
+ p.drawLine(xPos, bottomline - pWidth, xPos - 8, bottomline - pWidth);
+ break;
+ case ParagraphStyle::RightTab:
+ p.drawLine(xPos, tabline, xPos, bottomline - pWidth);
p.drawLine(xPos, bottomline - pWidth, xPos + 8, bottomline - pWidth);
- }
- break;
- case ParagraphStyle::RightTab:
- if (m_reverse)
- {
- p.save();
- p.translate(pos + m_tabValues[yg].tabPosition, 0);
- p.scale(-1, 1);
- p.drawLine(0, tabline, 0, bottomline - pWidth);
- p.drawLine(0, bottomline - pWidth, -8, bottomline - pWidth);
- p.restore();
- }
- else
- {
+ break;
+ case ParagraphStyle::CommaTab:
+ case ParagraphStyle::PeriodTab:
p.drawLine(xPos, tabline, xPos, bottomline - pWidth);
- p.drawLine(xPos, bottomline - pWidth, xPos - 8, bottomline - pWidth);
- }
- break;
- case ParagraphStyle::CommaTab:
- case ParagraphStyle::PeriodTab:
- p.drawLine(xPos, tabline, xPos, bottomline - pWidth);
- p.drawLine(xPos - 4, bottomline - pWidth, xPos + 4, bottomline - pWidth);
- p.drawLine(xPos + 3, bottomline - 3 - pWidth, xPos + 2, bottomline - 3 - pWidth);
- break;
- case ParagraphStyle::CenterTab:
- p.drawLine(xPos, tabline, xPos, bottomline - pWidth);
- p.drawLine(xPos - 4, bottomline - pWidth, xPos + 4, bottomline - pWidth);
- break;
- default:
- break;
+ p.drawLine(xPos - 4, bottomline - pWidth, xPos + 4, bottomline - pWidth);
+ p.drawLine(xPos - 3, bottomline - 3 - pWidth, xPos - 2, bottomline - 3 - pWidth);
+ break;
+ case ParagraphStyle::CenterTab:
+ p.drawLine(xPos, tabline, xPos, bottomline - pWidth);
+ p.drawLine(xPos - 4, bottomline - pWidth, xPos + 4, bottomline - pWidth);
+ break;
+ default:
+ break;
+ }
}
}
+
+ // end bracket on the left
+ p.setPen(QPen(marginColor, 1, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
+ xPos = textPosToLocal(pos);
+ p.drawLine(xPos, 0, xPos, bottomline - 1);
+ p.drawLine(xPos, 0, xPos + 8, 0);
}
-
- // end bracket
- p.setPen(QPen(marginColor, 1, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
- xPos = textPosToLocal(endPos);
- p.drawLine(xPos, 0, xPos, bottomline - 1);
- p.drawLine(xPos, 0, xPos - 8, 0);
+ else
+ {
+ // LTR: original logic
+ // start bracket
+ p.setPen(QPen(marginColor, 1, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
+ int xPos = textPosToLocal(pos);
+ p.drawLine(xPos, 0, xPos, bottomline);
+ p.drawLine(xPos, 0, (xPos + 8), 0);
+
+ // First Indent
+ xPos = textPosToLocal(pos + m_firstIndent + m_leftMargin);
+ p.setPen(QPen(textColor, 1, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
+ p.drawLine(xPos, tabline - 2, xPos, bottomline);
+ p.setPen(Qt::NoPen);
+ p.setRenderHints(QPainter::Antialiasing, true);
+ p.drawRect(QRect(xPos, tabline - 8, 8, 6));
+
+ // Left Margin
+ xPos = textPosToLocal(pos + m_leftMargin);
+ QPolygon cr2;
+ cr2.setPoints(3, xPos, tabline, xPos + 8, tabline, xPos, bottomline);
+ p.drawPolygon(cr2);
+
+ // Right Margin
+ xPos = textPosToLocal(pos + m_rightMargin);
+ QPolygon cr3;
+ cr3.setPoints(3, xPos - 8, tabline, xPos, tabline, xPos, bottomline);
+ p.drawPolygon(cr3);
+
+ p.setRenderHints(QPainter::Antialiasing, false);
+
+ // Tabulator
+ if (!m_tabValues.isEmpty())
+ {
+ int pWidth = 1;
+ p.setPen(QPen(textColor, pWidth * 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
+ for (int yg = 0; yg < m_tabValues.count(); ++yg)
+ {
+ xPos = textPosToLocal(pos + m_tabValues[yg].tabPosition);
+ if (pos + m_tabValues[yg].tabPosition >= endPos)
+ continue;
+ switch (m_tabValues[yg].tabType)
+ {
+ case ParagraphStyle::LeftTab:
+ if (m_reverse)
+ {
+ p.save();
+ p.translate(pos + m_tabValues[yg].tabPosition, 0);
+ p.scale(-1, 1);
+ p.drawLine(0, tabline, 0, bottomline - pWidth);
+ p.drawLine(0, bottomline - pWidth, 8, bottomline - pWidth);
+ p.restore();
+ }
+ else
+ {
+ p.drawLine(xPos, tabline, xPos, bottomline - pWidth);
+ p.drawLine(xPos, bottomline - pWidth, xPos + 8, bottomline - pWidth);
+ }
+ break;
+ case ParagraphStyle::RightTab:
+ if (m_reverse)
+ {
+ p.save();
+ p.translate(pos + m_tabValues[yg].tabPosition, 0);
+ p.scale(-1, 1);
+ p.drawLine(0, tabline, 0, bottomline - pWidth);
+ p.drawLine(0, bottomline - pWidth, -8, bottomline - pWidth);
+ p.restore();
+ }
+ else
+ {
+ p.drawLine(xPos, tabline, xPos, bottomline - pWidth);
+ p.drawLine(xPos, bottomline - pWidth, xPos - 8, bottomline - pWidth);
+ }
+ break;
+ case ParagraphStyle::CommaTab:
+ case ParagraphStyle::PeriodTab:
+ p.drawLine(xPos, tabline, xPos, bottomline - pWidth);
+ p.drawLine(xPos - 4, bottomline - pWidth, xPos + 4, bottomline - pWidth);
+ p.drawLine(xPos + 3, bottomline - 3 - pWidth, xPos - 2, bottomline - 3 - pWidth);
+ break;
+ case ParagraphStyle::CenterTab:
+ p.drawLine(xPos, tabline, xPos, bottomline - pWidth);
+ p.drawLine(xPos - 4, bottomline - pWidth, xPos + 4, bottomline - pWidth);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ // end bracket
+ p.setPen(QPen(marginColor, 1, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
+ xPos = textPosToLocal(endPos);
+ p.drawLine(xPos, 0, xPos, bottomline - 1);
+ p.drawLine(xPos, 0, xPos - 8, 0);
+ }
}
p.restore();
}
@@ -684,17 +783,21 @@
firstMark = ceil(m_offset / m_iter2) * m_iter2 - m_offset;
int markC = static_cast<int>(ceil(m_offset / m_iter2));
+ const bool isRtl = m_rtl && (m_currItem != nullptr);
+ const int pageRightMarkC = static_cast<int>(round(m_doc->currentPage()->width() / m_iter2));
+
QString tx;
double xl, frac;
while (firstMark < cc)
{
+ int effectiveMarkC = isRtl ? (pageRightMarkC - markC) : markC;
switch (m_doc->unitIndex())
{
case SC_MM:
- tx = QString::number(markC * m_iter2 / (m_iter2 / 100) / m_cor);
+ tx = QString::number(effectiveMarkC * m_iter2 / (m_iter2 / 100) / m_cor);
break;
case SC_IN:
- xl = (markC * m_iter2 / m_iter2) / m_cor;
+ xl = (effectiveMarkC * m_iter2 / m_iter2) / m_cor;
tx = QString::number(static_cast<int>(xl));
frac = fabs(xl - static_cast<int>(xl));
if ((static_cast<int>(xl) == 0) && (frac > 0.1))
@@ -708,13 +811,13 @@
break;
case SC_P:
case SC_C:
- tx = QString::number(markC * m_iter2 / (m_iter2 / 5) / m_cor);
+ tx = QString::number(effectiveMarkC * m_iter2 / (m_iter2 / 5) / m_cor);
break;
case SC_CM:
- tx = QString::number(markC * m_iter2 / m_iter2 / m_cor);
+ tx = QString::number(effectiveMarkC * m_iter2 / m_iter2 / m_cor);
break;
default:
- tx = QString::number(markC * m_iter2);
+ tx = QString::number(effectiveMarkC * m_iter2);
break;
}
p.drawLine(qRound(firstMark * sc), scaleL, qRound(firstMark * sc), bottomline);
@@ -748,19 +851,35 @@
int xli = textPosToLocal(xl);
p.drawLine(xli, scaleS, xli, bottomline);
}
- for (xl = pos; xl < endPos; xl += m_iter2)
+ // LTR: start at pos, step right, dist = xl - pos (0 at left edge, grows right)
+ // RTL: start at endPos, step left, dist = endPos - xl (0 at right edge, grows left)
+ double xlStart = m_rtl ? endPos : pos;
+ double xlStep = m_rtl ? -m_iter2 : m_iter2;
+ for (xl = xlStart; m_rtl ? (xl >= pos) : (xl < endPos); xl += xlStep)
{
int xli = textPosToLocal(xl);
p.drawLine(xli, scaleL, xli, bottomline);
+ double dist = m_rtl ? (endPos - xl) : (xl - pos);
+ auto drawNum = [&](const QString& tx) {
+ if (m_reverse) {
+ p.save();
+ p.translate(xli - 2.0, 0);
+ p.scale(-1, 1);
+ drawNumber(tx, 0, p);
+ p.restore();
+ } else {
+ drawNumber(tx, xli + 2, p);
+ }
+ };
switch (m_doc->unitIndex())
{
case SC_IN:
{
QString tx;
- int num1 = static_cast<int>((xl - pos) / m_iter2 / m_cor);
+ int num1 = static_cast<int>(dist / m_iter2 / m_cor);
if (num1 != 0)
tx = QString::number(num1);
- double frac = (xl / m_iter2 / m_cor) - num1;
+ double frac = (dist / m_iter2 / m_cor) - num1;
if ((frac > 0.24) && (frac < 0.26))
tx += QChar(0xBC);
if ((frac > 0.49) && (frac < 0.51))
@@ -767,65 +886,20 @@
tx += QChar(0xBD);
if ((frac > 0.74) && (frac < 0.76))
tx += QChar(0xBE);
- if (m_reverse)
- {
- p.save();
- p.translate(xli - 2.0, 0);
- p.scale(-1, 1);
- drawNumber(tx, 0, p);
- p.restore();
- }
- else
- drawNumber(tx, xli + 2, p);
+ drawNum(tx);
break;
}
case SC_P:
- if (m_reverse)
- {
- p.save();
- p.translate(xli - 2.0, 0);
- p.scale(-1, 1);
- drawNumber(QString::number((xl - pos) / m_iter / m_cor), 0, p);
- p.restore();
- }
- else
- drawNumber(QString::number((xl - pos) / m_iter / m_cor), xli + 2, p);
+ drawNum(QString::number(dist / m_iter / m_cor));
break;
case SC_CM:
- if (m_reverse)
- {
- p.save();
- p.translate(xli - 2.0, 0);
- p.scale(-1, 1);
- drawNumber(QString::number((xl - pos) / m_iter / 10 / m_cor), 0, p);
- p.restore();
- }
- else
- drawNumber(QString::number((xl - pos) / m_iter / 10 / m_cor), xli + 2, p);
+ drawNum(QString::number(dist / m_iter / 10 / m_cor));
break;
case SC_C:
- if (m_reverse)
- {
- p.save();
- p.translate(xli - 2.0, 0);
- p.scale(-1, 1);
- drawNumber(QString::number((xl - pos) / m_iter / m_cor), 0, p);
- p.restore();
- }
- else
- drawNumber(QString::number((xl - pos) / m_iter / m_cor), xli + 2, p);
+ drawNum(QString::number(dist / m_iter / m_cor));
break;
default:
- if (m_reverse)
- {
- p.save();
- p.translate(xli - 2.0, 0);
- p.scale(-1, 1);
- drawNumber(QString::number((xl - pos) / m_iter * 10 / m_cor), 0, p);
- p.restore();
- }
- else
- drawNumber(QString::number((xl - pos) / m_iter * 10 / m_cor), xli + 2, p);
+ drawNum(QString::number(dist / m_iter * 10 / m_cor));
break;
}
}
@@ -846,6 +920,18 @@
p.drawText(x,y,txt);
}
+QCursor Hruler::tabulatorCursor() const
+{
+ QCursor c = IconManager::instance().loadCursor("cursor-tabulator", 3);
+ if (m_rtl)
+ {
+ QPixmap pix = c.pixmap();
+ QPixmap mirrored = pix.transformed(QTransform(-1, 0, 0, 1, 0, 0));
+ c = QCursor(mirrored, pix.width() - c.hotSpot().x() - 1, c.hotSpot().y());
+ }
+ return c;
+}
+
double Hruler::ruleSpacing() const
{
return m_iter;
@@ -899,10 +985,17 @@
- 2 * m_lineCorr) / item->columns();
m_rightMargin = columnWidth - currentStyle.rightMargin();
m_reverse = item->imageFlippedH();
+ setRtl(item->currentStyle().direction() == ParagraphStyle::RTL);
m_textEditMode = true;
m_tabValues = currentStyle.tabValues();
}
+void Hruler::setRtl(bool rtl)
+{
+ m_rtl = rtl;
+ languageChange();
+}
+
void Hruler::updateTabList()
{
ParagraphStyle::TabRecord tb;
@@ -1087,11 +1180,22 @@
QSignalBlocker sigMenu(m_contextMenu);
m_contextMenu->clear();
- m_contextMenu->addAction(im.loadIcon("tabulator-left"), tr("Left"))->setData(ParagraphStyle::LeftTab);
- m_contextMenu->addAction(im.loadIcon("tabulator-center"), tr("Center"))->setData(ParagraphStyle::CenterTab);
- m_contextMenu->addAction(im.loadIcon("tabulator-comma"), tr("Comma"))->setData(ParagraphStyle::CommaTab);
- m_contextMenu->addAction(im.loadIcon("tabulator-dot"), tr("Period"))->setData(ParagraphStyle::PeriodTab);
- m_contextMenu->addAction(im.loadIcon("tabulator-right"), tr("Right"))->setData(ParagraphStyle::RightTab);
+ if (m_rtl)
+ {
+ m_contextMenu->addAction(im.loadIcon("tabulator-right"), tr("Right"))->setData(ParagraphStyle::LeftTab);
+ m_contextMenu->addAction(im.loadIcon("tabulator-center"), tr("Center"))->setData(ParagraphStyle::CenterTab);
+ m_contextMenu->addAction(im.loadIcon("tabulator-comma"), tr("Comma"))->setData(ParagraphStyle::CommaTab);
+ m_contextMenu->addAction(im.loadIcon("tabulator-dot"), tr("Period"))->setData(ParagraphStyle::PeriodTab);
+ m_contextMenu->addAction(im.loadIcon("tabulator-left"), tr("Left"))->setData(ParagraphStyle::RightTab);
+ }
+ else
+ {
+ m_contextMenu->addAction(im.loadIcon("tabulator-left"), tr("Left"))->setData(ParagraphStyle::LeftTab);
+ m_contextMenu->addAction(im.loadIcon("tabulator-center"), tr("Center"))->setData(ParagraphStyle::CenterTab);
+ m_contextMenu->addAction(im.loadIcon("tabulator-comma"), tr("Comma"))->setData(ParagraphStyle::CommaTab);
+ m_contextMenu->addAction(im.loadIcon("tabulator-dot"), tr("Period"))->setData(ParagraphStyle::PeriodTab);
+ m_contextMenu->addAction(im.loadIcon("tabulator-right"), tr("Right"))->setData(ParagraphStyle::RightTab);
+ }
}
void Hruler::contextMenuRequested(QPoint mouse)
Index: scribus/ui/hruler.h
===================================================================
--- scribus/ui/hruler.h (revision 27622)
+++ scribus/ui/hruler.h (working copy)
@@ -66,6 +66,7 @@
double ruleSpacing() const;
void setItem(PageItem * item);
+ void setRtl(bool rtl);
void textMode(bool state) { m_textEditMode = state; }
double textBase() const; // left text edge in canvas coord
double textWidth() const;
@@ -97,6 +98,7 @@
void drawMarker(QPainter& p) const;
void drawNumber(const QString& num, int startx, QPainter & p) const;
void updateTabList();
+ QCursor tabulatorCursor() const;
PageItem* m_currItem {nullptr};
QList<ParagraphStyle::TabRecord> m_tabValues;
@@ -106,6 +108,7 @@
bool m_drawMark {false};
bool m_mousePressed {false};
bool m_reverse {false};
+ bool m_rtl {false};
bool m_textEditMode {false};
double m_colGap {0.0};
double m_cor {0.0};
Index: scribus/ui/propertywidget_alignment.cpp
===================================================================
--- scribus/ui/propertywidget_alignment.cpp (revision 27622)
+++ scribus/ui/propertywidget_alignment.cpp (working copy)
@@ -17,8 +17,10 @@
#include "scribus.h"
#include "scribusapp.h"
#include "scribusdoc.h"
+#include "scribusview.h"
#include "selection.h"
#include "tabmanager.h"
+#include "hruler.h"
PropertyWidget_Alignment::PropertyWidget_Alignment(QWidget* parent) : QFrame(parent)
{
@@ -328,6 +330,12 @@
m_doc->itemSelection_SetAlignment(ParagraphStyle::LeftAligned, &tempSelection);
textAlignment->setTypeStyle(ParagraphStyle::LeftAligned);
}
+ // Update the horizontal ruler to reflect the new direction
+ if (m_item->isTextFrame())
+ {
+ m_ScMW->view->horizRuler->setRtl(d == ParagraphStyle::RTL);
+ m_ScMW->view->horizRuler->update();
+ }
}
void PropertyWidget_Alignment::handleFirstLinePolicy(int radioFlop)
Index: scribus/ui/smpstylewidget.cpp
===================================================================
--- scribus/ui/smpstylewidget.cpp (revision 27622)
+++ scribus/ui/smpstylewidget.cpp (working copy)
@@ -265,6 +265,7 @@
tabList->setTabs(tabs, unitIndex, hasParentTabs);
tabList->setParentTabs(parent->tabValues());
+ tabList->setRtl(pstyle->direction() == ParagraphStyle::RTL);
tabList->setLeftIndentValue(pstyle->leftMargin() * unitRatio,pstyle->isInhLeftMargin());
tabList->setParentLeftIndent(parent->leftMargin() * unitRatio);
@@ -386,6 +387,7 @@
alignment->setStyle(pstyle->alignment(), direction->getStyle());
tabList->setTabs(pstyle->tabValues(), unitIndex);
+ tabList->setRtl(pstyle->direction() == ParagraphStyle::RTL);
tabList->setLeftIndentValue(pstyle->leftMargin() * unitRatio);
tabList->setFirstLineValue(pstyle->firstIndent() * unitRatio);
tabList->setRightIndentValue(pstyle->rightMargin() * unitRatio);
@@ -759,6 +761,18 @@
}
tabList->setTabs(t, unitIndex);
+ // Mirror ruler for RTL — only if all selected styles agree on RTL direction
+ bool isRtl = (pstyles[0]->direction() == ParagraphStyle::RTL);
+ for (int i = 1; i < pstyles.count(); ++i)
+ {
+ if (pstyles[i]->direction() != pstyles[0]->direction())
+ {
+ isRtl = false;
+ break;
+ }
+ }
+ tabList->setRtl(isRtl);
+
double l = -4000.0;
for (int i = 0; i < pstyles.count(); ++i)
{
Index: scribus/ui/smtextstyles.cpp
===================================================================
--- scribus/ui/smtextstyles.cpp (revision 27622)
+++ scribus/ui/smtextstyles.cpp (working copy)
@@ -758,6 +758,9 @@
for (int i = 0; i < m_selection.count(); ++i)
m_selection[i]->setDirection(style);
+ // Mirror the tab ruler to match the new direction
+ m_pwidget->tabList->setRtl(style == ParagraphStyle::RTL);
+
slotSelectionDirty();
}
Index: scribus/ui/tabmanager.cpp
===================================================================
--- scribus/ui/tabmanager.cpp (revision 27622)
+++ scribus/ui/tabmanager.cpp (working copy)
@@ -60,3 +60,8 @@
{
m_tabList = tabList;
}
+
+void TabManager::setRtl(bool rtl)
+{
+ TabList->setRtl(rtl);
+}
Index: scribus/ui/tabmanager.h
===================================================================
--- scribus/ui/tabmanager.h (revision 27622)
+++ scribus/ui/tabmanager.h (working copy)
@@ -30,6 +30,7 @@
public slots:
void exitOK();
void setTabList(const QList<ParagraphStyle::TabRecord>& tabList);
+ void setRtl(bool rtl);
private:
QHBoxLayout* layout10;
Index: scribus/ui/tabruler.cpp
===================================================================
--- scribus/ui/tabruler.cpp (revision 27622)
+++ scribus/ui/tabruler.cpp (working copy)
@@ -82,6 +82,23 @@
update();
}
+int RulerT::tabToScreen(double tabPos) const
+{
+ return static_cast<int>((m_rtl ? width() - tabPos : tabPos) - offset);
+}
+
+QCursor RulerT::tabulatorCursor() const
+{
+ QCursor c = IconManager::instance().loadCursor("cursor-tabulator", 3);
+ if (m_rtl)
+ {
+ QPixmap pix = c.pixmap();
+ QPixmap mirrored = pix.transformed(QTransform(-1, 0, 0, 1, 0, 0));
+ c = QCursor(mirrored, pix.width() - c.hotSpot().x() - 1, c.hotSpot().y());
+ }
+ return c;
+}
+
void RulerT::paintEvent(QPaintEvent *)
{
double xl;
@@ -108,53 +125,67 @@
p.setFont(font());
p.setPen(QPen(textColor, 1, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
- for (xl = 0; xl < width() + offset; xl += m_iter)
- {
- if (xl < offset)
- continue;
- p.drawLine(qRound(xl), scaleS, qRound(xl), bottomline);
- }
+ // In RTL the logical origin (value 0) is the right edge (content x = width()).
+ // All three tick grids start there and step LEFT by exact multiples of their
+ // spacing, so ticks stay evenly spaced and the numbers come out whole.
+ // Visible content range is [offset, width() + offset].
- for (xl = 0; xl < width() + (m_iter2 / 2) + offset; xl += (m_iter2 / 2))
- {
- if (xl < offset)
- continue;
- p.drawLine(qRound(xl), scaleM, qRound(xl), bottomline);
- }
+ // Small and medium ticks — unified loop for RTL/LTR
+ auto drawTicks = [&](double step, int yTop) {
+ double xl;
+ if (m_rtl)
+ {
+ for (xl = width(); xl >= offset; xl -= step)
+ p.drawLine(qRound(xl), yTop, qRound(xl), bottomline);
+ }
+ else
+ {
+ for (xl = ceil(offset / step) * step; xl < width() + step + offset; xl += step)
+ p.drawLine(qRound(xl), yTop, qRound(xl), bottomline);
+ }
+ };
+ drawTicks(m_iter, scaleS);
+ drawTicks(m_iter2 / 2.0, scaleM);
- for (xl = 0; xl < width() + (m_iter2 / 2) + offset; xl += m_iter2)
+ // Large ticks + numbers
+ double startL = m_rtl ? width() : 0.0;
+ for (xl = startL;
+ m_rtl ? (xl > offset - 20) : (xl < width() + (m_iter2 / 2) + offset);
+ xl += (m_rtl ? -m_iter2 : m_iter2))
{
- if (xl < offset - 20) // -20px buffer to draw number that start outside the clipping rect
+ if (!m_rtl && xl < offset - 20) // -20px buffer to draw number that starts outside the clipping rect
continue;
p.drawLine(qRound(xl), scaleL, qRound(xl), bottomline);
+ // RTL: numbers count from the right edge (width() - xl), LTR: from left (xl)
+ double dispXl = m_rtl ? (width() - xl) : xl;
switch (unitIndex)
{
- case SC_IN:
- {
- QString tx;
- int num1 = static_cast<int>(xl / m_iter2);
- if (num1 != 0)
- tx = QString::number(num1);
- double frac = (xl / m_iter2) - num1;
- if ((frac > 0.24) && (frac < 0.26))
- tx += QChar(0xBC);
- if ((frac > 0.49) && (frac < 0.51))
- tx += QChar(0xBD);
- if ((frac > 0.74) && (frac < 0.76))
- tx += QChar(0xBE);
- p.drawText(qRound(xl + 2), textline, tx);
- break;
- }
- case SC_P:
- p.drawText(qRound(xl + 2), textline, QString::number(xl / m_iter));
- break;
- case SC_CM:
- p.drawText(qRound(xl + 2), textline, QString::number(xl / m_iter / 10));
- break;
- default:
- p.drawText(qRound(xl + 2), textline, QString::number(xl / m_iter * 10));
- break;
+ case SC_IN:
+ {
+ QString tx;
+ int num1 = static_cast<int>(dispXl / m_iter2);
+ if (num1 != 0)
+ tx = QString::number(num1);
+ double frac = (dispXl / m_iter2) - num1;
+ if ((frac > 0.24) && (frac < 0.26))
+ tx += QChar(0xBC);
+ if ((frac > 0.49) && (frac < 0.51))
+ tx += QChar(0xBD);
+ if ((frac > 0.74) && (frac < 0.76))
+ tx += QChar(0xBE);
+ p.drawText(qRound(xl + 2), textline, tx);
+ break;
}
+ case SC_P:
+ p.drawText(qRound(xl + 2), textline, QString::number(dispXl / m_iter));
+ break;
+ case SC_CM:
+ p.drawText(qRound(xl + 2), textline, QString::number(dispXl / m_iter / 10));
+ break;
+ default:
+ p.drawText(qRound(xl + 2), textline, QString::number(dispXl / m_iter * 10));
+ break;
+ }
}
for (int i = 0; i < tabValues.count(); i++)
@@ -163,44 +194,63 @@
p.setPen(QPen(selectedColor, 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
else
p.setPen(QPen(textColor, 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
+ // In RTL, tab positions are stored as distance from logical start (right edge),
+ // so screen position = width() - tabPosition.
+ double tabScreenPos = m_rtl ? (width() - tabValues[i].tabPosition) : tabValues[i].tabPosition;
+ int ts = qRound(tabScreenPos);
+ int foot = m_rtl ? -1 : 1; // direction multiplier for glyph feet
+ // Vertical stem is common to all tab types
+ p.drawLine(ts, tabline, ts, bottomline - 1);
switch (tabValues.at(i).tabType)
{
- case ParagraphStyle::LeftTab:
- p.drawLine(qRound(tabValues[i].tabPosition), tabline, qRound(tabValues[i].tabPosition), bottomline - 1);
- p.drawLine(qRound(tabValues[i].tabPosition), bottomline - 1, qRound(tabValues[i].tabPosition + 8), bottomline - 1);
- break;
- case ParagraphStyle::RightTab:
- p.drawLine(qRound(tabValues[i].tabPosition), tabline, qRound(tabValues[i].tabPosition), bottomline - 1);
- p.drawLine(qRound(tabValues[i].tabPosition), bottomline - 1, qRound(tabValues[i].tabPosition - 8), bottomline - 1);
- break;
- case ParagraphStyle::CommaTab:
- case ParagraphStyle::PeriodTab:
- p.drawLine(qRound(tabValues[i].tabPosition), tabline, qRound(tabValues[i].tabPosition), bottomline - 1);
- p.drawLine(qRound(tabValues[i].tabPosition - 4), bottomline - 1, qRound(tabValues[i].tabPosition + 4), bottomline - 1);
- p.drawLine(qRound(tabValues[i].tabPosition + 3), bottomline - 3, qRound(tabValues[i].tabPosition + 2), bottomline - 3);
- break;
- case ParagraphStyle::CenterTab:
- p.drawLine(qRound(tabValues[i].tabPosition), tabline, qRound(tabValues[i].tabPosition), bottomline - 1);
- p.drawLine(qRound(tabValues[i].tabPosition - 4), bottomline - 1, qRound(tabValues[i].tabPosition + 4), bottomline - 1);
- break;
- default:
- break;
+ case ParagraphStyle::LeftTab:
+ p.drawLine(ts, bottomline - 1, ts + foot * 8, bottomline - 1);
+ break;
+ case ParagraphStyle::RightTab:
+ p.drawLine(ts, bottomline - 1, ts - foot * 8, bottomline - 1);
+ break;
+ case ParagraphStyle::CommaTab:
+ case ParagraphStyle::PeriodTab:
+ p.drawLine(ts - 4, bottomline - 1, ts + 4, bottomline - 1);
+ p.drawLine(ts + foot * 3, bottomline - 3, ts + foot * 2, bottomline - 3);
+ break;
+ case ParagraphStyle::CenterTab:
+ p.drawLine(ts - 4, bottomline - 1, ts + 4, bottomline - 1);
+ break;
+ default:
+ break;
}
}
if (haveInd)
{
- // First Indent
p.setPen(QPen(textColor, 1, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
- p.drawLine(qRound(firstLine + leftIndent), tabline - 2, qRound(firstLine + leftIndent), bottomline);
- p.setPen(Qt::NoPen);
- p.setRenderHints(QPainter::Antialiasing, true);
- p.drawRect(QRect(qRound(firstLine + leftIndent), tabline - 8, 8, 6));
+ if (m_rtl)
+ {
+ // RTL: logical start is right edge; indent/firstLine measured from right
+ double indScreenPos = width() - leftIndent - firstLine;
+ p.drawLine(qRound(indScreenPos), tabline - 2, qRound(indScreenPos), bottomline);
+ p.setPen(Qt::NoPen);
+ p.setRenderHints(QPainter::Antialiasing, true);
+ p.drawRect(QRect(qRound(indScreenPos) - 8, tabline - 8, 8, 6));
- // Left Margin
- QPolygon cr2;
- cr2.setPoints(3, qRound(leftIndent), tabline, qRound(leftIndent + 8), tabline, qRound(leftIndent), bottomline);
- p.drawPolygon(cr2);
+ double leftScreenPos = width() - leftIndent;
+ QPolygon cr2;
+ cr2.setPoints(3, qRound(leftScreenPos), tabline, qRound(leftScreenPos - 8), tabline, qRound(leftScreenPos), bottomline);
+ p.drawPolygon(cr2);
+ }
+ else
+ {
+ // LTR: original
+ p.drawLine(qRound(firstLine + leftIndent), tabline - 2, qRound(firstLine + leftIndent), bottomline);
+ p.setPen(Qt::NoPen);
+ p.setRenderHints(QPainter::Antialiasing, true);
+ p.drawRect(QRect(qRound(firstLine + leftIndent), tabline - 8, 8, 6));
+
+ QPolygon cr2;
+ cr2.setPoints(3, qRound(leftIndent), tabline, qRound(leftIndent + 8), tabline, qRound(leftIndent), bottomline);
+ p.drawPolygon(cr2);
+ }
}
p.end();
}
@@ -231,7 +281,8 @@
}
for (qsizetype i = 0; i < tabValues.count(); ++i)
{
- fpo = QRect(static_cast<int>(tabValues[i].tabPosition - offset) - 3, 15, 8, 8);
+ int tabScreenX = tabToScreen(tabValues[i].tabPosition);
+ fpo = QRect(tabScreenX - 3, 15, 8, 8);
if (fpo.contains(m->pos()))
{
rulerCode = 3;
@@ -248,7 +299,9 @@
if ((rulerCode == 0) && (m->button() == Qt::LeftButton))
{
ParagraphStyle::TabRecord tb;
- tb.tabPosition = static_cast<double>(mPosX + offset);
+ // In RTL, logical position = rtlAnchor - screenX; in LTR = screenX + offset
+ tb.tabPosition = m_rtl ? static_cast<double>(width() - mPosX - offset)
+ : static_cast<double>(mPosX + offset);
tabValues.prepend(tb);
actTab = 0;
rulerCode = 3;
@@ -306,10 +359,13 @@
if (mousePressed && (mPosY < height()) && (mPosY > 0) && (mPosX > 0) && (mPosX < width()))
{
QApplication::changeOverrideCursor(QCursor(Qt::SizeHorCursor));
+ // In RTL, dragging right moves away from logical start (right edge), so negate delta
+ int dx = mouseX - mPosX;
+ int logicalDx = m_rtl ? -dx : dx;
switch (rulerCode)
{
case 1:
- firstLine -= mouseX - mPosX;
+ firstLine -= logicalDx;
if (firstLine + leftIndent + offset < offset)
firstLine += mouseX - mPosX;
if (firstLine + leftIndent > m_rulerWidth)
@@ -319,7 +375,7 @@
break;
case 2:
oldInd = leftIndent+firstLine;
- leftIndent -= mouseX - mPosX;
+ leftIndent -= logicalDx;
if (leftIndent < 0)
leftIndent = 0;
if (leftIndent > m_rulerWidth - 1)
@@ -330,7 +386,7 @@
update();
break;
case 3:
- tabValues[actTab].tabPosition -= mouseX - mPosX;
+ tabValues[actTab].tabPosition -= logicalDx;
if (tabValues[actTab].tabPosition < 0)
tabValues[actTab].tabPosition = 0;
if (tabValues[actTab].tabPosition > m_rulerWidth - 1)
@@ -347,7 +403,7 @@
}
if ((!mousePressed) && (mPosY < height()) && (mPosY > 0) && (mPosX > 0) && (mPosX < width()))
{
- setCursor(IconManager::instance().loadCursor("cursor-tabulator", 3));
+ setCursor(tabulatorCursor());
if (haveInd)
{
fpo = QRect(static_cast<int>(firstLine + leftIndent - offset) - 4, 0, 8, midline);
@@ -365,7 +421,8 @@
}
for (qsizetype i = 0; i < tabValues.count(); ++i)
{
- fpo = QRect(static_cast<int>(tabValues[i].tabPosition-offset) - 3, tabline, 8, 8);
+ int tabScreenX = tabToScreen(tabValues[i].tabPosition);
+ fpo = QRect(tabScreenX - 3, tabline, 8, 8);
if (fpo.contains(m->pos()))
{
setCursor(QCursor(Qt::SizeHorCursor));
@@ -761,6 +818,50 @@
tabData->setSuffix(unitSuffix);
}
+void Tabruler::setRtl(bool rtl)
+{
+ ruler->setRtl(rtl);
+ // Swap LeftTab/RightTab icons so the combo matches the mirrored ruler glyphs
+ IconManager& im = IconManager::instance();
+ int oldIndex = typeCombo->currentIndex();
+ bool blocked = typeCombo->blockSignals(true);
+ typeCombo->clear();
+ if (rtl)
+ {
+ typeCombo->addItem(im.loadIcon("tabulator-right"), tr("Right"), ParagraphStyle::LeftTab);
+ typeCombo->addItem(im.loadIcon("tabulator-center"), tr("Center"), ParagraphStyle::CenterTab);
+ typeCombo->addItem(im.loadIcon("tabulator-comma"), tr("Comma"), ParagraphStyle::CommaTab);
+ typeCombo->addItem(im.loadIcon("tabulator-dot"), tr("Period"), ParagraphStyle::PeriodTab);
+ typeCombo->addItem(im.loadIcon("tabulator-left"), tr("Left"), ParagraphStyle::RightTab);
+ }
+ else
+ {
+ typeCombo->addItem(im.loadIcon("tabulator-left"), tr("Left"), ParagraphStyle::LeftTab);
+ typeCombo->addItem(im.loadIcon("tabulator-center"), tr("Center"), ParagraphStyle::CenterTab);
+ typeCombo->addItem(im.loadIcon("tabulator-comma"), tr("Comma"), ParagraphStyle::CommaTab);
+ typeCombo->addItem(im.loadIcon("tabulator-dot"), tr("Period"), ParagraphStyle::PeriodTab);
+ typeCombo->addItem(im.loadIcon("tabulator-right"), tr("Right"), ParagraphStyle::RightTab);
+ }
+ typeCombo->setCurrentIndex(oldIndex);
+ typeCombo->blockSignals(blocked);
+
+ if (m_haveFirst)
+ {
+ if (rtl)
+ {
+ leftIndentData->setToolTip(tr("Indentation from the right for the whole paragraph"));
+ rightIndentData->setToolTip(tr("Indentation from the left for the whole paragraph"));
+ }
+ else
+ {
+ leftIndentData->setToolTip(tr("Indentation from the left for the whole paragraph"));
+ rightIndentData->setToolTip(tr("Indentation from the right for the whole paragraph"));
+ }
+ leftIndentLabel->setToolTip(leftIndentData->toolTip());
+ rightIndentLabel->setToolTip(rightIndentData->toolTip());
+ }
+}
+
void Tabruler::setTabs(const QList<ParagraphStyle::TabRecord>& tabs, int unit)
{
m_docUnitRatio = unitGetRatioFromIndex(unit);
Index: scribus/ui/tabruler.h
===================================================================
--- scribus/ui/tabruler.h (revision 27622)
+++ scribus/ui/tabruler.h (working copy)
@@ -40,6 +40,7 @@
bool mousePressed { false };
QList<ParagraphStyle::TabRecord> tabValues;
bool haveInd { false };
+ bool m_rtl { false };
int unitIndex { 0 };
int offset { 0 };
int actTab { -1 };
@@ -59,6 +60,7 @@
void removeActTab();
void moveFirstLine(double t);
void moveLeftIndent(double t);
+ void setRtl(bool rtl) { m_rtl = rtl; update(); }
signals:
void tabMoved(double);
@@ -80,6 +82,8 @@
void leaveEvent(QEvent*) override;
private:
+ int tabToScreen(double tabPos) const;
+ QCursor tabulatorCursor() const;
double m_iter { 10.0 }; // Result of unitRulerGetIter1FromIndex() for point unit
double m_iter2 { 100.0 }; // Result of unitRulerGetIter2FromIndex() for point unit
double m_rulerWidth { 0.0 };
@@ -99,6 +103,8 @@
virtual void setTabs(const QList<ParagraphStyle::TabRecord>& tabs, int unit);
+ void setRtl(bool rtl);
+
QList<ParagraphStyle::TabRecord> getTabVals() const;
double getFirstLine() const;
double getLeftIndent() const;
|
|
|
Yes, now the indents work correctly also for RTL text! Thanks! |
| Date Modified | Username | Field | Change |
|---|---|---|---|
| 2020-07-29 20:20 | ale | New Issue | |
| 2020-07-29 20:23 | ale | Note Added: 0047889 | |
| 2020-07-30 11:42 | ale | Note Edited: 0047889 | |
| 2020-07-30 13:46 | ale | Summary | RTL in the text ruler: tabs an margins => RTL in the text ruler: tabs and margins |
| 2020-07-30 14:20 | ale | Note Edited: 0047889 | |
| 2020-07-30 15:41 | ale | Note Edited: 0047889 | |
| 2020-07-31 06:00 | ale | Note Edited: 0047889 | |
| 2020-08-06 12:19 | ale | Note Edited: 0047889 | |
| 2020-08-06 12:33 | ale | Note Edited: 0047889 | |
| 2020-08-06 17:25 | ale | Note Edited: 0047889 | |
| 2020-08-19 06:12 | ale | Note Edited: 0047889 | |
| 2025-04-27 19:16 | cbradney | Category | Story Editor / Text Frames => Text Frames / Story Editor |
| 2026-05-14 10:35 | Fahad | Note Added: 0053693 | |
| 2026-06-04 08:23 | Fahad | Note Added: 0053759 | |
| 2026-06-04 08:23 | Fahad | File Added: RTL ruler.patch | |
| 2026-06-04 08:23 | Fahad | File Added: RTL_RULER_IMPLEMENTATION.md | |
| 2026-06-04 08:23 | Fahad | File Added: rtl rulers.png | |
| 2026-06-04 20:39 | ale | Note Added: 0053763 | |
| 2026-06-05 14:27 | Fahad | Note Added: 0053766 | |
| 2026-06-05 14:27 | Fahad | File Added: RTL ruler01.patch | |
| 2026-06-05 14:27 | Fahad | File Added: tab ruler -rtl.png | |
| 2026-06-06 06:51 | ale | Note Added: 0053770 | |
| 2026-06-06 06:51 | ale | File Added: RTL-hanging-indent.png | |
| 2026-06-06 06:58 | ale | Note Added: 0053771 | |
| 2026-06-06 06:58 | ale | File Added: rtl-left-margin.gif | |
| 2026-06-06 07:47 | ale | Note Edited: 0053770 | |
| 2026-06-06 10:57 | Fahad | Note Added: 0053775 | |
| 2026-06-06 10:57 | Fahad | File Added: rtl-intends.png | |
| 2026-06-06 10:57 | Fahad | File Added: RTL ruler02.patch | |
| 2026-06-06 11:03 | Fahad | Note Edited: 0053775 | |
| 2026-06-06 20:35 | ale | Note Added: 0053784 |