Tag: Qt

GitQlient 1.4.0 released

I’m happy to announce the release of GitQlient 1.4.0

Five months after the last big release, I present you a new GitQlient 1.4.0 version. This comes with less features than the previous versions but in exchange I hope it brings better performance.

In this release I’ve focused in the performance of the Git queries and the RAM memory used by GitQlient. I’ve done a deep research on the usage of QString and the allocations that were done to reduce them to the minimum. I’ve also added an internal cache system so now the UI won’t freeze every time the main graph needs to be refreshed. This also includes the separation between the data update of the references and the log history.

During this version I’ve paused the development of GitHub integration although is totally stable and usable, and the integration of Jenkins. This last integration will take a bit longer to refactor so I’ll focus on it in the following releases.

GitQlient 1.4.0 binaries

You can find the binaries for GitQlient 1.4.0 on the release section on the GitHub repo:

New features in GitQlient 1.4.0?

  • Squash merge branches
  • Squash commits: it is now possible to squash the last commits of the current branch. Of course, if the commits where already pushed to remote, they will need to be pushed force.
  • GitQlient now has an internal cache: Local Git operations will be done against the cache and later asynchronously against remote. This allow GitQlient to run faster for local changes that doesn’t need synchronization. This also reduces the UI freeze when refreshing because of changes in the graph.
  • Credentials can be managed by GitQlient with the proper configuration. This can be done through the Config screen.
  • Multi cherry-pick: It is possible now to cherry-pick several commits at once when they’re not part of the current branch.
  • Search tags in the Branches widget: In addition to branches, it is possible to search tags by name.
  • DEB package: GitQlient is now release as well as a DEB package in addition to RPM.

Other features

Technical improvements:

  • Reduced the number of memory allocations (specially with QString)

Known issues:

  • Any MacOS issues are still opened waiting for a system to be tested on.

GitQlient 1.3.0 released

I’m happy to announce the release of GitQlient 1.3.0

Six months after the last big release, I present a new GitQlient 1.3.0 version. This comes with a lot of new stuff that I’m going to talk about in this post.

In this release I’ve continued UX refactor I started in the previous one. This time I harmonized the styles for both the bright and the dark color schemas. Another part I’ve focused in a follow-up of the last release, one of the main focus areas has been the UX in the commit area and in the code editor (adding search).

The second big area has been extending the support of GitQlient for other platforms as Haiku, providing RPMs for CentOS and Fedora and removing submodules to facilitate the release in ArchLinux.

The last area but the most important one is about the new functionality and the improvements that GitQlient 1.3.0 includes. This will need a new section, of course. One of the big changes is the inclusion of new Qt dependencies: WebEngineWidgets and WebChannel.

GitQlient 1.3.0 binaries

You can find the binaries for GitQlient 1.3.0 on the release section on the GitHub repo:

New features in GitQlient 1.3.0?

  • GitHub integration
  • Jenkins integration (preview)
  • Stage chunk
  • Subtrees
  • Deleting submodules
  • Remotes
  • Edit Git/GitQlient configuration in place
  • Translations enabled
  • Pomodoro

Extending GitQlient

One of the new things in this GitQlient 1.3.0 is the extension of the GitHub integration and the addition of Jenkins integration (as preview).

With the GitHub integration it is possible now to create issues and PRs. You can do code reviews of a PR with code extracts, comments, reviews and markdown support as well.

The other big feature, Jenkins, it’s in an early stage. You will be able to review the status of your remote Jenkins server, trigger builds and check the result of the different builds.

Finally, not a big feature but a nice to have one is the Pomodoro. GitQlient is becoming more a workspace manager than a simple GitQlient. So, I thought that having a pomodoro clock in GitQlient would be a nice to have feature.

Minor features

The UX/UI refactor I’ve been doing would be included in the minor features. Unfortunately, it doesn’t bring any really big change, but I hope it simplifies workflows when doing certain Git operations. Among other changes I’ve included:

  • Showing if commits are signed: The History view now shows a green check on the author column if the commit is signed
  • Search functionality in code editor
  • Search functionality for the branches
  • Unified untracked and unstaged lists

The future

For the next release there are some nice features I’ve already planned:

  • Code refactor: Caused by the big growth of GitQlient.
  • Moving commits from UI
  • Squashing commits
  • DEB packages
  • Refactor the GitHub API classes
  • Finish Jenkins integration

