Redrawing control behind composite with SWT.NO_BACKGROUND - swt

Original goal:
I have a TreeMenu that i use to display my Menu.
In this tree, a user can select different items.
I would like to disable the tree, so that a user cannot select a new item after choosing the first.
The catch is, we cannot use setEnabled, because we are not allowed to use the greyed out look. The look/colors may not change.
Our proposed solution
Our first idea was to use a Composite with SWT.NO_BACKGROUND on top of the menu, to prevent any user interaction with the TreeMenu.
Code:
final Composite cover = new Composite(getPage().shell, SWT.NO_BACKGROUND);
cover.setLocation(getMenu().getLocation());
cover.setSize(getMenu().getSize());
cover.moveAbove(getMenu());
This has a problem with redrawing.
If the screen is covered by another screen and then brought back to front, the Cover Composite is filled with fragments of the previous overlapping window.
Our idea was to manually redraw the menu:
cover.moveBelow(getMenu());
getMenu().update();
cover.moveAbove(getMenu());
We placed the code inside the paintEventListener.
But this caused an infinite loop and did not solve the problem.
Questions
Does anyone have an idea how we can achive our orignial goal?
Does anyone know how we can make our proposed solution work?

Look at SWT-Snippet80. It shows how to prevent selections. A solution to your problem would be adding a listener like this to your tree:
tree.addListener(SWT.Selection, new Listener() {
TreeItem[] oldSelection = null;
public void handleEvent( Event e ) {
Tree tree = (Tree)(e.widget);
TreeItem[] selection = tree.getSelection();
if ( oldSelection != null )
tree.setSelection(oldSelection);
else
oldSelection = selection;
}
});
I wouldn't recommend trying to implement your workaround. I believe that placing transparent controls on top of each other is unsupported in SWT - I think I read a comment from Steve Northover on this subject once. Even if you made it work for some OS, it probably won't work in another - it's too much of a hack.
A solution that is supported by SWT, is having transparent windows on top of each other. But that is also really hard to implement (resizing, moving, always on top, redraw artifacts) and probably as big a hack as the other workaround. Go for the listener.

Related

GWTQuery Drag Drop - Drag between nodes in cell tree

I am using the wonderful GWTQuery library to add drag drop support to my GWT cell widgets.
I have a CellTable, and a CellTree both in different modules of my application (I am using GWTP, so everything is decoupled). Neither of these widgets are allowed to know about each other, they simply accept draggables/droppables, check the underlying datatype and then handle them appropriately.
The problem I am having is that I need to support dragging "in between" my cell tree nodes. IE: The typical functionality where if you drag directly over an element in the tree, it drops into that element, BUT if you drag just slightly below or above, you are given a visual indicator (usually a horizontal line) that indicates to the user they can drag the current item in between nodes as well.
And here-in lies the problem, thus far I have no found a way to provide this functionality becuase the setOnDrag() method does not tell me anything about detected droppables, and setOnOver only fires once when it first encounters a droppable.
So far as I can tell this leaves me with only two options:
1.) Add extra "invisible" nodes into my CellTree which are also droppable and sit in between my other nodes.
2.) Implement some custom event handler which I attach to the helper draggable before drag start and use to compare positions of the helper and the droppable once the draggable is actually over the droppable.
Option 1 is really unsavory because it seriously mucks up my CellTree design, and potentially impacts efficiency pretty badly.
Option 2 is really unsavory because it requires a lot of extra code and hacks to get it to work just right.
So I am hoping there is an Option 3 which I might not have though of, any help would be much appreciated.
Cheers,
Casey
I think I have found a solution although it may not be the best, but it is working for me at the moment. In the setOnDrag method, I determine where the item is being dragged at which point I can either add a line before or after the element, or put some css on the element to denote that I am dropping the dragged item on top. I create a GQuery place holder to show the before/after line, and putting a border around element with css for dropping on top.
To know which element I am dropping on top of, I set a global variable in the setOnOver method. Here is a simple mock up:
private GQuery placeHolder = $("<div id='cellPlaceHolder' style=' outline: thin dashed #B5D5FF; height: 2px; background:#B5D5FF;'></div> ");
private Element oldEl = null;
options.setOnOver(new DroppableFunction() {
#Override
public void f(DragAndDropContext context) {
oldEl = context.getDroppable();
}
});
options.setOnDrag(new DragFunction() {
#Override
public void f(DragContext context) {
if (oldEl != null) {
int difference = Math.abs(oldEl.getAbsoluteTop() - context.getHelperPosition().top);
if (difference > 0 && difference < 16) {
/* dragging on top edge, so insert place holder */
oldEl.getFirstChildElement().getStyle().clearProperty("border");
placeHolder.insertBefore(oldEl.getFirstChildElement());
} else if (difference > 26 && difference < 53) {
/* dragging on bottom edge, so insert place holder */
oldEl.getFirstChildElement().getStyle().clearProperty("border");
placeHolder.insertAfter(oldEl.getFirstChildElement());
}else if (difference > 15 && difference < 27) {
/* dragging in middle so add border */
placeHolder.remove();
oldEl.getFirstChildElement().getStyle().setProperty("border", "2px solid red");
}
}
}
});
This way uses several global variables, but it seems to be the best method I have found since the drag options do not include info about the droppable element. And you will have to add the logic to know if it is being dropped before/after/or on and do what you want with it at that point.

