QtWebView - How to enable scrolling of page and scrolling of elements in a page (e.g. Google Maps) - c++

I've run into a bit of an issue related to a whitelist Web Browser my company has been developing / maintaining for one of our product lines. The browser runs on top of Qt 4.8.6, using qtwebkit (Migration to 5.X would be ideal, but the embedded Linux OS we're using is too old to support the newer versions based on our testing, and upgrading to a newer OS is too costly to us / our customers). The primary interface to the browser is a 6x8 touchscreen, mounted inside an aircraft cockpit.
For sites that have things like scrollable/embedded maps (ex. Google Maps), the users of the browser want the ability to drag the entire page when they are selecting something outside of the map, and drag just the map (without the entire page scrolling) when the map is selected (Ala most of the popular mobile browsers).
Thus far, I am able to do one or the other, but not both:
When I hook mouse handlers into a QWebView or QGraphicsWebView, I can turn the cursor into a hand and very easily support dragging of the entire web page. However, that inhibits the page's ability to handle the mouse events for when a user is pulling over a map (i.e. When a user drags over a map, it drags the entire page without moving the map).
When I don't add in the hooks to handle mouse events, things like maps are scrollable by grapping/dragging them, but of course the user loses the ability to drag the entire page.
Right now, the browser uses the later, with scroll bars disabled and a directional-arrow overlay to allow the user to scroll the entire page (as the display size is limited, and scrollbars take up too much space when they are sized large enough for the user to interact with them)...but this is not ideal.
My Question: Is there any easy way to make it so that the page, and elements in a page, can be scrolled seamlessly?
Thanks!
Rob

Seems to me like you need to check if you are over such a map and ignore(pass along) the event in that case. I think you should be able to do something like this:
bool GraphicsWebView::isOverMap(QPoint pos) {
QWebPage* webPage = this->page();
if (webPage) {
QWebFrame* webFrame = webPage->frameAt(pos);
if (webFrame) {
QString selectorQuery = "#map-canvas"; // Based on https://developers.google.com/maps/tutorials/fundamentals/adding-a-google-map
QList<QWebElement> list = webFrame->findAllElements(selectorQuery).toList(); // Find all the maps!
foreach(QWebElement element, list) {
if (element.geometry().contains(pos)) {
return true; // Cursor is over a map
}
}
}
}
return false; // No match
}
Obviously this is a pretty specific function but there is probably a way to come up with a better selector query that will apply to all those kinds of QWebElement.
Assuming you hook mouse events by subclassing QGraphicsWebView and reimplementing void mouseMoveEvent(QGraphicsSceneMouseEvent * event), I suggest you do something like:
void GraphicsWebView::mouseMoveEvent(QGraphicsSceneMouseEvent* event) {
if (isOverMap(mapFromScene(event->scenePos()).toPoint())) { // We got a map!
event.ignore(); // Clear the accept flag
return; // Return, we're done here
}
handleMoveView(); // Not over any maps, let's scroll the page
}
This part of the doc explains how events are handled with regard to the topmost item. I especially recommend you read the third paragraph.
Hope that helps!
EDIT: Did a bit more research and it looks like something like that could be more generic:
graphicsView.focusItem()->flags().testFlag(QGraphicsItem::ItemIsMovable);
It's at the very least worth investigating as a replacement to isOverMap()
EDIT: Gotcha, here is something you can try then.
Start by subclassing QGraphicsSceneMouseEvent and add a signal called void destroyedWithoutAccept() that's emitted in the destructor if the event has not been accepted.
Then modify mouseMoveEvent to look like this:
void GraphicsWebView::mouseMoveEvent(QGraphicsSceneMouseEvent* event) {
MyEvent myEvent = new MyEvent(event); // Copy event
event.accept(); // accept original event
connect(myEvent, SIGNAL(destroyedWithoutAccept),
this, SLOT(handleMoveView)); // Callback if unused
QGraphicsWebView::mouseMoveEvent(myEvent); // Pass it to Base class
}
If that works, it might introduce a bit of delay if deleteLater is used to destroy it. But in that case reimplement it as well.

Related

Call button click function from grandchild