As a result of the code growth during the last two releases I’d like to improve the structure and readability of it.

Consequently, I wouldn’t expect too many new additions or changes for next release but a better performance and code split to facilitate fixes and app extension in the future.

You can see the release planning and all the features that every release will contain in the Release Plan. And as always, if you’d like some feature or you’re missing something in GitQlient, check that it’s not yet in the backlog and open an issue on GitHub!

Deleaker review on GitQlient

Today I’m going to change the subject a bit! Even that is still based on Qt it only touches GitQlient tangentially. This time GitQlient is the test environment and thing to show is the Deleaker review!

Artem contacted me a long time ago and my plan was to publish a post before the word pandemic became mainstream. Unfortunately, for obvious reasons I’ve had to delay it until I found time.

I never checked how GitQlient was doing it with the memory before. Mainly because I trusted Qt handling it for me. That’s fine most of the time but is also a bit error-prone since GitQlient is doing a lot of things. And not always they are under Qt’s radar.

Nowadays, I’m keeping GitQlient running more than usual. That made me realize that memory is really important so it doesn’t swallow all the RAM in the computer. During Christmas I finally found some time to go back to my Windows partition to test Deleaker with GitQlient.

After some time with it I think I’ve learned a bit about how it works. So here are some of my thoughts!

My setup

For the test I’m using Visual Studio 2019 Community, although Deleaker also works with Qt Creator and it has a really nice integration. I’m using all the allocation types because I wanted to see everything although in my case I was interested in the memory ones:

Using all Deleaker allocation types
Using all Deleaker allocation types

Running Deleaker

The first test I did was to run GitQlient, open a repository and closing it without interacting with Git at all.

Deleaker allows you to take Snapshots and that’s a really powerful tool I’ve used during the whole test. In Visual Studio, you just need to go to Extensions -> Deleaker -> Deleaker Window. In the top left you can see the option Take Snapshot and that’s the one I press every time I did a significant change.

For this Deleaker review I used 3 snapshots in addition to the last one that Deleaker automatically creates. After you take a snapshot, the stack trace will be available for you to dig into it. So there is no need to wait until the final execution and remember what each snapshot was about. After the very first snapshot I got the following stack trace:

First snapshot with Deleaker
First snapshot of Deleaker

This scared me a lot, because I wasn’t expecting to get so many leaks to start with. But I was wrong: when I took the snapshots, the objects are attached to the life of their parent widget. So, for now I don’t need to worry!


Next step was to open a repository, take a snapshot and right after that, close it taking another snapshot afterwards. This is a summary of the snapshots I took:

  1. After GitQlient starts
  2. Once a Git repository is open
  3. After closing a Git repository
  4. Diff between #2 and #1
  5. Diff between #3 and #1
  6. Automatic snapshot after closing GitQlient

In Deleaker you can see the diff between two snapshots by clicking on the Compare with… button. That let’s you select the snapshot you want to compare with.

That’s a nice feature but I was confused since I expected to see the subtraction between both snapshots. What it seems not to be the case at least as straight-forward as I thought for the Snapshot #4.

I compared the status after I closed the repository and this time the results where way better. Deleaker actually detected several leaks I was having!

Leaks found!

One of them was located in GitRepoLoader class: I just forgot to use smart pointers!

Stack trace of Deleaker
Stack trace of Deleaker
The leak is detected!
The leak is detected!

Deleaker also help me finding a really nasty bug/leak that was quite challenge to fix. It involved threads and singletons and it was hard since it was during tearing down the app. The problem is apparently that the deleteLater() was not being called (or maybe not detected by Deleaker, since it’s managed by Qt). Digging more I found that QLogger had an issue when tearing down and sometimes I was actually leaking, so I did a refactor on it.

Another leak was in IssueDetailedView. According to Deleaker it was leaking all around and that was weird:

Leaking that much?
Leaking that much?

This was a especially difficult case to find the leak since the stack trace shows the creation of the object but nothing else. After digging a bit where this class is used I found that I was deleting layouts but not their content:

Forgot to delete those widgets!
Forgot to delete those widgets!

Checking the RAM usage