GWT drag and drop animation

I have a flow panel with many photo-widgets inside (gallery with random number of rows and columns, depends on screen size) for which I want to implement drag and drop behavior to change their order. I am using gwt-dnd library. Its FlowPanelDropController allows you to define your own positioner (delimiter) which shows the candidate location for dropping the dragged widget.
I want this positioner to be the empty space with defined width, and the challenging thing is to implement sliding animation effect for the when positioner is added and removed.
If you are a desktop Picasa app user you know what I mean: the target row slides both sides (little to the left, little to the right) extending the space between the items where you are going to drop a photo.
The whole thing is complex enough, but any help related to how to apply the animation for positioner attach/detach is appreciated. Maybe I need to use a different approach (e.g., use GWT native dnd instead of gwt-dnd lib and no "positioners" at all) if you have any ideas how this could be helpful.
Thanks.
Well, I ended up overriding AbstractPositioningDropController (parent of FlowPanelDropController) and adding some extra features.
1) newPositioner() method now builds the Label, which is vertical space with some small width, height and decoration. This widget's element has constant id (say, "POSITIONER"), which helps to distinguish between multiple positioners if you plan to have several of them while navigating with a drag object over multiple drop targets. Also some transition CSS effects were applied to the Label, which will be responsible for handling animated extension of Label's width.
2) in onEnter() I do the following
...
removePositioner(getPositionerElement());
Widget positioner = newPositioner();
dropTarget.insert(positioner, targetIndex);
animatePositionerExtension();
where getPositionerElement() returns DOM.getElementById(POSITIONER)
At the same time removePositioner(..) resets the id of this element to something abstract and ideally should provide some animation before calling .removeFromParent(). But I didn't have enough time to properly debug this so ended up just removing the old positioner with no animation.
Method animatePositionerExtension() contains the code that changes the width of the positioner widget, so that CSS transition will catch that and provides animation.
All access to positioner widget in the class should be provided through updated methods.
3) onLeave() contains line removePositioner(getPositionerElement());
4) In the end of onMove() I added a couple of lines:
galleryWidget.extendHoveredRow(targetIndex - 1);
animatePositionerExtension();
where extendHoveredRow(hoveredWidgetOrdinal) implemented the logic to "limit" the sliding effect in the single line:
int rowHovered = -1;
public void extendHoveredRow(int hoveredWidgetOrdinal) {
int newRowHovered = getRowByOrdinalHovered(hoveredWidgetOrdinal);
if (rowHovered != newRowHovered) {
// adjust position of items in the previously hovered row
int firstInPreviouslyHoveredRow = (rowHovered - 1) * itemsInARow;
shiftFirstItemLeft(firstInPreviouslyHoveredRow, false);
rowHovered = newRowHovered;
// extend this row
int firstInThisRow = getOrdinalFirstInThisRowByOrdinal(hoveredWidgetOrdinal);
shiftFirstItemLeft(firstInThisRow, true);
}
}
This is in short how I did the thing. And still there's some room for improvements, like adding animated removal.
Again, it's all about overriding DropController and manipulations with elements inside the "gallery" widget. The benefit of this approach is that I remain in the gwt-dnd operations framework, and also reused a bunch of existent code.
Some notes:
CSS transition is not supported in IE pre-9, but this is unrelated to
this topic.
Put a transparent "glass" div on top of the Image widget if you use it
as a face of dragProxy. This will save you tons of time trying to
understand why either setting element's draggable to false, or
calling event.preventDefault() somewhere else, or other workarounds don't work in one or several browsers and the image itself is being dragged instead of the whole dragProxy widget.

QCompleter and Tab key

