I have several dockable widgets and the user can move it, to any place in the screen.
Also sometimes if the user drags out of the viewable area, there is no way to bring it back.
Is there any way to restore the widget position in Qt, to default position?
Store the Widget positions in Registry. Have a default setting while installation and save the screen geometry. Eg:
QSettings settings;
settings.beginGroup("MainWindow");
settings.setValue("MRU", m_RecentFiles);
settings.setValue("maximized", isMaximized());
settings.setValue("minimized", isMinimized());
if(!isMaximized())
{
settings.setValue("size", size());
}
QByteArray array = saveState();
settings.setValue("state", array);
QDesktopWidget desktopWidget;
int nb = desktopWidget.numScreens();
settings.setValue("screenNumber", nb);
for(int i = 0; i < nb; ++i)
{
YString screenName = "screen" + YString::number(i);
QRect rect = desktopWidget.screenGeometry(i);
settings.setValue(screenName.data(), rect);
}
You can use the QMainWindow::saveState() and QMainWindow::restoreState() methods.
saveState returns a QByteArray containing the internal state of the main window, including the state and positions of the dock area's dock widgets. You can, for example, save this array in a file and restore the contents later by calling restoreState().
If you want to have a default position, then position the dock widgets as you like to have it by default, retrieve the byte array (e.g. through some action which is only available in debug mode of your application), put the array hard coded into your source code or into some default configuration file, and then implement some kind of "reset" or "set defaults" action by simply passing this array to the restoreState() method.
Generally you just add a check into the mouseMoveEvent that tests the QWidgets location and then decide if we need to move the widget back on screen.
Related
How do I recognize a double click on a tab in order to change its label?
Preferrably I could edit the label in place but alternatively I could also get a string from another input box. Any suggestions?
The tab gets added and the label specified currently like:
QString tab_label = QString("Shell (") + QString::number(session->id(), 16) + ")";
addTab(session->widget(), tab_label);
and I'd want to be able to edit the label after creation.
Oh and I should mention here that I'm a Qt newbie, too!
EDIT1
full method:
int SessionStack::addSession(Session::SessionType type)
{
Session* session = new Session(type, this);
connect(session, SIGNAL(titleChanged(int,QString)), this, SIGNAL(titleChanged(int,QString)));
connect(session, SIGNAL(terminalManuallyActivated(Terminal*)), this, SLOT(handleManualTerminalActivation(Terminal*)));
connect(session, SIGNAL(activityDetected(Terminal*)), m_window, SLOT(handleTerminalActivity(Terminal*)));
connect(session, SIGNAL(silenceDetected(Terminal*)), m_window, SLOT(handleTerminalSilence(Terminal*)));
connect(session, SIGNAL(destroyed(int)), this, SLOT(cleanup(int)));
m_sessions.insert(session->id(), session);
QString tab_label = QString("Shell (") + QString::number(session->id(), 16) + ")";
addTab(session->widget(), tab_label);
emit sessionAdded(session->id());
raiseSession(session->id());
return session->id();
}
There's a QTabBar::tabBarDoubleClicked signal, you just need to connect it to a slot to detect a double click. Also you'll need some widget to actually edit the tab's text. If you want it "out of place" (say, you open a dialog) then it should be enough to do something like:
connect(tabWidget->tabBar(), &QTabBar::tabBarDoubleClicked,
this, MyWidget::editTabBarLabel);
void MyWidget::editTabBarLabel(int tabIndex)
{
if (tabIndex < 0)
return;
// open dialog asking for the new label,
// f.i. via QInputDialog::getText
// set the new label bakc
}
If instead you want some in-place modification you'll need to more or less heavily modify QTabBar to do so.
The simplest option would be opening a QLineEdit on the right tab. To get the geometry of a tab via QTabBar:.tabrect, so that you can place the line edit in the same geometry. You'll very likely fall short on that path (see below) and you'll need to subclass QTabBar and use initStyleOption for the given tab, then set the lineedit's geometry to the right subrect (for instance, do not cover the "side widgets" of a tab).
Random pseudo braindumped code:
void MyTabBar::editTabBarLabel(int tabIndex)
{
if (tabIndex < 0)
return;
if (!m_lineEdit) {
m_lineEdit = new QLineEdit(this);
// once done, commit the change, or abort it, anyhow
// destroy/hide (?) the line edit
connect(m_lineEdit, &QLineEdit::editingFinished,
this, &MyTabBar::renameLabel);
} else {
// we're actually editing something else, what to do?
// commit the other change and start editing here?
}
m_editedTabIndex = tabIndex; // remember which one we're editing
m_lineEdit->setText(tabText(tabIndex));
// not "entirely" accurate, you need better subrect control here,
// cf. QStyle and https://doc.qt.io/qt-5/style-reference.html#widget-walkthrough
// that's why this should really be a QTabBar subclass, because
// you'll need to invoke initStyleOption and then fetch the subrects from the style
m_lineEdit->setGeometry(tabRect(tabIndex));
m_lineEdit->show();
}
// also track resize of the tabbar, relayout, tab reorder, tab insertion and removal, etc.
// move the QLineEdit accordingly
I have a Qt Desktop aplication which has several top-level widgets. Subwidgets of top-level widgets can be moved between top-level widgets by using drag-and-drop mechanism.
The problem i have now is to drop a sub-widget outside any of existing top-level widgets and create a new top-level widget to contain this one. Lets call this separation.
Can this be done using drag-and-drop? I could not find a way where my dropEvent goes?
Can i want to handle the drop event in my application even if the drop place is not allowed? Maybe a mouse release or something?
I cannot change everything now but also a question for the future. Is docking/undocking a better way to do this?
Regards
Mihai
I found a way to do this. When drag moves outside of the application widgets QDrag object emits a targetChanged signal with 0 parameter.
So i inherited from QDrag and then emit a custom signal in destructor if the target() is null.
The only problem is that the cursor looks like interdiction of drop and this i could not fix because QDrag can only set cursor pixmap for valid actions like Move or Copy or Link
Update:
Here is the inherited class.
class TabDrag: public QDrag
{
Q_OBJECT
public:
explicit TabDrag(QWidget *dragSource);
~TabDrag();
signals:
void tearOff(); /// emit tearOff signal if the QDrag object is destroyed and target was null
};
TabDrag::TabDrag(QWidget *dragSource):QDrag(dragSource)
{
}
TabDrag::~TabDrag()
{
// check if we need to detach this tab
if(!target())
{
emit tearOff();
}
}
The tearOff signal should be connected to whatever you want to happen. In my case i pull out the widget from the tab and change parent to a new window.
Example of usage
void MyTabBar::mouseMoveEvent(QMouseEvent* event)
{
..................
TabDrag * drag = new TabDrag(this);
drag->setMimeData(mimeData);
drag->setPixmap(*m_tabPixmap.data());
drag->setHotSpot(QPoint(m_dragStartPos.x() - tabAtRect.x(), m_dragStartPos.y() - tabAtRect.y()));
drag->exec();
connect(drag, SIGNAL(tearOff()), this, SLOT(onTearOff()));
}
I'm writing a program using Qt 4.8 that displays a table (QTableWidget) filled with filenames and file's params. First an user adds files to the list and then clicks process. The code itself updates the contents of the table with simple progress description. I want the table by default to be scrolled automatically to show the last processed file and that code is ready.
If I want to scroll it by hand the widget is being scrolled automatically as soon as something changes moving the viewport to the last element. I want to be able to override the automated scroll if I detect that it was the user who wanted to change view.
This behavior can be seen in many terminal emulator programs. When there's a new line added the view is scrolled but when user forces the terminal to see some previous lines the terminal does not try to scroll down.
How could I do that?
Solution:
I created an object which filters event processed by my QTableWidget and QScrollBar embedded inside. If I spot the event that should turn off automatic scrolling I just set a flag and stop scrolling view if that flag is set.
Everything is implemented inside tableController class. Here are parts of three crucial methods.
bool tableController::eventFilter(QObject* object, QEvent* event)
{
switch (event->type())
{
case QEvent::KeyPress:
case QEvent::KeyRelease:
case QEvent::Wheel:
case QEvent::MouseButtonDblClick:
case QEvent::MouseButtonPress:
case QEvent::MouseButtonRelease:
_autoScrollEnabled = false;
default:
break;
}
return QObject::eventFilter(object, event);
}
void tableController::changeFile(int idx)
{
[...]
if (_autoScrollEnabled)
{
QTableWidgetItem* s = _table.item(_engine.getLastProcessed(), 1);
_table.scrollToItem(s);
}
[...]
}
void tableController::tableController()
{
[...]
_autoScrollEnabled = true;
_table.installEventFilter(this);
_table.verticalTableScrollbar()->installEventFilter(this);
[...]
}
Thanks for all the help. I hope somebody will find it useful :)
Subclass QTableWidget and overload its wheelEvent. You can use the parameters of the supplied QWheelEvent object in order to determine if the user scrolled up or down.
Then use a simple boolean flag which is set (or reset) in your wheelEvent override. The method which is responsible for calling scrollToBottom() should then consider this boolean flag.
You will have to find a way to figure out when to set or reset that flag, e.g. always set it when the user scrolls up and reset it when the user scrolls down and the currently displayed area is at the bottom.
connect(_table->view()->verticalScrollBar(), &QAbstractSlider::actionTriggered, this, [this](int) {
_autoScrollEnabled = false;
});
I am trying to make a GUI so when you increase the "Article" count, then more of the article inputs show up. For example, if I change the Articles count to 2, I would want another group of inputs to show up for Article 2, and if the Articles count changes to three, there would be three groups of inputs, but since that would use up more space than the window has, it would begin to scroll.
I was thinking of using one of the tree, list, or table widgets, but I am not sure if that is even the right direction I am supposed to be going to. Can anyone push me in the right direction?
Here is a picture, since my description is not good.
You should put all the widgets needed for one article into one single custom widget. Whenever the spin box is changed (code in slot) you can add / remove one instance of such a custom widget to a scroll area.
Within the constructor of this custom widget class (let's call it ArticleWidget), you should define signals in your custom widget which notify about changes made in its child widgets. Connect these within your custom widget:
ArticleWidget::ArticleWidget(QWidget *parent) :
QWidget(parent)
{
ui->setupUi(this); // when you use QtDesigner to design the widget
// propagate signals from my inner widgets to myself:
connect(ui->title, SIGNAL(textChanged(QString)),
SIGNAL(titleChanged(QString)));
}
In the outer widget, whenever creating such a custom widget, connect its signals to your processing slots:
void OuterWidget::articleCountChanged(int)
{
...
if(/*increased*/)
{
ArticleWidget *article = new ArticleWidget(this);
connect(article, SIGNAL(titleChanged(QString)),
SLOT(art_titleChanged(QString)));
ui->scrollAreaViewport->layout()->addWidget(article);
}
...
}
You can access the article widget using sender():
void OuterWidget::art_titleChanged(QString)
{
ArticleWidget *articleWidget = qobject_cast<ArticleWidget*>(sender());
Q_ASSERT(articleWidget); // make sure the signal comes from an ArticleWidget
// if you want to store articles in a vector of custom types,
// you could give this type a pointer to the widget, so you can
// find the index if you have the widget pointer:
foreach(Article *article, articles)
if(article->widget == articleWidget)
article->title = title; // make some changes
}
This code assumes that you hold all your articles in a struct similar to this:
struct ArticleData
{
ArticleWidget *widget;
QString title;
...
};
and have a vector of them in your outer widget class:
QVector<ArticleData*> articles;
I am trying to develop an image gallery application using Qt Framework. The application loads all the images from the selected folder and those images are displayed using QListView control.
But now i want to reduce the memory consumption by loading only the images that are visible to user. Since there is no direct function to get all the visible items in the view, i am not able to achieve this.
You can get the visible items of a list view using the indexAt function. For more details and an example you can check the following article:
http://qt-project.org/faq/answer/how_can_i_get_hold_of_all_of_the_visible_items_in_my_qlistview
I found it! You have to connect the vertical scrollbar of the listwidget to a signal:
connect(ui->listWidget->verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(launch_timer()));
Every time the user scrolls, the valuechanged(int) signal is being omitted! The thing is that you shouldn't run the code provided by webclectic in this question every time the value of the vertical scrollbar of the listwidget changes, because the program will be unresponsive with so much code to run in so little time.
So, you have to have a singleshot timer and point it to the function that webclectic posted above. When launch_timer() is called, you do something like this:
if(timer->isActive()){
timer->stop();
timer->start(300);
}
else
timer->start(300);
and the timeout() signal of timer will be connected to the slot webclectic talked about. This way, if the user scrolls quickly all the way down only the last items will be updated. Generally, it will be updated anything visible for more than 300 milliseconds!
I think what you need is to implement your own model (take a look to the QAbstractListModel documentation) so that way you could decide when you have to load more images to show and maybe free some of the images that became non-visible.
although this is not so simple in Qt 4 but
it is always simple to copy below:
#include <private/qlistview_p.h>
class QListViewHelper : public QListView
{
typedef QListView super;
inline QListViewHelper() {} //not intended to be constructed
public:
inline static QVector<QModelIndex> indexFromRect(const QListView *view,
const QRect &rect)
{
const QListViewPrivate *d = static_cast<const QListViewPrivate *>(QObjectPrivate::get(view)); //to access "QListViewPrivate::intersectingSet(...)"
const QListViewHelper *helper = static_cast<const QListViewHelper *>(view); //to access "QListView::horizontalOffset()"
return d->intersectingSet(rect.translated(helper->horizontalOffset(), helper->verticalOffset()), false);
}
inline static QVector<QModelIndex> visibleItems(const QListView *view)
{ return indexFromRect(view, view->rect()); }
inline static QModelIndex firstVisible(const QListView *view)
{ return visibleItems(view).value(0); }
inline static QModelIndex lastVisible(const QListView *view) {
const QVector<QModelIndex> &items = visibleItems(view);
return items.value(items.count() - 1);
}
};
void ourTest(const QListView *view) {
QModelIndex &index = QListViewHelper::firstVisible(view);
qDebug("QListViewHelper: first visible row is %d", index.row());
index = QListViewHelper::lastVisible(view);
qDebug("QListViewHelper: last visible row is %d", index.row());
}
usage:
QModelIndex &index =
QListViewHelper::firstVisible(listViewPointerHere)
note: since it does use Qt 4.8 private-headers it may no longer work in latter versions and will need some changes.
You can keep track of all the elements that are drawn per paint event. I used a delegate and overloaded the paint event.
I also overloaded the paint event in the view. During this call, all the visible delegates will get a paint event.
If you just need to know if an item is visible, you can increment a frame count in view->paintEvent and set that number in the delegate item. The item is visible of the item matches the current frame number.
If you need a list of all visible items, clear the visible item list in view->paintEvent and add each item in the int the delegate->paintEvent to the visible items list.