I'm creating my first C++ wxWidgets application. I'm trying to create some kind of split button where the options are displayed in a grid. I have a custom button class which, when right-clicked on, opens a custom wxPopupTransientWindow that contains other buttons.
When I click on the buttons in the popup, I want to simulate a left click on the main button. I'm trying to achieve this through events, but I'm kinda confused.
void expandButton::mouseReleased(wxMouseEvent& evt)
{
if (pressed) {
pressed = false;
paintNow();
wxWindow* mBtn = this->GetGrandParent();
mBtn->SetLabel(this->GetLabel());
mBtn->Refresh();
wxCommandEvent event(wxEVT_BUTTON);
event.SetId(GetId());
event.SetEventObject(mBtn);
mBtn-> //make it process the event somehow?
wxPopupTransientWindow* popup = wxDynamicCast(this->GetParent(), wxPopupTransientWindow);
popup->Dismiss();
}
}
What is the best way to do this?
You should do mBtn->ProcessWindowEvent() which is a shorter synonym for mBtn->GetEventHandler()->ProcessEvent() already mentioned in the comments.
Note that, generally speaking, you're not supposed to create wxEVT_BUTTON events from your own code. In this particular case and with current (and all past) version(s) of wxWidgets it will work, but a cleaner, and guaranteed to also work with the future versions, solution would be define your own custom event and generate it instead.

Performantly appending (rich) text into QTextEdit or QTextBrowser in Qt