Another tool that I wanted to use is the RAM analyzer to see how much memory GitQlient is using and if it’s freeing it. I’m not entering in detail about how Qt works and what I can do for that (probably needs a post or a series of them). For the Deleaker review is to see how it performs compared to the Visual Studio tool.

For this case unfortunately I have to say that Visual Studio provides a better integration feature. On Deleaker I missed a mark in the RAM history for when I took the snapshot and to be able to scroll no it’s usage. On Visual Studio I’m able to tooltip the amount of memory that GitQlient was using in a specific moment:

The RAM usage of GitQlient
The RAM usage of GitQlient

Finally, one thing that needs improvements is the over-watch of the RAM when it’s being freed. On Deleaker I didn’t manage to get any significant change in the graph and that difficult me this specific task:

Comparison between Visual Studio and Deleaker
Comparison between Visual Studio and Deleaker

Deleaker review: some final thoughts

I feel that the Deleaker brings a more powerful UI that the tool that Visual Studio uses for the stack trace. It also detects (or at least shows) the files and lines way better and it allows you to select the module you want to process, so you can discard false positives really quick.

The integration with Qt applications makes it way more intuitive and easier to use that the Visual Studio tool. Especially when it comes to identify the file and line that is the root cause of the leak. On Visual Studio that gets quite messy when it comes to show the allocations.

In the performance part, I would like to see some more UI interactions with the RAM graph, especially when linked with snapshots. This can is a very powerful tool if you want to check not only leaks but performance.

QLogger: A Qt multi-threading logger

Since I restarted the blog more than 2 years ago I’ve never talked about QLogger in-depth. I linked to the repo and did a small introduction in the second entry (Logging with Qt) but that was all.

During this year QLogger has had some nice improvements going from some sort of threading to full multi-threading support. During the last month it went from close configuration to full. When once it was only possible to choose the log level, now you can configure a lot of different things, including the output type.

All of that while keeping the retro-compatibility from the old usage.

How to use of QLogger

The initialization of QLogger remains the same as it was before. It’s still follows the singleton pattern and same signature. The following line will create the instance if it doesn’t exist or it will retrieve the existing one:


After that you can choose to pause it so it doesn’t print any messages until you resume it:

const auto logger = QLoggerManager::getInstance();


Adding destinations (basic level)

QLogger works in a way where you add modules (at least one) and a file where you want to print. This is the simplest scenario and gives you the ability to add several modules that print in the same file by passing the list of modules. Or splitting the file printing a variable number of modules by adding destinations one by one.

const auto logger = QLoggerManager::getInstance();

logger->addDestination("myLog.log", "UI");
logger->addDestination("myOtherLog.log", {"Network", "DB"});

Finally, to change the log level of those modules, you can use the following:

const auto logger = QLoggerManager::getInstance();
const auto infoLogLevel = LogLevel::Debug;


Logging with QLogger

The easier way to log using QLogger is by using the predefined methods. They automatically log the message using the desired level from the following 7 ones:

  • QLog_Trace
  • QLog_Debug
  • QLog_Info
  • QLog_Warning
  • QLog_Error
  • QLog_Fatal

The usage would be something like:

QLog_Info("UI", QString("Executing the command: {%1}").arg(cmd));

If the choosen level (suffix _Info) is equal or higher than the configured level, it will print the message onto the destination.

New configuration for QLogger

Recently, a user named AphaseDev has introduced a new bunch of features in QLogger. That includes a whole level of configuration for the destinations as well as adding support for console logging using QLogger. The new configuration includes the following fields:

  • Folder where the logs directory will be placed.
  • Log mode: Now the user can define if the logs will be printed in the file, the console or both.
  • File name suffix: It is possible to configure the log file suffix showing the date and time or a number.
  • Log message format: The log message can include a wider amount of information and not only the module and the log message. It includes the thread that printed the message, the function who printed the message, and the file and line.

In addition to the configuration of the destination, it is now possible to configure the maximum size of the file. The default value is 1 MB but you can add any number in here. Removing the log files is also possible through the clearFileDestinationFolder method and optionally providing the oldest date to keep the files from.

It is also possible to overwrite the log mode (output redirected to console or log file) during execution time.


So far I’ve kept QLogger as an “under development” repository. The problem of releasing versions of a library for Qt is that I would have to provide versions compiled with all the possible compilers and at least several Qt versions, what is something annoying.