I'm trying to make a Completion when pressing tab, you get the first completion of all possibilities.
But, in a QWidget-based main window, pressing tab will make that QLineEdit lost focus, and completion popup hides after that.
Is there a way to fix it ?
Have you tried to subclass QLineEdit and intercept the key press event?
Alternatively you could set up an event filter.
Whew. It took me some time to figure this out :) Multiple times I have tried to solve this problem, but always gave up. Now, I dug enough to find the answer.
OP, please pardon me, because the code here is Python, but should be understandable and work for C++ as well.
Basically, the problem I had was "how to select an entry in the QCompleter"; I didn't notice before, but the answer is in the popup() method. QCompleter works with a model and a view, which contains the things to show.
You can change the current row as you wish, then get the index of that row in the model, then select it in the pop-up.
In my code, I subclassed QLineEdit, created a tabPressed signal which is emitted every time Tab is pressed. Then, connected this signal to a method of the same class which does this:
get the current index;
select the index in the popup;
advance to the next row.
As implementation, this is very trivial, but for my current purpose this is enough. Here's the skeleton (just for the tab part, it's missing the model and everything else).
class MyLineEdit(QLineEdit):
tabPressed = pyqtSignal()
def __init__(self, parent=None):
super().__init__(parent)
self._compl = QCompleter()
self.tabPressed.connect(self.next_completion)
def next_completion(self):
index = self._compl.currentIndex()
self._compl.popup().setCurrentIndex(index)
start = self._compl.currentRow()
if not self._compl.setCurrentRow(start + 1):
self._compl.setCurrentRow(0)
def event(self, event):
if event.type() == QEvent.KeyPress and event.key() == Qt.Key_Tab:
self.tabPressed.emit()
return True
return super().event(event)
You may need to adjust/fix few things, but this is the basic idea.
EDIT:
For details see
http://www.qtcentre.org/threads/23518-How-to-change-completion-rule-of-QCompleter
There's a little issue: when Return is pressed, the things don't work properly. Maybe you can find a solution to this problem in the link above, or in the referenced resources therein. I'll fix this in the next few days and update this answer.
There is probably a better solution but one that comes to mind is to change the focus policy for all other widgets on the form to something that doesn't include "tab" focus. The only options that don't use the tab key are Qt::ClickFocus and Qt::NoFocus.

Displaying forms using Tree in Qt