QTextEdit can be appended text to simply using append(). However, if the document is rich text, every time you append to the document, it is apparently reparsed. This seems like a bit of a trap in Qt.
If you're using the edit box as a log window and appending text in fast successions as a result of external signals, the appending can easily hang your app with no intermediate appends shown until each of the appends have completed.
How do I append rich text to a QTextEdit without it slowing down the entire UI?
If you want each append to actually show quickly & separately (instead of waiting until they've all been appended before they are shown), you need to access the internal QTextDocument:
void fastAppend(QString message,QTextEdit *editWidget)
{
const bool atBottom = editWidget->verticalScrollBar()->value() == editWidget->verticalScrollBar()->maximum();
QTextDocument* doc = editWidget->document();
QTextCursor cursor(doc);
cursor.movePosition(QTextCursor::End);
cursor.beginEditBlock();
cursor.insertBlock();
cursor.insertHtml(message);
cursor.endEditBlock();
//scroll scrollarea to bottom if it was at bottom when we started
//(we don't want to force scrolling to bottom if user is looking at a
//higher position)
if (atBottom) {
scrollLogToBottom(editWidget);
}
}
void scrollLogToBottom(QTextEdit *editWidget)
{
QScrollBar* bar = editWidget->verticalScrollBar();
bar->setValue(bar->maximum());
}
The scrolling to bottom is optional, but in logging use it's a reasonable default for UI behaviour.
Also, if your app is doing lots of other processing at the same time, appending this at the end of fastAppend, will prioritize actually getting the message displayed asap:
//show the message in output right away by triggering event loop
QCoreApplication::processEvents();
This actually seems a kind of trap in Qt. I would know why there isn't a fastAppend method directly in QTextEdit? Or are there caveats to this solution?
(My company actually paid KDAB for this advice, but this seems so silly that I thought this should be more common knowledge.)

QTableView slow scrolling when many cells are visible at once

Background: I'm developing application using Qt 5.5.1, compiling with msvc2013. In this app I use my own implementation of QTableView, along with custom QStyledItemDelegate (needed custom cell editing) and QAbstractTableModel. I intend this view to work with massive amount of data that I wrap inside mentioned model. I allow the user few data editing options, custom sorting, 'invalid' rows windup etc.
The problem: scrolling speed of my QTableView subclass is slow - it gets slower the more table is shown (by resizing window), e.g. ~250 cells shown (in fullscreen) = slow, ~70 cells shown (small window) = fast.
Whad did I try so far:
First was to check if my model is slowing things down - I have measured times (using QTime::elapsed()) reading 10k samples and it shown 0 or 1ms. Then I have simply altered QTableView::data method to always return predefined string and not acquire any real data.
QVariant DataSet_TableModel::data(const QModelIndex &index, int role) const
{
if (role == Qt::ItemDataRole::DisplayRole) {
return QVariant("aRatherLongString"); //results in slow scrolling
//return QVariant("a"); // this instead results in fast scrolling
}
else return QVariant();
}
As you can see, the speed seems to be affected by number of characters vieved per cell, and not by underlying connections to data source.
In my custom implementation of QStyledItemDelegate I have tried same 'trick' as above - this time overriging displayText method:
QString DataSet_TableModel_StyledItemDelegate::displayText(const QVariant &value, const QLocale &locale) const
{
return "a" //fast
// return "aRatherLongString"; //slow
// return QStyledItemDelegate::displayText(value, locale); //default
}
After some thought with a friend we concluded that maybe we could disable drawing/painting/updating of cells until whole scroll action is done. It might cause some flickering, but it's worth a try. Unfortunately we dont really know how to aproach this. We have everriden QTableView methods: scrollContentsBy(int dx, int dy) and verticalScrollbarAction(int action) - we have captured scroll action properly (either method intercepts it) and tried to somehow disable repainting like this:
void DataSet_TableView::verticalScrollbarAction(int action) {
this->setUpdatesEnabled(false);
QTableView::verticalScrollbarAction(action);
this->setUpdatesEnabled(true);
}
...but it did not have any visible effect.
How should we approach it? Do we need to use setUpdatesEnabled() on items that are put inside cells directly? (not sure what those are - widgets?)
Here are screenshots taken as part of testing this problem:
Predefined text, no calls to underlying data structure - slow scrolling, 'full screen'
Predefined text, no calls to underlying data structure - fast scrolling, windowed
Request: Could you kindly help me pinpoint the cause of this and suggest solution if possible? Is it limitation of the classes that I use?
First of all, you should also run your application in release mode to check your perfomance, in my experience, the performance decreases greatly when using debug mode.
Secondly, you need to be aware that the model data method and delegates methods are called every time you resize, scroll, focus out, right click etc. These actions trigger those methods to be called for each displayed cell, therefore you would need to make sure that you don't do any unnecessary processing.
The items inside cells are delegates that call their own methods (eg: paint).
Some C++ specific optimisations would be helpful in the implementation of these methods, like using a switch instead of an if statement, see explanation here and here. The usage of Conditional (Ternary) Operators might also speed up the things, more information here, here and some information about expensive checkings here.
Also, QVariant handles text in different ways, as exemplified below, you should try both ways and check if there is any difference in speed. Some conversions are more expensive than others.
v = QVariant("hello"); // The variant now contains a QByteArray
v = QVariant(tr("hello")); // The variant now contains a QString

Infragistics grid scrolling issue

I have this code, which works fine if a cell in the IgGrid control is being edited:
var verticalContainer = $("#BookLabor_scrollContainer");
var topPos = verticalContainer.scrollTop();
$("#BookLabor").igGrid("option", "dataSource", blankLaborDS);
$('#BookLabor').igGrid('dataBind');
verticalContainer.scrollTop(topPos);
However, when I use an IgDialog that I have pop open on a grid cell with a button click event, this is not scrolling back to the row being edited:
var verticalContainer = $("#BookLabor_scrollContainer");
var topPos = verticalContainer.scrollTop();
$("#BookLabor").igGrid("option", "dataSource", blankLaborDS);
$('#BookLabor').igGrid('dataBind');
verticalContainer.scrollTop(topPos);
There is a virtual scroll method for the IgGrid, but the online documentation does not explain in detail how to use it.
Any tricks, tips, hints from all you Infragistics experts out there?
The scroll related API is very basic and what you are using is pretty much comparable:
.igGrid("scrollContainer") is merely a shorthand so you don't have to use #BookLabor_scrollContainer (it's an internal id)
.igGrid("virtualScrollTo", scrollContainerTop); is just like scroll top when you are using virtual scrolling, which you might be (can't tell without more code) so you might want to try that out.
HOWEVER, is there a reason to call dataBind after cell edit? ( I'm having a hard time finding a scenario for that). It is not intended by any means and it creates a lot of overhead with bigger data. If you need to update cell values you should be using the Updating API that does not require re-bind and will not require scroll after as well..see:
http://help.infragistics.com/jQuery/2012.2/ui.iggridupdating#methods
As for the dialog, the Updating again provides a row template that internally uses the dialog and I highly recommend that if row editing is acceptable. Sample:
http://www.infragistics.com/products/jquery/sample/grid/row-edit-template

Qt User customizable hotkeys

I'm trying to design a Qt GUI application with user customize-able hotkeys. The main issue I'm running into is how to synchronize the hotkeys across the application since a particular hotkey (for example, copy) may be used by multiple widgets/components.
My current strategy is to use a reference class which holds a list of QKeySequence objects for each different hotkey. Each widget would have to have a way to reference this master list and have custom implementations of low-level the keyPressEvent which would compare inputted keys vs. the hotkeys. I don't particularly like this strategy, though, as it requires significant re-implimentation in each widget and feels like I'm trying to re-invent the wheel.
I also tried using QAction objects which can hold QKeySequence shortcuts internally, then use these to trigger higher-level events which I can handle using slots & signals. However, the main issue I have here is how to manage which slots signals get routed to.
For example, say I have 2 open widgets which can both receive a copy action signal. I can connect a slot for both of these to the same signal and take advantage of the single update point for shortcuts, but then things get messy since only the active widget should act on the copy signal, not both widgets. I can re-implement the focusOutEvent and focusInEvent handlers to connect/disconnect slots manually, but this also seems to run into the same issue above where I'm trying to re-invent the wheel and doing more work than is necessary.
Is there an easier way around this problem?
I don't think there is a particularly easy/non-tedious solution to this problem, but when I needed to add user-customizable hotkeys to my application, here is how I did it:
1) Start with your application that has hard-coded key shortcuts, e.g. code like this:
QMenu * editMenu = new QMenu;
QAction * copyItem = menu->addAction(tr("Copy"), this, SLOT(CopyData()));
copyItem->setShortcut(tr("Ctrl+C"));
2) Create a GetKeySequence() function that looks something like this:
static QHash<QString, QKeySequence> _usersKeyPreferences;
static bool _usersKeyPreferencesLoaded = false;
QKeySequence GetKeySequence(const QString & keySequence, const QString & contextStr)
{
if (_usersKeyPreferencesLoaded == false)
{
// Oops, time to load in the user's saved custom-key settings from a file somewhere
_usersKeyPreferences = LoadUsersKeyPreferencesFromFile();
_usersKeyPreferencesLoaded = true; // so we'll only try to load the file once
}
if (_usersKeyPreferences.contains(contextStr))
{
return _usersKeyPreferences[contextStr];
}
else
{
// No user preference specified? Okay, fall back to using the
// hard-coded default key sequence instead.
return QKeySequence(qApp->translate(contextStr, keySequence));
}
}
3) Now the tedious part: grovel over all of your code, and anywhere you've specified a key-sequence explicitly (like in the third line of the code shown for step 1), wrap it with a call to GetKeySequence(), like this:
copyItem->setShortcut(GetKeySequence(tr("Ctrl+C"), tr("Edit_Menu|Copy")));
4) At this point, your program's key-sequences will be customizable; just make sure that the key-settings-file is present on disk before GUI-creation code runs. Here's an excerpt from my program's key-mappings file (which I store as a simple ASCII text file):
Edit_Menu|Copy = Ctrl+C
Edit_Menu|Cut = Ctrl+X
Edit_Menu|Paste = Ctrl+V
[... and so on for all other menu items, etc...]
... of course one downside to this approach is that once the GUI is created, the key-bindings can't be modified "on the fly" (at least, not without a lot of additional coding). My program gets around this simply by closing and then re-creating all windows after the user clicks "Save and Apply" in the Edit Key Bindings dialog.
5) An optional further step (which is some extra work up front but saves time in the long run) is to write a program (or script) that greps all the .cpp files in your program's codebase looking for calls GetKeySequence() in the code. When it finds a GetKeySequence() call, it parses out the two arguments to the call and prints them as a line in a key-bindings file with the default settings. This is useful because you can make this script part of your autobuild, and thereafter you'll never have to remember to manually update the default key-settings-file whenever you add a new menu item (or other key-sequence specifier) to your program.
This worked well for me, anyway. The advantage is that you don't have to refactor your existing program at all; you can just go through it inserting GetKeySequence() as necessary while leaving the larger logic/structure of the program intact.