So far nobody has requested that and my intention is to keep the repository free of of pre-compiled libraries. However at the same time of this post I’m starting to add a release tag to the QLogger repository. This will allow users to have an easier way to get the tested and ready-for-production versions of the library.

Since this is a library, I’m concerned about breaking changes so I’m going to use the QT_DEPRECATED_X attribute (equivalent to the deprecated attribute added in C++14) and add that flag in the configuration of the project. This will allow people to get at least warnings or errors in the best case.

The code can be found in here.

Feel free to comment and let me know what you would like to see in QLogger in the future.

New release of GitQlient 1.2.0

I’m happy to announce the release of GitQlient 1.2.0

Three months after the last minor release I present a new GitQlient 1.2.0 with some fixes, several improvements and a lot of new features. For this release I’ve put the main focus in the UI/UX. That includes both the new features and the improvements of the existing ones.

During this development phase, part of the UI changes have happened in the diff widget by providing multiple views for the changes. I’ve also added a tiny text editor with syntax highlight (for C/C++ for now). Other UI/UX changes are the possibility to minimize the branches widget and to pin repositories. All of this has been marinated with an improvement in the way the settings are stored.

Finally, it is worth to mention the tech preview of GitHub/GitLab integration where you can create issues, pull/merge requests as well as see the status of those in the graph view. This is a new path that I’ll explore in the future where I have other ideas about the integration of third party apps. As a complement, from now on, GitQlient will notify you when a new version is available!

The binaries

You can find the binaries for GitQlient 1.2.0 on the release section on the GitHub repo:

What’s new in GitQlient 1.2.0?

The new version includes a lot of things:

  • Pinned tabs
  • GitHub integration (tech preview)
  • Tiny text editor
  • New diff for the WIP
  • Minimalist branches widget
  • Refactored the diff view
  • Bright colours schema
  • Update notifications
  • Settings per repository
  • More configuration options

New WIP diff view

The new diff view adds the possibility of viewing the file in differnt ways (full view or split) as well as navigate through the changes (in the split mode for now).

When the diff it’s opened from the WIP view, it also adds several extra controls like edit, stage and revert.

New WIP diff view
GitQlient – New WIP diff view

New tiny text editor

The second big feature is the tiny text editor I’ve embedded in GitQlient. It allows you to edit the files that are currently in WIP and pending to be commited.

Some times I’ve missed the ability to edit the files because I forgot something and in QtCreator may be I’ve changed the project or closed the file. I usually work with very large projects and it’s very handy to have an editor just in case.

GitQlient tiny text editor
GitQlient – Tiny text editor

The future

For the next release there are some nice features already planned:

  • Add subtree Git feature support
  • Git LFS
  • RPM and DEB packages
  • Prepare GitQlient for translations

As I said, you can see the release planning and all the features that every release will contain in the Release Plan.

As always, if you’d like some feature or you’re missing something in GitQlient, check that it’s not yet in the backlog and open an issue on GitHub!

Non closable QDialog

This is quite beginner post but I believe it’s good to share what we learn through experience with the people that are starting. In this entry I’ll show you how to have a UI non closable QDialog but that it can be close from the code.

In GitQlient I’m using this dialog to inform the user that the repository is being loaded. There is some data I’m loading in the cache/background (branches, tags, etc) that can take a bit if the repository is massive. In my case, I’m working with some repos that are quite big (~10 Gb only text files) and old. People usually forget to remove their branches and right know I can see around 2000 branches and close to 500 tags. In that case an async load is needed so the user has to wait.

How to do a non closable QDialog?

We want to prevent the user to close the dialog in all it’s forms:

  • Disabling the window close icon.
  • Disabling the Esc key that in QDialog closes/cancels the dialog.

Acting on the windows title

The way to do that is quite simple. In the first place we hide the close button:

setWindowFlags(windowFlags() & ~Qt::WindowCloseButtonHint);

This will make the close button gone and keep the resize behaviour and dialog title. If you want a dialog only for the text you can do:


That will remove the title bar and all it’s components.

Method 1: Acting on the Esc key event

QDialog process the Esc key as close/cancel. Since we don’t want that the user closes our dialog, we need to take care of that behaviour. To do that we need to capture the key press event and avoid processing it:

void WaitingDlg::keyPressEvent(QKeyEvent *e)
   if (e->key() == Qt::Key_Escape)