I'm building a Qt plugin with multiple forms. I have a main form which has a tree widget placed on the left of the form.
I want to add items to this tree, such that clicking on these items would load the corresponding form on the same form. But I want the tree widget to be active so that I can select any other form also.
I was able to display a form on the main form using the following code:
Form1 *myform;
myform=new Form1(this);
myform->show();
where Form1 is the class of the form i intend to display. However this, covers up the tree widget also. And I have to do a string comparison of the item in tree being clicked to display the appropriate form.
Can someone please help me with this as I'm very new to Qt programming.
Thanks
ixM has a good suggestion. The first step should definitely be to use layouts in your main window - separating the tree from the rest of the window - where you are going to put your form. I would suggest using a splitter, because then the user can resize the two halves. You can set the splitter as the main widget of your CentralWidget in your main window.
QSplitter splitter = new QSplitter(CentralWidget);
splitter->setOrientation(Qt::Horizontal);
splitter->setHandleWidth(3);
splitter->setChildrenCollapsible(false);
MyTree= new QTreeWidget(splitter);
splitter->addWidget(MyTree);
Then add your tree widget to the splitter, which will be on the left side.
The next step is to add a placeholder widget on the right side of your splitter. We are also going to add a layout inside that widget. This layout is very important we are going to use it later.
QWidget WidgetRightSide = new QWidget(splitter);
QVBoxLayout setupLayout= new QVBoxLayout(WidgetRightSide);
setupLayout->setSpacing(0);
setupLayout->setContentsMargins(0, 0, 0, 0);
Now, at this point, this is where my answer really differs from the previous answer. You could use a QStackedWidget. That is certainly an option. The problem with that is that you have to create and load all your forms at the beginning. That uses way more memory, and will take longer to start up. That's not so bad if you have 2-5 forms, but when we are talking about 20, 30 or more forms that's really ugly.
So what I would suggest instead, is that when the user selects something in the tree, we will remove the old form, and add the newly selected form at that point.
When the selected item in the tree changes this is now what we have to do.
First, remove all the stuff from the previously selection form.
QLayoutItem *_Item;
while ((_Item = setupLayout->takeAt(0)))
delete _Item;
Next, figure out what form to show next, and create it.
QWidget *ActiveSetupForm = NULL;
if ( I need to load form 1)
{
ActiveSetupForm = new YourNewForm( WidgetRightSide);
}
else ...
And lastly, add your new form to our layout.
if(ActiveSetupForm)
{
setupLayout->addWidget(pActiveSetupForm);
}
Just as a side note. Layouts are tricky to do by hand. I would strongly suggest that you look into using the QtDesigner when you are creating your forms. It makes life soooo much easier. If you would like to know more about it check out this link.
I don't exactly understand what you are trying to achieve but the bit of code you are showing suggests that you do not use the layouts provided by Qt.
If your goal is to be able to dynamically load a form depending on the item that was clicked in the tree, you could achieve that by having a layout (let's say QHBoxLayout) where you would insert your tree and a QStackedWidget in which you could "store" each form (by using addWidget()) and choose which one you want to display by calling setCurrentIndex().

GWT 2.4 DataGrid automatic scrolling when selecting an item

I am using GWT 2.4's new DataGrid in a project. I configured the DataGrid with a pagesize of 50.
The available screen is not big enough to display all items and thus a vertical scrollbar is shown (this is actually the main purpose for using a DataGrid in the first place).
I attached a SingleSelectionModel to the DataGrid in order to be able to select items.
This works fine so far.
However I also have another widget with which the user can interact. Based on that user action a item from the DataGrid should be selected.
Sometimes the selected item is not in the visible screen region and the user has to scroll down in the DataGrid to see it.
Is there any way to automatically or manually scroll down, so that the selected item is visible?
I checked the JavaDocs of the DataGrid and found no appropriate method or function for doing that.
Don't know if this works, but you could try to get the row element for the selection and use the scrollIntoView Method.
Example Code:
dataGrid.getRowElement(INDEX_OF_SELECTED_ITEM).scrollIntoView();
The answer above works pretty well, though if the grid is wider than your window and has a horizontal scroll bar, it also scrolls all the way to the right which is pretty annoying. I was able to get it to scroll down and stay scrolled left by getting the first cell in the selected row and then having it scroll that into view.
dataGrid.getRowElement(dataGrid.getVisibleItems().indexOf(object)).getCells().getItem(0).scrollIntoView();
Don't have time to try it out, but DataGrid implements the interface HasRows, and HasRows has, among other things, a method called setVisibleRange. You just need to figure out the row number of the item that you want to focus on, and then set the visible range from that number n to n+50. That way the DataGrid will reset to put that item at the top (or near the top if it is in the last 50 elements of the list backing the DataGrid). Don't forget to redraw your DataGrid.
Have you already looked at this? If so, I'd be surprised that it didn't work.
Oh, and since this is one widget talking to another, you probably have some messaging set up and some message handlers so that when the user interacts with that second widget and "selects" the item, the message fires on the EventBus and a handler for that message fixes up the DataGrid along the lines I've described. I think you'll have to do this wiring yourself.
My solution, a little better:
dataGrid.getRow(model).scrollIntoView();
I got a Out of bounds exception doing the above.
I solved it getting the ScrollPanel in the DataGrid and used .scrollToTop() and so on on the ScrollPanel. However, to access the ScrollPanel in the DataGrid I had to use this comment:
http://code.google.com/p/google-web-toolkit/issues/detail?id=6865
As Kem pointed out, it's annoying the "scrollToRight" effect after the scrollIntoView. After me, Kem's solution gives a better behaviour than the base one as usually the first columns in a table are the more meaningful.
I improved a bit his approach, which scrolls horizontally to the first column of the row we want to be visible, by calculating the first visible column on the left before applying the scroll and then scrolling to it.
A final note: Columns absolute left is tested against "51". This is a value I found "experimentally" by looking the JS values in the browser's developer tool, I think it depends on the table's style, you may need to change/calculate it.
Below the code:
public void scrollIntoView(T next) {
int index = datagrid.getVisibleItems().indexOf(next);
NodeList<TableCellElement> cells = datagrid.getRowElement(index).getCells();
int firstVisibleIndex = -1;
for(int i=0; i<cells.getLength() && firstVisibleIndex<0;i++)
if(UIObject.isVisible(cells.getItem(i)) && (cells.getItem(i).getAbsoluteLeft() > 51) && (cells.getItem(i).getAbsoluteTop() > 0))
firstVisibleIndex = i;
cells.getItem(firstVisibleIndex>=0? firstVisibleIndex : 0).scrollIntoView();
}