The code is quite simple, we check the pressed key and if it’s the one we’re expecting, we don’t process the event.

Additionally, it’s also useful to have full control of another event: the closeEvent. That method plus a custom close method will give us the full behaviour:

void WaitingDlg::closeEvent(QCloseEvent *e)
   if (!mPrepareToClose)

void WaitingDlg::close()
   mPrepareToClose = true;


In this code I’m using a class member to store when I do want to close the dialog, preventing the dialog to close if it wasn’t me how said it. The result is that this dialog can only be close by calling the close() method.

Method 2: Override reject method

Suggested in the comments by NickD2039 another possibility is to override the reject method, that would prevent the end user from closing the dialog. The code posted has a closeNow method that acts in a similar way to the method 1, it activates a control variable that finall calls accept.

I kind of like a bit more this code, since it’s a bit more elegant and requires you to write less code:

void closeNow()
   mPrepareToClose = true;
   mPrepareToClose = false;

void accept()
   if (mPrepareToClose)

void reject()
   if (mPrepareToClose)

Where to find the source coude?

You can find the source code for the non closable dialog in my GitHub repository for Qt tips. Please noticed that I name it WaitingDlg since I did the code for GitQlient and that was its purpose.

New GitQlient v1.1.1 (+ Mac)

There is available a patch for the version 1.1 of GitQlient.

The new patch version solves a problem with new repositories. The problem prevented users to work with repositories that have no commits. That applies to both new repositories (via init new repo) and already created repos that don’t have any commits.

I took advantage of this issue to release also a release for OSX. It’s as functional as I would expect to have it in the next 1.2.0. However, I’d like if people can test it before going into the next release.

The binaries

You can find the binaries for GitQlient v1.1.1 on the release section on the GitHub repo:

How to pin tabs in QTabWidget

Pin tabs in QTabWidget

When I was doing GitQlient I wanted to have a QTabWidget where I could pin some tabs. I did some research but I found that I had to tweak/hack how QTabWidget and specially the QTabBar inside they behave. At that time it was not an option since I wanted to have the work done ASAP. But now, I have had a little bit more time and I could manage to pin tabs in a QTabWidget.

I thought it was going to be easier than I expected and I’m aware that I don’t probably cover all cases. However, I think I’ve managed to created a class that inherits from QTabWidget where the tabs can actually be pinned.

Pin tabs in QTabWidget
Pin tabs in QTabWidget

In the image above I show how I pinned the first three tabs. The widget puts the pinned tabs in the beginning whereas the unpinned remain after them. Once a tab is unpinned, the widget will remove it from the left part and put the tab as the first one in the right part.

To pin/unpin (and to close a tab) you can do it through the UI using the context menu. This menu appears when you left-click in a tab and it acts over that clicked tab:

QPinTabWidget context menu
Context menu for QPinnableTabWidget

In the other hand, if you need to modify that behaviour programmatically, there are two methods you can use for that:

int addPinnedTab(QWidget *page, const QString &label);
int addPinnedTab(QWidget *page, const QIcon &icon, const QString &label);

Both methods perform the same action that they do in QTabWidget but with the addition that they move the pinned tab to the beginning of the QTabBar.

Explaining the code

You can find the code in GitHub. In the next sections I’ll do an introduction to the code I wrote and why some things look like they are.

The code can be easily understood by a mid-level Qt developer. But even in that case I had to do some tweaks in order to bypass the normal functionality of the QTabBar.


The first thing I needed, of course, was to create a class that inherits from QTabWidget. In that class I’ve created two addPinnedTab methods that add the functionality to pin tabs. The main reason is that I need to keep track of what tabs are pinned and to move them to the right place when pinned.

I also had to overwrite the mousePressEvent and mouseReleaseEvent since I want to create a context menu to pin/unpin tabs. The reason for this is that I’m processing the signal tabBarClicked from the QTabWidget. This signal tells me what tab has been clicked and after getting it I need to filter the left click.

Hidding the close button

By default, the QPinnableTabWidget has the close button visible for all the tabs. This is to allow the user to close easily a tab. However this adds an extra layer of complexity since I need to remove that close button for the pinned tabs. In addition, that same button needs to come back if the user decides to unpin the tab.

I dig into the Qt code and I saw that the QTabBar is adding an overloaded QAbstractButton so I did the same thing. I’ve created two different buttons one that adds the close button, and another that doesn’t show anything. I’ve extracted the code for the real close button from the implementation of the QTabBar. The fake one is just a dummy impl, necessary so it doesn’t crash.

Custom QTabBar

Another tweak I had to do is to overload the QTabBar that QTabWidget has by default since I needed to disable the moving of the pinned tabs. I’ve created a class that overloads the mousePressEvent, mouseMoveEvent and mouseReleaseEvent. Here I check what tab index the user wants to move and I filter to only accept the unpinned ones.

Conclusions and code

I’d like to notice that most of the things I needed to do, can be done in Qt official widgets in an easier and more elegant way. Actually, my code seems more complicated than it is. Basically, because I had to fight the default implementation.

I’ve uploaded the code for QPinnableTabWidget as a library in my GitHub space. Please feel free to open bugs or add comments if you liked it.

Released GitQlientPlugin 1.2.0 for QtCreator

I’m happy to announce the release of GitQlientPlugin 1.2.0

A month ago I released the version 1.1.0 of GitQlient. With that I shipped some improvements, a few fixes and a big new functionality I was missing: the Merge view.

I still have to add some features and add OSX support in GitQlient, but for now the code is fully compatible with the three big ones (Linux, Mac and Windows).

What was missing is some fixes at plugin level, even that I’ve tried to be on track with it, I prefer to focus on GitQlient as app. However, after a month I think I have a stable version of the plugin that can be ship or at least published as version.

I’ve generated two binaries: one for Linux, compiled with GCC 5.3.1 and another with MSVC2017 32bits. The main reason is because those are the compilers that QtCreator uses and then I make sure that the dependencies, that are shipped as dynamic libraries work well. As you can see, Mac is still the pending task.

The binaries

You can find the binaries for GitQlientPlugin 1.2.0 on the release section on the GitHub repo:

Further work

Since GitQlientPlugin is a wrap of GitQlient, you can see the release planning and all the features that every release will contain in the Release Plan for GitQlient.

As always, if you’d like some feature or you’re missing something in GitQlient or in GitQlientPlugin, check that it’s not yet in the backlog and open an issue on GitHub!

New GitQlient release v1.1.0

I’m happy to announce the release of GitQlient 1.1.0

After two and a half months from the first release here I present a new release with some fixes, several improvements and new features. The main focus on this release was to improve the stability of GitQlient, making it faster on Windows (there is room for improvement still) and adding some big features that didn’t go into the first release for time reasons.

During this development phase, some non-development tasks where done as well. One thing I wanted was to improve the documentation and the guides and for that reason I’ve shipped the GitQlient User Manual. Another thing I’ve added is Doxygen documentation to the headers (although it’s not yet finished). It will speed up the future documentation for developers who want to collaborate with the project. Related with that, I’ve also set up a Wiki to show the release plan and I’m currently working with GitHub kanban boards.

Finally, in the part of new features, the major addition is The Merge View, that will show the files that are in conflict and will allow you to resolve the conflict. The edition of the files, though, is done in a external editor that you can now configure. The future will be a live editor in GitQlient.

The binaries

You can find the binaries for GitQlient 1.1.0 on the release section on the GitHub repo:

What’s new in GitQlient 1.1.0?

The new version includes a lot of things:

  • New merge view
  • Basic Git config UI
  • Quick access to the recent projects in the init screen
  • Improved display of branches distance
  • Improved UX when pushing a local branch
  • Improved UX when checkout a branch
  • Improved UX for the graph view
  • Ability to open an external editor to edit files
  • Improved performance on Windows
  • Added Doxygen
  • Added User Manual
  • Added Planning

The Merge View

The merge view it’s special since it isn’t accessible as a regular view. It’s only triggered when GitQlient detects that a merge, pull or cherry-pick has conflicts.

GitQlient – The Merge View

As you can see, once this view is activated a red banner will appear under the quick access buttons. It will remain there until the merge is done. It doesn’t mean you cannot do other Git actions. But you won’t be able to push anything until solved. Of course, you can still navigate between all the other views.

The future

As I said, you can see the release planning and all the features that every release will contain in the Release Plan.

As always, if you’d like some feature or you’re missing something in GitQlient, check that it’s not yet in the backlog and open an issue on GitHub!