Merge remote-tracking branch 'origin/13.0'

Conflicts:
	src/plugins/android/androidsettingswidget.cpp

Change-Id: Ifcb16aa16c7bc2792de25d0ee7a22cf0e39a05f8
This commit is contained in:
Eike Ziller
2024-02-29 12:44:58 +01:00
104 changed files with 2545 additions and 1567 deletions

View File

@@ -39,6 +39,8 @@ General
for searching in `Files in File System`
* Added `Copy to Clipboard` to the `About Qt Creator` dialog
([QTCREATORBUG-29886](https://bugreports.qt.io/browse/QTCREATORBUG-29886))
* Fixed issues with the window actions
([QTCREATORBUG-30381](https://bugreports.qt.io/browse/QTCREATORBUG-30381))
Editing
-------
@@ -78,6 +80,8 @@ Editing
* Clangd
* Fixed that `Follow Symbol Under Cursor` only worked for exact matches
([QTCREATORBUG-29814](https://bugreports.qt.io/browse/QTCREATORBUG-29814))
* Fixed the version check for remote `clangd` executables
([QTCREATORBUG-30374](https://bugreports.qt.io/browse/QTCREATORBUG-30374))
### QML
@@ -133,6 +137,8 @@ Projects
([QTCREATORBUG-29530](https://bugreports.qt.io/browse/QTCREATORBUG-29530))
* Added a file wizard for Qt translation (`.ts`) files
([QTCREATORBUG-29775](https://bugreports.qt.io/browse/QTCREATORBUG-29775))
* Added an optional warning for special characters in build directories
([QTCREATORBUG-20834](https://bugreports.qt.io/browse/QTCREATORBUG-20834))
* Improved the environment settings by making the changes explicit in a
separate, text-based editor
* Increased the maximum width of the target selector
@@ -184,6 +190,9 @@ Debugging
### C++
* Added a pretty printer for `std::tuple`
* Improved the display of size information for the pretty printer of
`QByteArray`
([QTCREATORBUG-30065](https://bugreports.qt.io/browse/QTCREATORBUG-30065))
* Fixed that breakpoints were not hit while the message dialog about missing
debug information was shown
([QTCREATORBUG-30168](https://bugreports.qt.io/browse/QTCREATORBUG-30168))
@@ -270,6 +279,7 @@ Andre Hartmann
André Pönitz
Andreas Loth
Artem Sokolovskii
Assam Boudjelthia
Brook Cronin
Burak Hancerli
Christian Kandeler
@@ -306,6 +316,7 @@ Robert Löhning
Sami Shalayel
Samuel Jose Raposo Vieira Mira
Samuel Mira
Semih Yavuz
Serg Kryvonos
Shrief Gabr
Sivert Krøvel

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

View File

@@ -99,7 +99,7 @@
\section2 Supported GDB Versions
Use GDB 7.5, or later, with the Python scripting extension and Python version
3.3, or later.
3.5, or later.
For remote debugging using GDB and GDB server, the minimum supported version
of GDB server on the target \l{glossary-device}{device} is 7.0.
@@ -121,35 +121,26 @@
On Linux, the minimum supported version is LLDB 3.8.
\omit
\section2 GDB Adapter Modes
[Advanced Topic]
\section2 GDB Run Modes
The GDB native debugger used internally by the debugger plugin runs in
different adapter modes to cope with the variety of supported platforms and
environments. All GDB adapters inherit from AbstractGdbAdapter:
different modes to cope with the variety of supported platforms and
environments:
\list
\li PlainGdbAdapter debugs locally started GUI processes. It is
physically split into parts that are relevant only when Python is
available, parts relevant only when Python is not available, and
mixed code.
\li Plain mode debugs locally started processes that do not need console input.
\li TermGdbAdapter debugs locally started processes that need a console.
\li Terminal mode debugs locally started processes that need a console.
\li AttachGdbAdapter debugs local processes started outside \QC.
\li Attach mode debugs local processes started outside \QC.
\li CoreGdbAdapter debugs core files generated from crashes.
\li Core mode debugs core files generated from crashes.
\li RemoteGdbAdapter interacts with the GDB server running on Linux.
\li Remote mode interacts with the GDB server running on Linux.
\endlist
\endomit
\section1 Installing Native Debuggers
The following sections describe installing native debuggers.

View File

@@ -238,6 +238,10 @@
\image qtcreator-qml-js-editing.webp {QML/JS Editing preferences}
When using \c qmlls from Qt 6.7 or later, it is recommended
to set \l{QT_QML_GENERATE_QMLLS_INI} to \c{ON} in
\uicontrol {Initial Configuration}.
\sa {Manage Language Servers}{How To: Manage Language Servers},
{Enabling and Disabling Messages}, {Language Servers}
*/

View File

@@ -15,9 +15,8 @@
application or the \uicontrol {Open Terminal} button to open a terminal,
it opens as an output view.
To open the terminal in a separate window, select \preferences >
\uicontrol Terminal, and deselet the \uicontrol {Use internal terminal}
check box.
To open the terminal in a separate window, go to \preferences >
\uicontrol Terminal, and clear \uicontrol {Use internal terminal}.
On Linux and \macos, you can set the terminal to open by selecting
\preferences > \uicontrol Environment > \uicontrol System.
@@ -39,7 +38,8 @@
.
\li To select a word in a terminal, double-click it. To select the whole line,
triple-click it.
triple-click it. To select all text, select \uicontrol {Select All}
in the context menu or press \key {Ctrl+A}.
\li To open links in a browser, files in the editor, or folders in the
\l Projects view, hover the mouse over them, and press \key Ctrl.

View File

@@ -309,10 +309,11 @@
\section2 Working with Branches
To work with Git branches, select \uicontrol {Branches}. The
\uicontrol {Git Branches} sidebar view shows the checked out
branch in bold and underlined in the list of branches.
\uicontrol {Git Branches} view shows a list of branches, as well
as the differences between your local branches and their origin.
The branch you checked out is shown in bold and underlined.
\image qtcreator-vcs-gitbranch.png {Git Branches sidebar view}
\image qtcreator-git-branches.webp {Git Branches view}
Old entries and tags are filtered out of the list of branches
by default. To include them, select \inlineimage icons/filtericon.png
@@ -332,6 +333,20 @@
To refresh the list of branches, select \inlineimage icons/reload_gray.png
(\uicontrol Refresh).
\section3 Adding Branches
To create a new tracking or non-tracking branch, select
\inlineimage icons/plus.png.
\image qtcreator-git-add-branch.webp {Add Branch dialog}
To check out the branch when creating it, select
\uicontrol {Checkout new branch}.
To track the selected branch, select \uicontrol {Track local branch}.
\section3 Managing Branches
The context menu for a branch has the following functions:
\table

View File

@@ -11,7 +11,7 @@ from utils import TypeCode
sys.path.insert(1, os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))))
from dumper import DumperBase, SubItem
from dumper import DumperBase, SubItem, Children, DisplayFormat, UnnamedSubItem
class FakeVoidType(cdbext.Type):
@@ -84,10 +84,9 @@ class Dumper(DumperBase):
self.check(isinstance(nativeValue, cdbext.Value))
val = self.Value(self)
val.name = nativeValue.name()
val._type = self.fromNativeType(nativeValue.type())
# There is no cdb api for the size of bitfields.
# Workaround this issue by parsing the native debugger text for integral types.
if val._type.code == TypeCode.Integral:
if nativeValue.type().code() == TypeCode.Integral:
try:
integerString = nativeValue.nativeDebuggerValue()
except UnicodeDecodeError:
@@ -106,16 +105,18 @@ class Dumper(DumperBase):
base = 16
else:
base = 10
signed = not val._type.name.startswith('unsigned')
signed = not nativeValue.type().name().startswith('unsigned')
try:
val.ldata = int(integerString, base).to_bytes(val._type.size(),
val.ldata = int(integerString, base).to_bytes((nativeValue.type().bitsize() +7) // 8,
byteorder='little', signed=signed)
except:
# read raw memory in case the integerString can not be interpreted
pass
if val._type.code == TypeCode.Enum:
if nativeValue.type().code() == TypeCode.Enum:
val.ldisplay = self.enumValue(nativeValue)
val.isBaseClass = val.name == val._type.name
elif not nativeValue.type().resolved and nativeValue.type().code() == TypeCode.Struct and not nativeValue.hasChildren():
val.ldisplay = self.enumValue(nativeValue)
val.isBaseClass = val.name == nativeValue.type().name()
val.nativeValue = nativeValue
val.laddress = nativeValue.address()
val.lbitsize = nativeValue.bitsize()
@@ -136,6 +137,9 @@ class Dumper(DumperBase):
for f in nativeType.fields()])
return typeId
def nativeValueType(self, nativeValue):
return self.fromNativeType(nativeValue.type())
def fromNativeType(self, nativeType):
self.check(isinstance(nativeType, cdbext.Type))
typeId = self.nativeTypeId(nativeType)
@@ -150,51 +154,66 @@ class Dumper(DumperBase):
if nativeType.name().startswith('<function>'):
code = TypeCode.Function
elif nativeType.targetName() != nativeType.name():
targetType = self.lookupType(nativeType.targetName(), nativeType.moduleId())
if targetType is not None and targetType is not nativeType:
return self.createPointerType(targetType)
return self.createPointerType(nativeType.targetName())
if code == TypeCode.Array:
# cdb reports virtual function tables as arrays those ar handled separetly by
# the DumperBase. Declare those types as structs prevents a lookup to a
# none existing type
if not nativeType.name().startswith('__fptr()') and not nativeType.name().startswith('<gentype '):
targetType = self.lookupType(nativeType.targetName(), nativeType.moduleId())
if targetType is not None:
return self.createArrayType(targetType, nativeType.arrayElements())
targetName = nativeType.targetName()
count = nativeType.arrayElements()
if targetName.endswith(']'):
(prefix, suffix, inner_count) = self.splitArrayType(targetName)
type_name = '%s[%d][%d]%s' % (prefix, count, inner_count, suffix)
else:
type_name = '%s[%d]' % (targetName, count)
tdata = self.TypeData(self, typeId)
tdata.name = type_name
tdata.code = TypeCode.Array
tdata.ltarget = targetName
tdata.lbitsize = lambda: nativeType.bitsize()
return self.Type(self, typeId)
code = TypeCode.Struct
tdata = self.TypeData(self, typeId)
tdata.name = nativeType.name()
tdata.lbitsize = nativeType.bitsize()
tdata.lbitsize = lambda: nativeType.bitsize()
tdata.code = code
tdata.moduleName = nativeType.module()
tdata.moduleName = lambda: nativeType.module()
if code == TypeCode.Struct:
tdata.lfields = lambda value: \
self.listFields(nativeType, value)
tdata.lalignment = lambda: \
self.nativeStructAlignment(nativeType)
if code == TypeCode.Enum:
tdata.enumDisplay = lambda intval, addr, form: \
self.nativeTypeEnumDisplay(nativeType, intval, form)
tdata.templateArguments = lambda: \
self.listTemplateParameters(nativeType.name())
return self.Type(self, typeId)
def listFields(self, nativeType, value):
if value.address() is None or value.address() == 0:
raise Exception("")
nativeValue = value.nativeValue
if nativeValue is None:
nativeValue = cdbext.createValue(value.address(), nativeType)
def listNativeValueChildren(self, nativeValue):
index = 0
nativeMember = nativeValue.childFromIndex(index)
while nativeMember is not None:
while nativeMember:
if nativeMember.address() != 0:
yield self.fromNativeValue(nativeMember)
index += 1
nativeMember = nativeValue.childFromIndex(index)
def listValueChildren(self, value):
nativeValue = value.nativeValue
if nativeValue is None:
nativeValue = cdbext.createValue(value.address(), self.lookupNativeType(value.type.name, 0))
return self.listNativeValueChildren(nativeValue)
def listFields(self, nativeType, value):
nativeValue = value.nativeValue
if nativeValue is None:
nativeValue = cdbext.createValue(value.address(), nativeType)
return self.listNativeValueChildren(nativeValue)
def nativeStructAlignment(self, nativeType):
#DumperBase.warn("NATIVE ALIGN FOR %s" % nativeType.name)
def handleItem(nativeFieldType, align):
@@ -307,6 +326,7 @@ class Dumper(DumperBase):
namespaceIndex = name.find('::')
if namespaceIndex > 0:
namespace = name[:namespaceIndex + 2]
self.qtNamespace = lambda: namespace
self.qtCustomEventFunc = self.parseAndEvaluate(
'%s!%sQObject::customEvent' %
(self.qtCoreModuleName(), namespace)).address()
@@ -498,7 +518,7 @@ class Dumper(DumperBase):
else:
val = self.Value(self)
val.laddress = value.pointer()
val._type = value.type.dereference()
val._type = DumperBase.Type(self, value.type.targetName)
val.nativeValue = value.nativeValue
return val
@@ -519,3 +539,424 @@ class Dumper(DumperBase):
def symbolAddress(self, symbolName):
res = self.nativeParseAndEvaluate(symbolName)
return None if res is None else res.address()
def putItemX(self, value):
#DumperBase.warn('PUT ITEM: %s' % value.stringify())
typeobj = value.type # unqualified()
typeName = typeobj.name
self.addToCache(typeobj) # Fill type cache
if not value.lIsInScope:
self.putSpecialValue('optimizedout')
#self.putType(typeobj)
#self.putSpecialValue('outofscope')
self.putNumChild(0)
return
if not isinstance(value, self.Value):
raise RuntimeError('WRONG TYPE IN putItem: %s' % type(self.Value))
# Try on possibly typedefed type first.
if self.tryPutPrettyItem(typeName, value):
if typeobj.code == TypeCode.Pointer:
self.putOriginalAddress(value.address())
else:
self.putAddress(value.address())
return
if typeobj.code == TypeCode.Pointer:
self.putFormattedPointer(value)
return
self.putAddress(value.address())
if value.lbitsize is not None:
self.putField('size', value.lbitsize // 8)
if typeobj.code == TypeCode.Function:
#DumperBase.warn('FUNCTION VALUE: %s' % value)
self.putType(typeobj)
self.putSymbolValue(value.pointer())
self.putNumChild(0)
return
if typeobj.code == TypeCode.Enum:
#DumperBase.warn('ENUM VALUE: %s' % value.stringify())
self.putType(typeobj.name)
self.putValue(value.display())
self.putNumChild(0)
return
if typeobj.code == TypeCode.Array:
#DumperBase.warn('ARRAY VALUE: %s' % value)
self.putCStyleArray(value)
return
if typeobj.code == TypeCode.Integral:
#DumperBase.warn('INTEGER: %s %s' % (value.name, value))
val = value.value()
self.putNumChild(0)
self.putValue(val)
self.putType(typeName)
return
if typeobj.code == TypeCode.Float:
#DumperBase.warn('FLOAT VALUE: %s' % value)
self.putValue(value.value())
self.putNumChild(0)
self.putType(typeobj.name)
return
if typeobj.code in (TypeCode.Reference, TypeCode.RValueReference):
#DumperBase.warn('REFERENCE VALUE: %s' % value)
val = value.dereference()
if val.laddress != 0:
self.putItem(val)
else:
self.putSpecialValue('nullreference')
self.putBetterType(typeName)
return
if typeobj.code == TypeCode.Complex:
self.putType(typeobj)
self.putValue(value.display())
self.putNumChild(0)
return
self.putType(typeName)
if value.summary is not None and self.useFancy:
self.putValue(self.hexencode(value.summary), 'utf8:1:0')
self.putNumChild(0)
return
self.putExpandable()
self.putEmptyValue()
#DumperBase.warn('STRUCT GUTS: %s ADDRESS: 0x%x ' % (value.name, value.address()))
if self.showQObjectNames:
#with self.timer(self.currentIName):
self.putQObjectNameValue(value)
if self.isExpanded():
self.putField('sortable', 1)
with Children(self):
baseIndex = 0
for item in self.listValueChildren(value):
if item.name.startswith('__vfptr'):
with SubItem(self, '[vptr]'):
# int (**)(void)
self.putType(' ')
self.putSortGroup(20)
self.putValue(item.name)
n = 100
if self.isExpanded():
with Children(self):
n = self.putVTableChildren(item, n)
self.putNumChild(n)
continue
if item.isBaseClass:
baseIndex += 1
# We cannot use nativeField.name as part of the iname as
# it might contain spaces and other strange characters.
with UnnamedSubItem(self, "@%d" % baseIndex):
self.putField('iname', self.currentIName)
self.putField('name', '[%s]' % item.name)
self.putSortGroup(1000 - baseIndex)
self.putAddress(item.address())
self.putItem(item)
continue
with SubItem(self, item.name):
self.putItem(item)
if self.showQObjectNames:
self.tryPutQObjectGuts(value)
def putFormattedPointerX(self, value: DumperBase.Value):
self.putOriginalAddress(value.address())
pointer = value.pointer()
self.putAddress(pointer)
if pointer == 0:
self.putType(value.type)
self.putValue('0x0')
return
typeName = value.type.name
try:
self.readRawMemory(pointer, 1)
except:
# Failure to dereference a pointer should at least
# show the value of a pointer.
#DumperBase.warn('BAD POINTER: %s' % value)
self.putValue('0x%x' % pointer)
self.putType(typeName)
return
if self.currentIName.endswith('.this'):
self.putDerefedPointer(value)
return
displayFormat = self.currentItemFormat(value.type.name)
if value.type.targetName == 'void':
#DumperBase.warn('VOID POINTER: %s' % displayFormat)
self.putType(typeName)
self.putSymbolValue(pointer)
return
if displayFormat == DisplayFormat.Raw:
# Explicitly requested bald pointer.
#DumperBase.warn('RAW')
self.putType(typeName)
self.putValue('0x%x' % pointer)
self.putExpandable()
if self.currentIName in self.expandedINames:
with Children(self):
with SubItem(self, '*'):
self.putItem(value.dereference())
return
limit = self.displayStringLimit
if displayFormat in (DisplayFormat.SeparateLatin1String, DisplayFormat.SeparateUtf8String):
limit = 1000000
if self.tryPutSimpleFormattedPointer(pointer, typeName,
value.type.targetName, displayFormat, limit):
self.putExpandable()
return
if DisplayFormat.Array10 <= displayFormat and displayFormat <= DisplayFormat.Array10000:
n = (10, 100, 1000, 10000)[displayFormat - DisplayFormat.Array10]
self.putType(typeName)
self.putItemCount(n)
self.putArrayData(value.pointer(), n, value.type.targetName)
return
#DumperBase.warn('AUTODEREF: %s' % self.autoDerefPointers)
#DumperBase.warn('INAME: %s' % self.currentIName)
if self.autoDerefPointers:
# Generic pointer type with AutomaticFormat, but never dereference char types:
if value.type.targetName not in (
'char',
'signed char',
'int8_t',
'qint8',
'unsigned char',
'uint8_t',
'quint8',
'wchar_t',
'CHAR',
'WCHAR',
'char8_t',
'char16_t',
'char32_t'
):
self.putDerefedPointer(value)
return
#DumperBase.warn('GENERIC PLAIN POINTER: %s' % value.type)
#DumperBase.warn('ADDR PLAIN POINTER: 0x%x' % value.laddress)
self.putType(typeName)
self.putSymbolValue(pointer)
self.putExpandable()
if self.currentIName in self.expandedINames:
with Children(self):
with SubItem(self, '*'):
self.putItem(value.dereference())
def putCStyleArray(self, value):
arrayType = value.type
innerType = arrayType.ltarget
address = value.address()
if address:
self.putValue('@0x%x' % address, priority=-1)
else:
self.putEmptyValue()
self.putType(arrayType)
displayFormat = self.currentItemFormat()
arrayByteSize = arrayType.size()
n = self.arrayItemCountFromTypeName(value.type.name, 100)
p = value.address()
if displayFormat != DisplayFormat.Raw and p:
if innerType.name in (
'char',
'int8_t',
'qint8',
'wchar_t',
'unsigned char',
'uint8_t',
'quint8',
'signed char',
'CHAR',
'WCHAR',
'char8_t',
'char16_t',
'char32_t'
):
self.putCharArrayHelper(p, n, innerType, self.currentItemFormat(),
makeExpandable=False)
else:
self.tryPutSimpleFormattedPointer(p, arrayType, innerType,
displayFormat, arrayByteSize)
self.putNumChild(n)
if self.isExpanded():
if n > 100:
addrStep = innerType.size()
with Children(self, n, innerType, addrBase=address, addrStep=addrStep):
for i in self.childRange():
self.putSubItem(i, self.createValue(address + i * addrStep, innerType))
else:
with Children(self):
n = 0
for item in self.listValueChildren(value):
with SubItem(self, n):
n += 1
self.putItem(item)
def putArrayData(self, base, n, innerType, childNumChild=None):
self.checkIntType(base)
self.checkIntType(n)
addrBase = base
innerType = self.createType(innerType)
innerSize = innerType.size()
self.putNumChild(n)
#DumperBase.warn('ADDRESS: 0x%x INNERSIZE: %s INNERTYPE: %s' % (addrBase, innerSize, innerType))
enc = innerType.simpleEncoding()
maxNumChild = self.maxArrayCount()
if enc:
self.put('childtype="%s",' % innerType.name)
self.put('addrbase="0x%x",' % addrBase)
self.put('addrstep="0x%x",' % innerSize)
self.put('arrayencoding="%s",' % enc)
self.put('endian="%s",' % self.packCode)
if n > maxNumChild:
self.put('childrenelided="%s",' % n)
n = maxNumChild
self.put('arraydata="')
self.put(self.readMemory(addrBase, n * innerSize))
self.put('",')
else:
with Children(self, n, innerType, childNumChild, maxNumChild,
addrBase=addrBase, addrStep=innerSize):
for i in self.childRange():
self.putSubItem(i, self.createValue(addrBase + i * innerSize, innerType))
def tryPutSimpleFormattedPointer(self, ptr, typeName, innerType, displayFormat, limit):
if isinstance(innerType, self.Type):
innerType = innerType.name
if displayFormat == DisplayFormat.Automatic:
if innerType in ('char', 'signed char', 'unsigned char', 'uint8_t', 'CHAR'):
# Use UTF-8 as default for char *.
self.putType(typeName)
(length, shown, data) = self.readToFirstZero(ptr, 1, limit)
self.putValue(data, 'utf8', length=length)
if self.isExpanded():
self.putArrayData(ptr, shown, innerType)
return True
if innerType in ('wchar_t', 'WCHAR'):
self.putType(typeName)
charSize = self.lookupType('wchar_t').size()
(length, data) = self.encodeCArray(ptr, charSize, limit)
if charSize == 2:
self.putValue(data, 'utf16', length=length)
else:
self.putValue(data, 'ucs4', length=length)
return True
if displayFormat == DisplayFormat.Latin1String:
self.putType(typeName)
(length, data) = self.encodeCArray(ptr, 1, limit)
self.putValue(data, 'latin1', length=length)
return True
if displayFormat == DisplayFormat.SeparateLatin1String:
self.putType(typeName)
(length, data) = self.encodeCArray(ptr, 1, limit)
self.putValue(data, 'latin1', length=length)
self.putDisplay('latin1:separate', data)
return True
if displayFormat == DisplayFormat.Utf8String:
self.putType(typeName)
(length, data) = self.encodeCArray(ptr, 1, limit)
self.putValue(data, 'utf8', length=length)
return True
if displayFormat == DisplayFormat.SeparateUtf8String:
self.putType(typeName)
(length, data) = self.encodeCArray(ptr, 1, limit)
self.putValue(data, 'utf8', length=length)
self.putDisplay('utf8:separate', data)
return True
if displayFormat == DisplayFormat.Local8BitString:
self.putType(typeName)
(length, data) = self.encodeCArray(ptr, 1, limit)
self.putValue(data, 'local8bit', length=length)
return True
if displayFormat == DisplayFormat.Utf16String:
self.putType(typeName)
(length, data) = self.encodeCArray(ptr, 2, limit)
self.putValue(data, 'utf16', length=length)
return True
if displayFormat == DisplayFormat.Ucs4String:
self.putType(typeName)
(length, data) = self.encodeCArray(ptr, 4, limit)
self.putValue(data, 'ucs4', length=length)
return True
return False
def putDerefedPointer(self, value):
derefValue = value.dereference()
savedCurrentChildType = self.currentChildType
self.currentChildType = value.type.targetName
self.putType(value.type.targetName)
derefValue.name = '*'
derefValue.autoDerefCount = value.autoDerefCount + 1
if derefValue.type.code == TypeCode.Pointer:
self.putField('autoderefcount', '{}'.format(derefValue.autoDerefCount))
self.putItem(derefValue)
self.currentChildType = savedCurrentChildType
def extractPointer(self, value):
code = 'I' if self.ptrSize() == 4 else 'Q'
return self.extractSomething(value, code, 8 * self.ptrSize())
def createValue(self, datish, typish):
if self.isInt(datish): # Used as address.
return self.createValueFromAddressAndType(datish, typish)
if isinstance(datish, bytes):
val = self.Value(self)
if isinstance(typish, self.Type):
val._type = typish
else:
val._type = self.Type(self, typish)
#DumperBase.warn('CREATING %s WITH DATA %s' % (val.type.name, self.hexencode(datish)))
val.ldata = datish
val.check()
return val
raise RuntimeError('EXPECTING ADDRESS OR BYTES, GOT %s' % type(datish))
def createValueFromAddressAndType(self, address, typish):
val = self.Value(self)
if isinstance(typish, self.Type):
val._type = typish
else:
val._type = self.Type(self, typish)
val.laddress = address
if self.useDynamicType:
val._type = val.type.dynamicType(address)
return val

View File

@@ -3041,7 +3041,7 @@ class DumperBase():
or self.type.name.startswith('unsigned ') \
or self.type.name.find(' unsigned ') != -1
if bitsize is None:
bitsize = self.type.bitsize()
bitsize = self.type.lbitsize
return self.extractInteger(bitsize, unsigned)
def floatingPoint(self):
@@ -3512,26 +3512,40 @@ class DumperBase():
tdata.moduleName = self.moduleName
return tdata
@property
def bitsize(self):
if callable(self.lbitsize):
self.lbitsize = self.lbitsize()
return self.lbitsize
class Type():
def __init__(self, dumper, typeId):
self.typeId = typeId
self.typeId = typeId.replace('@', dumper.qtNamespace())
self.dumper = dumper
self.tdata = dumper.typeData.get(typeId, None)
if self.tdata is None:
#DumperBase.warn('USING : %s' % self.typeId)
self.dumper.lookupType(self.typeId)
self.tdata = self.dumper.typeData.get(self.typeId)
self.initialized = False
def __str__(self):
#return self.typeId
return self.stringify()
@property
def tdata(self):
if not self.initialized:
self.initialized = True
self.data = self.dumper.typeData.get(self.typeId, None)
if self.data is None:
#DumperBase.warn('USING : %s' % self.typeId)
self.dumper.lookupType(self.typeId)
self.data = self.dumper.typeData.get(self.typeId)
return self.data
def setTdata(self, tdata):
self.initialized = True
self.data = tdata
@property
def name(self):
tdata = self.dumper.typeData.get(self.typeId)
if tdata is None:
return self.typeId
return tdata.name
return self.typeId if self.tdata is None else self.tdata.name
@property
def code(self):
@@ -3539,7 +3553,7 @@ class DumperBase():
@property
def lbitsize(self):
return self.tdata.lbitsize
return self.tdata.bitsize
@property
def lbitpos(self):
@@ -3547,15 +3561,25 @@ class DumperBase():
@property
def ltarget(self):
if isinstance(self.tdata.ltarget, str):
self.tdata.ltarget = self.dumper.createType(self.tdata.ltarget)
return self.tdata.ltarget
@property
def targetName(self):
if self.tdata.ltarget is None:
return ''
return self.tdata.ltarget if isinstance(self.tdata.ltarget, str) else self.tdata.ltarget.name
@property
def moduleName(self):
if callable(self.tdata.moduleName):
self.tdata.moduleName = self.tdata.moduleName()
return self.tdata.moduleName
def stringify(self):
return 'Type(name="%s",bsize=%s,code=%s)' \
% (self.tdata.name, self.tdata.lbitsize, self.tdata.code)
% (self.tdata.name, self.lbitsize, self.tdata.code)
def __getitem__(self, index):
if self.dumper.isInt(index):
@@ -3659,7 +3683,7 @@ class DumperBase():
def alignment(self):
if self.tdata.code == TypeCode.Typedef:
return self.tdata.ltarget.alignment()
return self.ltarget.alignment()
if self.tdata.code in (TypeCode.Integral, TypeCode.Float, TypeCode.Enum):
if self.tdata.name in ('double', 'long long', 'unsigned long long'):
# Crude approximation.
@@ -3678,7 +3702,7 @@ class DumperBase():
return self.dumper.createPointerType(self)
def target(self):
return self.tdata.ltarget
return self.ltarget
def stripTypedefs(self):
if isinstance(self, self.dumper.Type) and self.code != TypeCode.Typedef:
@@ -3687,7 +3711,7 @@ class DumperBase():
return self.ltarget
def size(self):
bs = self.bitsize()
bs = self.lbitsize
if bs % 8 != 0:
DumperBase.warn('ODD SIZE: %s' % self)
return (7 + bs) >> 3
@@ -3797,12 +3821,12 @@ class DumperBase():
return val
def createPointerType(self, targetType):
if not isinstance(targetType, self.Type):
raise RuntimeError('Expected type in createPointerType(), got %s'
if not isinstance(targetType, (str, self.Type)):
raise RuntimeError('Expected type or str in createPointerType(), got %s'
% type(targetType))
typeId = targetType.typeId + ' *'
typeId = (targetType if isinstance(targetType, str) else targetType.typeId) + ' *'
tdata = self.TypeData(self, typeId)
tdata.name = targetType.name + '*'
tdata.name = (targetType if isinstance(targetType, str) else targetType.name) + '*'
tdata.lbitsize = 8 * self.ptrSize()
tdata.code = TypeCode.Pointer
tdata.ltarget = targetType
@@ -3927,7 +3951,7 @@ class DumperBase():
tdata = self.typeData.get(typish, None)
if tdata is not None:
if tdata.lbitsize is not None:
if tdata.lbitsize > 0:
if callable(tdata.lbitsize) or tdata.lbitsize > 0:
return self.Type(self, typish)
knownType = self.lookupType(typish)
@@ -3944,7 +3968,7 @@ class DumperBase():
if typish.endswith('*'):
tdata.code = TypeCode.Pointer
tdata.lbitsize = 8 * self.ptrSize()
tdata.ltarget = self.createType(typish[:-1].strip())
tdata.ltarget = typish[:-1].strip()
typeobj = self.Type(self, tdata.typeId)
#DumperBase.warn('CREATE TYPE: %s' % typeobj.stringify())

View File

@@ -419,7 +419,7 @@ class Dumper(DumperBase):
targetTypeName = typeName[0:pos1].strip()
#DumperBase.warn("TARGET TYPENAME: %s" % targetTypeName)
targetType = self.fromNativeType(nativeTargetType)
targetType.tdata = targetType.tdata.copy()
targetType.setTdata(targetType.tdata.copy())
targetType.tdata.name = targetTypeName
return self.createArrayType(targetType, count)
if hasattr(nativeType, 'GetVectorElementType'): # New in 3.8(?) / 350.x
@@ -1583,7 +1583,8 @@ class Dumper(DumperBase):
result += ',ignorecount="%d"' % loc.GetIgnoreCount()
result += ',file="%s"' % toCString(lineEntry.GetFileSpec())
result += ',line="%d"' % lineEntry.GetLine()
result += ',addr="%s"},' % addr.GetFileAddress()
result += ',addr="%s"' % addr.GetLoadAddress(self.target)
result += ',faddr="%s"},' % addr.GetFileAddress()
result += ']'
if lineEntry is not None:
result += ',file="%s"' % toCString(lineEntry.GetFileSpec())

View File

@@ -1,4 +1,5 @@
[General]
Includes=dark.figmatokens
ThemeName=Dark
PreferredStyles=
DefaultTextEditorColorScheme=dark.xml
@@ -406,39 +407,6 @@ Debugger_WatchItem_ValueChanged=ffff6666
Debugger_Breakpoint_TextMarkColor=ffff4040
; Qt Creator Color Tokens - dark mode
Token_Basic_Black=ff131313
Token_Basic_White=fff8f8f8
Token_Accent_Default=ff23b26a
Token_Accent_Muted=ff1f9b5d
Token_Accent_Subtle=ff1a8550
Token_Background_Default=ff1f1f1f
Token_Background_Muted=ff262626
Token_Background_Subtle=ff2e2e2e
Token_Foreground_Default=ff5a5a5a
Token_Foreground_Muted=ff3e3e3e
Token_Foreground_Subtle=ff303030
Token_Text_Default=fff8f8f8
Token_Text_Muted=ffaeaeae
Token_Text_Subtle=ff595959
Token_Stroke_Strong=ffeeeeee
Token_Stroke_Muted=ff727272
Token_Stroke_Subtle=ff3a3a3a
Token_Notification_Alert=ffc98014
Token_Notification_Success=ff1f9b5d
Token_Notification_Neutral=ff016876
Token_Notification_Danger=ffb22245
Welcome_TextColor=text
Welcome_ForegroundPrimaryColor=ffa3a3a3
Welcome_ForegroundSecondaryColor=ff808080
Welcome_BackgroundPrimaryColor=normalBackground
Welcome_BackgroundSecondaryColor=shadowBackground
Welcome_HoverColor=ff404040
Welcome_AccentColor=ff57d658
Welcome_LinkColor=ff67e668
Welcome_DisabledLinkColor=textDisabled
Timeline_TextColor=text
Timeline_BackgroundColor1=normalBackground
Timeline_BackgroundColor2=ff444444

View File

@@ -0,0 +1,31 @@
; Qt Creator Color Tokens - dark mode
[Colors]
Token_Basic_Black=ff131313
Token_Basic_White=fff8f8f8
Token_Accent_Default=ff23b26a
Token_Accent_Muted=ff1f9b5d
Token_Accent_Subtle=ff1a8550
Token_Background_Default=ff1f1f1f
Token_Background_Muted=ff262626
Token_Background_Subtle=ff2e2e2e
Token_Foreground_Default=ff5a5a5a
Token_Foreground_Muted=ff3e3e3e
Token_Foreground_Subtle=ff303030
Token_Text_Default=fff8f8f8
Token_Text_Muted=ffaeaeae
Token_Text_Subtle=ff595959
Token_Stroke_Strong=ffeeeeee
Token_Stroke_Muted=ff727272
Token_Stroke_Subtle=ff3a3a3a
Token_Notification_Alert=ffc98014
Token_Notification_Success=ff1f9b5d
Token_Notification_Neutral=ff016876
Token_Notification_Danger=ffb22245

View File

@@ -1,4 +1,5 @@
[General]
Includes=light.figmatokens
ThemeName=Classic
PreferredStyles=
@@ -398,39 +399,6 @@ Debugger_WatchItem_ValueChanged=ffc80000
Debugger_Breakpoint_TextMarkColor=ffff4040
; Qt Creator Color Tokens - light mode
Token_Basic_Black=ff131313
Token_Basic_White=fff2f2f2
Token_Accent_Default=ff23b26a
Token_Accent_Muted=ff1f9b5d
Token_Accent_Subtle=ff1a8550
Token_Background_Default=ffe3e3e3
Token_Background_Muted=ffeeeeee
Token_Background_Subtle=fffbfbfb
Token_Foreground_Default=ffcdcdcd
Token_Foreground_Muted=ffd5d5d5
Token_Foreground_Subtle=ffdddddd
Token_Text_Default=ff393939
Token_Text_Muted=ff7c7c7c
Token_Text_Subtle=ffbebebe
Token_Stroke_Strong=ff464646
Token_Stroke_Muted=ff727272
Token_Stroke_Subtle=ffcdcdcd
Token_Notification_Alert=ffeb991f
Token_Notification_Success=ff23b26a
Token_Notification_Neutral=ff0e7887
Token_Notification_Danger=ffdc1343
Welcome_TextColor=ff000000
Welcome_ForegroundPrimaryColor=shadowBackground
Welcome_ForegroundSecondaryColor=ff939393
Welcome_BackgroundPrimaryColor=fffafafa
Welcome_BackgroundSecondaryColor=ffffffff
Welcome_HoverColor=ffefefef
Welcome_AccentColor=ff45ce55
Welcome_LinkColor=ff20a020
Welcome_DisabledLinkColor=textDisabled
Timeline_TextColor=darkText
Timeline_BackgroundColor1=ffffffff
Timeline_BackgroundColor2=fff6f6f6

View File

@@ -1,4 +1,5 @@
[General]
Includes=light.figmatokens
ThemeName=Design Light
PreferredStyles=
@@ -410,39 +411,6 @@ Debugger_WatchItem_ValueChanged=ffbf0303
Debugger_Breakpoint_TextMarkColor=ffff4040
; Qt Creator Color Tokens - light mode
Token_Basic_Black=ff131313
Token_Basic_White=fff2f2f2
Token_Accent_Default=ff23b26a
Token_Accent_Muted=ff1f9b5d
Token_Accent_Subtle=ff1a8550
Token_Background_Default=ffe3e3e3
Token_Background_Muted=ffeeeeee
Token_Background_Subtle=fffbfbfb
Token_Foreground_Default=ffcdcdcd
Token_Foreground_Muted=ffd5d5d5
Token_Foreground_Subtle=ffdddddd
Token_Text_Default=ff393939
Token_Text_Muted=ff7c7c7c
Token_Text_Subtle=ffbebebe
Token_Stroke_Strong=ff464646
Token_Stroke_Muted=ff727272
Token_Stroke_Subtle=ffcdcdcd
Token_Notification_Alert=ffeb991f
Token_Notification_Success=ff23b26a
Token_Notification_Neutral=ff0e7887
Token_Notification_Danger=ffdc1343
Welcome_TextColor=ff000000
Welcome_ForegroundPrimaryColor=ff404040
Welcome_ForegroundSecondaryColor=ff727272
Welcome_BackgroundPrimaryColor=ffeaeaea
Welcome_BackgroundSecondaryColor=ffefefef
Welcome_HoverColor=ffe1e1e1
Welcome_AccentColor=ff25709a
Welcome_LinkColor=ff104090
Welcome_DisabledLinkColor=textDisabled
Timeline_TextColor=text
Timeline_BackgroundColor1=normalBackground
Timeline_BackgroundColor2=fff6f6f6

View File

@@ -1,4 +1,5 @@
[General]
Includes=dark.figmatokens
ThemeName=Design Dark
PreferredStyles=
DefaultTextEditorColorScheme=creator-dark.xml
@@ -414,39 +415,6 @@ Debugger_WatchItem_ValueChanged=ffff6666
Debugger_Breakpoint_TextMarkColor=ffff4040
; Qt Creator Color Tokens - dark mode
Token_Basic_Black=ff131313
Token_Basic_White=fff8f8f8
Token_Accent_Default=ff23b26a
Token_Accent_Muted=ff1f9b5d
Token_Accent_Subtle=ff1a8550
Token_Background_Default=ff1f1f1f
Token_Background_Muted=ff262626
Token_Background_Subtle=ff2e2e2e
Token_Foreground_Default=ff5a5a5a
Token_Foreground_Muted=ff3e3e3e
Token_Foreground_Subtle=ff303030
Token_Text_Default=fff8f8f8
Token_Text_Muted=ffaeaeae
Token_Text_Subtle=ff595959
Token_Stroke_Strong=ffeeeeee
Token_Stroke_Muted=ff727272
Token_Stroke_Subtle=ff3a3a3a
Token_Notification_Alert=ffc98014
Token_Notification_Success=ff1f9b5d
Token_Notification_Neutral=ff016876
Token_Notification_Danger=ffb22245
Welcome_TextColor=text
Welcome_ForegroundPrimaryColor=ffa3a3a3
Welcome_ForegroundSecondaryColor=ff808080
Welcome_BackgroundPrimaryColor=ff242424
Welcome_BackgroundSecondaryColor=ff1c1c1c
Welcome_HoverColor=ff2b2a2a
Welcome_AccentColor=ff3f8ccc
Welcome_LinkColor=ff5fafef
Welcome_DisabledLinkColor=textDisabled
Timeline_TextColor=text
Timeline_BackgroundColor1=normalBackground
Timeline_BackgroundColor2=ff444444

View File

@@ -1,4 +1,5 @@
[General]
Includes=dark.figmatokens
ThemeName=Flat Dark
PreferredStyles=
DefaultTextEditorColorScheme=creator-dark.xml
@@ -410,39 +411,6 @@ Debugger_WatchItem_ValueChanged=ffff6666
Debugger_Breakpoint_TextMarkColor=ffff4040
; Qt Creator Color Tokens - dark mode
Token_Basic_Black=ff131313
Token_Basic_White=fff8f8f8
Token_Accent_Default=ff23b26a
Token_Accent_Muted=ff1f9b5d
Token_Accent_Subtle=ff1a8550
Token_Background_Default=ff1f1f1f
Token_Background_Muted=ff262626
Token_Background_Subtle=ff2e2e2e
Token_Foreground_Default=ff5a5a5a
Token_Foreground_Muted=ff3e3e3e
Token_Foreground_Subtle=ff303030
Token_Text_Default=fff8f8f8
Token_Text_Muted=ffaeaeae
Token_Text_Subtle=ff595959
Token_Stroke_Strong=ffeeeeee
Token_Stroke_Muted=ff727272
Token_Stroke_Subtle=ff3a3a3a
Token_Notification_Alert=ffc98014
Token_Notification_Success=ff1f9b5d
Token_Notification_Neutral=ff016876
Token_Notification_Danger=ffb22245
Welcome_TextColor=text
Welcome_ForegroundPrimaryColor=ff999999
Welcome_ForegroundSecondaryColor=ff808080
Welcome_BackgroundPrimaryColor=normalBackground
Welcome_BackgroundSecondaryColor=ff242628
Welcome_HoverColor=ff404243
Welcome_AccentColor=ff36c148
Welcome_LinkColor=ff5fcf4f
Welcome_DisabledLinkColor=textDisabled
Timeline_TextColor=text
Timeline_BackgroundColor1=normalBackground
Timeline_BackgroundColor2=ff444444

View File

@@ -1,4 +1,5 @@
[General]
Includes=light.figmatokens
ThemeName=Flat Light
PreferredStyles=
@@ -407,39 +408,6 @@ Debugger_WatchItem_ValueChanged=ffbf0303
Debugger_Breakpoint_TextMarkColor=ffff4040
; Qt Creator Color Tokens - light mode
Token_Basic_Black=ff131313
Token_Basic_White=fff2f2f2
Token_Accent_Default=ff23b26a
Token_Accent_Muted=ff1f9b5d
Token_Accent_Subtle=ff1a8550
Token_Background_Default=ffe3e3e3
Token_Background_Muted=ffeeeeee
Token_Background_Subtle=fffbfbfb
Token_Foreground_Default=ffcdcdcd
Token_Foreground_Muted=ffd5d5d5
Token_Foreground_Subtle=ffdddddd
Token_Text_Default=ff393939
Token_Text_Muted=ff7c7c7c
Token_Text_Subtle=ffbebebe
Token_Stroke_Strong=ff464646
Token_Stroke_Muted=ff727272
Token_Stroke_Subtle=ffcdcdcd
Token_Notification_Alert=ffeb991f
Token_Notification_Success=ff23b26a
Token_Notification_Neutral=ff0e7887
Token_Notification_Danger=ffdc1343
Welcome_TextColor=ff000000
Welcome_ForegroundPrimaryColor=ff232323
Welcome_ForegroundSecondaryColor=ff939393
Welcome_BackgroundPrimaryColor=fffafafa
Welcome_BackgroundSecondaryColor=ffffffff
Welcome_HoverColor=ffefefef
Welcome_AccentColor=ff45ce55
Welcome_LinkColor=ff20a020
Welcome_DisabledLinkColor=textDisabled
Timeline_TextColor=text
Timeline_BackgroundColor1=normalBackground
Timeline_BackgroundColor2=fff6f6f6

View File

@@ -1,4 +1,5 @@
[General]
Includes=light.figmatokens
ThemeName=Flat
PreferredStyles=
@@ -405,39 +406,6 @@ Debugger_WatchItem_ValueChanged=ffbf0303
Debugger_Breakpoint_TextMarkColor=ffff4040
; Qt Creator Color Tokens - light mode
Token_Basic_Black=ff131313
Token_Basic_White=fff2f2f2
Token_Accent_Default=ff23b26a
Token_Accent_Muted=ff1f9b5d
Token_Accent_Subtle=ff1a8550
Token_Background_Default=ffe3e3e3
Token_Background_Muted=ffeeeeee
Token_Background_Subtle=fffbfbfb
Token_Foreground_Default=ffcdcdcd
Token_Foreground_Muted=ffd5d5d5
Token_Foreground_Subtle=ffdddddd
Token_Text_Default=ff393939
Token_Text_Muted=ff7c7c7c
Token_Text_Subtle=ffbebebe
Token_Stroke_Strong=ff464646
Token_Stroke_Muted=ff727272
Token_Stroke_Subtle=ffcdcdcd
Token_Notification_Alert=ffeb991f
Token_Notification_Success=ff23b26a
Token_Notification_Neutral=ff0e7887
Token_Notification_Danger=ffdc1343
Welcome_TextColor=ff000000
Welcome_ForegroundPrimaryColor=shadowBackground
Welcome_ForegroundSecondaryColor=ff939393
Welcome_BackgroundPrimaryColor=fffafafa
Welcome_BackgroundSecondaryColor=ffffffff
Welcome_HoverColor=ffefefef
Welcome_AccentColor=ff45ce55
Welcome_LinkColor=ff20a020
Welcome_DisabledLinkColor=textDisabled
Timeline_TextColor=text
Timeline_BackgroundColor1=normalBackground
Timeline_BackgroundColor2=fff6f6f6

View File

@@ -0,0 +1,31 @@
; Qt Creator Color Tokens - light mode
[Colors]
Token_Basic_Black=ff131313
Token_Basic_White=fff2f2f2
Token_Accent_Default=ff23b26a
Token_Accent_Muted=ff1f9b5d
Token_Accent_Subtle=ff1a8550
Token_Background_Default=fffcfcfc
Token_Background_Muted=ffefefef
Token_Background_Subtle=ffe7e7e7
Token_Foreground_Default=ffcdcdcd
Token_Foreground_Muted=ffd5d5d5
Token_Foreground_Subtle=ffdddddd
Token_Text_Default=ff393939
Token_Text_Muted=ff6a6a6a
Token_Text_Subtle=ffbebebe
Token_Stroke_Strong=ff464646
Token_Stroke_Muted=ff727272
Token_Stroke_Subtle=ffcdcdcd
Token_Notification_Alert=ffeb991f
Token_Notification_Success=ff23b26a
Token_Notification_Neutral=ff0e7887
Token_Notification_Danger=ffdc1343

View File

@@ -121,6 +121,9 @@ static std::string stripPointerType(const std::string &typeNameIn)
std::string typeName = typeNameIn;
if (typeName.back() == '*') {
typeName.pop_back();
trimBack(typeName);
if (endsWith(typeName, "const"))
typeName = typeName.erase(typeName.size() - 5, 5);
} else {
const auto arrayPosition = typeName.find_first_of('[');
if (arrayPosition != std::string::npos
@@ -296,23 +299,7 @@ int PyType::code() const
return std::nullopt;
};
if (!resolve())
return parseTypeName(name()).value_or(TypeCodeUnresolvable);
if (m_tag < 0) {
if (const std::optional<TypeCodes> typeCode = parseTypeName(name()))
return *typeCode;
IDebugSymbolGroup2 *sg = 0;
if (FAILED(ExtensionCommandContext::instance()->symbols()->CreateSymbolGroup2(&sg)))
return TypeCodeStruct;
const std::string helperValueName = SymbolGroupValue::pointedToSymbolName(0, name(true));
ULONG index = DEBUG_ANY_ID;
if (SUCCEEDED(sg->AddSymbol(helperValueName.c_str(), &index)))
m_tag = PyValue(index, sg).tag();
sg->Release();
}
if (m_tag >= 0) {
switch (m_tag) {
case SymTagUDT: return TypeCodeStruct;
case SymTagEnum: return TypeCodeEnum;
@@ -323,8 +310,8 @@ int PyType::code() const
case SymTagBaseType: return isIntegralType(name()) ? TypeCodeIntegral : TypeCodeFloat;
default: break;
}
return TypeCodeStruct;
}
return parseTypeName(name()).value_or(TypeCodeStruct);
}
PyType PyType::target() const
@@ -533,6 +520,7 @@ PY_FUNC_RET_OBJECT_LIST(fields, PY_OBJ_NAME)
PY_FUNC_RET_STD_STRING(module, PY_OBJ_NAME)
PY_FUNC(moduleId, PY_OBJ_NAME, "K")
PY_FUNC(arrayElements, PY_OBJ_NAME, "k")
PY_FUNC_RET_BOOL(resolved, PY_OBJ_NAME)
PY_FUNC_DECL(templateArguments, PY_OBJ_NAME)
{
PY_IMPL_GUARD;
@@ -568,6 +556,8 @@ static PyMethodDef typeMethods[] = {
"Returns the number of elements in an array or 0 for non array types"},
{"templateArguments", PyCFunction(templateArguments), METH_NOARGS,
"Returns all template arguments."},
{"resolved", PyCFunction(resolved), METH_NOARGS,
"Returns whether the type is resolved"},
{NULL} /* Sentinel */
};

View File

@@ -29,6 +29,7 @@ public:
std::string module() const;
ULONG64 moduleId() const;
int arrayElements() const;
bool resolved() const { return m_resolved.value_or(false); }
struct TemplateArgument
{

View File

@@ -933,7 +933,17 @@ QColor StyleHelper::ensureReadableOn(const QColor &background, const QColor &des
return foreground;
}
static QStringList brandFontFamilies()
static const QStringList &applicationFontFamilies()
{
const static QStringList families = [] {
const QLatin1String familyName("Inter");
// Font is either installed in the system, or was loaded from share/qtcreator/fonts/
return QFontDatabase::hasFamily(familyName) ? QStringList(familyName) : QStringList();
}();
return families;
}
static const QStringList &brandFontFamilies()
{
const static QStringList families = []{
const int id = QFontDatabase::addApplicationFont(":/studiofonts/TitilliumWeb-Regular.ttf");
@@ -959,10 +969,14 @@ static const UiFontMetrics& uiFontMetrics(StyleHelper::UiElement element)
{StyleHelper::UiElementH5, {14, 16, QFont::DemiBold}},
{StyleHelper::UiElementH6, {12, 14, QFont::DemiBold}},
{StyleHelper::UiElementH6Capital, {12, 14, QFont::DemiBold}},
{StyleHelper::UiElementBody1, {14, 20, QFont::Light}},
{StyleHelper::UiElementBody2, {12, 20, QFont::Light}},
{StyleHelper::UiElementButtonMedium, {12, 16, QFont::Bold}},
{StyleHelper::UiElementButtonSmall, {10, 12, QFont::Bold}},
{StyleHelper::UiElementCaptionStrong, {10, 12, QFont::DemiBold}},
{StyleHelper::UiElementCaption, {10, 12, QFont::Normal}},
{StyleHelper::UIElementIconStandard, {12, 16, QFont::Normal}},
{StyleHelper::UIElementIconActive, {12, 16, QFont::DemiBold}},
{StyleHelper::UiElementIconStandard, {12, 16, QFont::Medium}},
{StyleHelper::UiElementIconActive, {12, 16, QFont::DemiBold}},
};
QTC_ASSERT(metrics.count(element) > 0, return metrics.at(StyleHelper::UiElementCaptionStrong));
return metrics.at(element);
@@ -983,8 +997,10 @@ QFont StyleHelper::uiFont(UiElement element)
case UiElementH3:
case UiElementH6Capital:
font.setCapitalization(QFont::AllUppercase);
break;
[[fallthrough]];
default:
if (!applicationFontFamilies().isEmpty())
font.setFamilies(applicationFontFamilies());
break;
}

View File

@@ -42,15 +42,15 @@ constexpr char C_TOOLBAR_ACTIONWIDGET[] = "toolbar_actionWidget";
constexpr char C_QT_SCALE_FACTOR_ROUNDING_POLICY[] = "QT_SCALE_FACTOR_ROUNDING_POLICY";
namespace SpacingTokens {
constexpr int VPaddingXXS = 4; // Top and bottom padding within the component
constexpr int HPaddingXXS = 4; // Left and right padding within the component
constexpr int VGapXXS = 4; // Vertical Space between TEXT LINE within the Component
constexpr int HGapXXS = 4; // Horizontal Space between elements within the Component
constexpr int VPaddingXxs = 4; // Top and bottom padding within the component
constexpr int HPaddingXxs = 4; // Left and right padding within the component
constexpr int VGapXxs = 4; // Vertical Space between TEXT LINE within the Component
constexpr int HGapXxs = 4; // Horizontal Space between elements within the Component
constexpr int VPaddingXS = 8;
constexpr int HPaddingXS = 8;
constexpr int VGapXS = 4;
constexpr int HGapXS = 8;
constexpr int VPaddingXs = 8;
constexpr int HPaddingXs = 8;
constexpr int VGapXs = 4;
constexpr int HGapXs = 8;
constexpr int VPaddingS = 8;
constexpr int HPaddingS = 16;
@@ -62,10 +62,15 @@ namespace SpacingTokens {
constexpr int VGapM = 4;
constexpr int HGapM = 16;
constexpr int VPaddingL = 12;
constexpr int VPaddingL = 16;
constexpr int HPaddingL = 24;
constexpr int VGapL = 8;
constexpr int HGapL = 16;
constexpr int ExPaddingGapS = 2;
constexpr int ExPaddingGapM = 6;
constexpr int ExPaddingGapL = 12;
constexpr int ExVPaddingGapXl = 24;
}
enum ToolbarStyle {
@@ -82,10 +87,14 @@ enum UiElement {
UiElementH5,
UiElementH6,
UiElementH6Capital,
UiElementBody1,
UiElementBody2,
UiElementButtonMedium,
UiElementButtonSmall,
UiElementCaptionStrong,
UiElementCaption,
UIElementIconStandard,
UIElementIconActive,
UiElementIconStandard,
UiElementIconActive,
};
// Height of the project explorer navigation bar

View File

@@ -245,7 +245,6 @@ void Theme::readSettingsInternal(QSettings &settings)
QMetaEnum e = m.enumerator(m.indexOfEnumerator("Flag"));
for (int i = 0, total = e.keyCount(); i < total; ++i) {
const QString key = QLatin1String(e.key(i));
QTC_ASSERT(settings.contains(key), return );
d->flags[i] = settings.value(key).toBool();
}
settings.endGroup();

View File

@@ -247,18 +247,6 @@ public:
Token_Notification_Neutral,
Token_Notification_Danger,
/* Welcome Plugin */
Welcome_TextColor,
Welcome_ForegroundPrimaryColor,
Welcome_ForegroundSecondaryColor,
Welcome_BackgroundPrimaryColor,
Welcome_BackgroundSecondaryColor,
Welcome_HoverColor,
Welcome_AccentColor,
Welcome_LinkColor,
Welcome_DisabledLinkColor,
/* Timeline Library */
Timeline_TextColor,
Timeline_BackgroundColor1,

View File

@@ -819,15 +819,13 @@ void AndroidRunnerWorker::removeForwardPort(const QString &port)
void AndroidRunnerWorker::onProcessIdChanged(PidUserPair pidUser)
{
qint64 pid = pidUser.first;
qint64 user = pidUser.second;
// Don't write to m_psProc from a different thread
QTC_ASSERT(QThread::currentThread() == thread(), return);
qCDebug(androidRunWorkerLog) << "Process ID changed from:" << m_processPID
<< "to:" << pid;
m_processPID = pid;
m_processUser = user;
if (pid == -1) {
<< "to:" << pidUser.first;
m_processPID = pidUser.first;
m_processUser = pidUser.second;
if (m_processPID == -1) {
emit remoteProcessFinished(QLatin1String("\n\n") + Tr::tr("\"%1\" died.")
.arg(m_packageName));
// App died/killed. Reset log, monitor, jdb & gdbserver/lldb-server processes.
@@ -852,7 +850,10 @@ void AndroidRunnerWorker::onProcessIdChanged(PidUserPair pidUser)
QTC_ASSERT(m_psIsAlive, return);
m_psIsAlive->setObjectName("IsAliveProcess");
m_psIsAlive->setProcessChannelMode(QProcess::MergedChannels);
connect(m_psIsAlive.get(), &Process::done, this, [this] { onProcessIdChanged({-1, -1}); });
connect(m_psIsAlive.get(), &Process::done, this, [this] {
m_psIsAlive.release()->deleteLater();
onProcessIdChanged({-1, -1});
});
}
}

View File

@@ -13,14 +13,15 @@
#include <projectexplorer/projectexplorerconstants.h>
#include <utils/async.h>
#include <utils/detailswidget.h>
#include <utils/hostosinfo.h>
#include <utils/infolabel.h>
#include <utils/layoutbuilder.h>
#include <utils/pathchooser.h>
#include <utils/progressindicator.h>
#include <utils/qtcprocess.h>
#include <utils/qtcassert.h>
#include <utils/qtcprocess.h>
#include <utils/utilsicons.h>
#include <QCheckBox>
@@ -203,6 +204,56 @@ enum OpenSslValidation {
OpenSslCmakeListsPathExists
};
static expected_str<void> testJavaC(const FilePath &jdkPath)
{
if (!jdkPath.isReadableDir())
return make_unexpected(Tr::tr("The selected path does not exist or is not readable."));
const QString javacCommand("javac");
const QString versionParameter("-version");
constexpr int requiredMajorVersion = 17;
const FilePath bin = jdkPath / "bin" / (javacCommand + QTC_HOST_EXE_SUFFIX);
if (!bin.isExecutableFile())
return make_unexpected(
Tr::tr("Could not find \"%1\" in the selected path.")
.arg(bin.toUserOutput()));
QVersionNumber jdkVersion;
Process javacProcess;
const CommandLine cmd(bin, {versionParameter});
javacProcess.setProcessChannelMode(QProcess::ProcessChannelMode::MergedChannels);
javacProcess.setCommand(cmd);
javacProcess.runBlocking();
const QString stdOut = javacProcess.stdOut().trimmed();
if (javacProcess.exitCode() != 0)
return make_unexpected(
Tr::tr("The selected path does not contain a valid JDK. (%1 failed: %2)")
.arg(cmd.toUserOutput())
.arg(stdOut));
// We expect "javac <version>" where <version> is "major.minor.patch"
const QString outputPrefix = javacCommand + " ";
if (!stdOut.startsWith(outputPrefix))
return make_unexpected(Tr::tr("Unexpected output from \"%1\": %2")
.arg(cmd.toUserOutput())
.arg(stdOut));
jdkVersion = QVersionNumber::fromString(stdOut.mid(outputPrefix.length()).split('\n').first());
if (jdkVersion.isNull() || jdkVersion.majorVersion() != requiredMajorVersion) {
return make_unexpected(Tr::tr("Unsupported JDK version (needs to be %1): %2 (parsed: %3)")
.arg(requiredMajorVersion)
.arg(stdOut)
.arg(jdkVersion.toString()));
}
return {};
}
AndroidSettingsWidget::AndroidSettingsWidget()
{
setWindowTitle(Tr::tr("Android Configuration"));
@@ -307,6 +358,15 @@ AndroidSettingsWidget::AndroidSettingsWidget()
Tr::tr("OpenSSL settings have errors."),
openSslDetailsWidget);
m_openJdkLocationPathChooser->setValidationFunction([](const QString &s) {
return Utils::asyncRun([s]() -> expected_str<QString> {
expected_str<void> test = testJavaC(FilePath::fromUserInput(s));
if (!test)
return make_unexpected(test.error());
return s;
});
});
connect(m_openJdkLocationPathChooser, &PathChooser::rawPathChanged,
this, &AndroidSettingsWidget::validateJdk);
if (androidConfig().openJDKLocation().isEmpty())
@@ -533,10 +593,9 @@ bool AndroidSettingsWidget::isDefaultNdkSelected() const
void AndroidSettingsWidget::validateJdk()
{
androidConfig().setOpenJDKLocation(m_openJdkLocationPathChooser->filePath());
bool jdkPathExists = androidConfig().openJDKLocation().exists();
const FilePath bin = androidConfig().openJDKLocation()
.pathAppended("bin/javac" QTC_HOST_EXE_SUFFIX);
m_androidSummary->setPointValid(JavaPathExistsAndWritableRow, jdkPathExists && bin.exists());
expected_str<void> test = testJavaC(androidConfig().openJDKLocation());
m_androidSummary->setPointValid(JavaPathExistsAndWritableRow, test.has_value());
updateUI();

View File

@@ -79,14 +79,16 @@ void CatchCodeParser::handleIdentifier()
|| unprefixed == "TEMPLATE_PRODUCT_TEST_CASE_SIG") {
handleParameterizedTestCase(false);
} else if (unprefixed == "TEST_CASE_METHOD") {
handleFixtureOrRegisteredTestCase(true);
handleFixtureOrRegisteredTestCase(/*fixture=*/true, /*scenario=*/false);
} else if (unprefixed == "SCENARIO_METHOD") {
handleFixtureOrRegisteredTestCase(/*fixture=*/true, /*scenario=*/true);
} else if (unprefixed == "TEMPLATE_TEST_CASE_METHOD_SIG"
|| unprefixed == "TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG"
|| unprefixed == "TEMPLATE_TEST_CASE_METHOD"
|| unprefixed == "TEMPLATE_LIST_TEST_CASE_METHOD") {
handleParameterizedTestCase(true);
} else if (unprefixed == "METHOD_AS_TEST_CASE" || unprefixed == "REGISTER_TEST_CASE") {
handleFixtureOrRegisteredTestCase(false);
handleFixtureOrRegisteredTestCase(/*fixture=*/false, /*scenario=*/false);
}
}
@@ -124,7 +126,7 @@ void CatchCodeParser::handleParameterizedTestCase(bool isFixture)
if (!skipCommentsUntil(T_LPAREN))
return;
if (isFixture && !skipFixtureParameter())
if (isFixture && !skipParameter())
return;
CatchTestCodeLocationAndType locationAndType
@@ -154,18 +156,13 @@ void CatchCodeParser::handleParameterizedTestCase(bool isFixture)
m_testCases.append(locationAndType);
}
void CatchCodeParser::handleFixtureOrRegisteredTestCase(bool isFixture)
void CatchCodeParser::handleFixtureOrRegisteredTestCase(bool isFixture, bool isScenario)
{
if (!skipCommentsUntil(T_LPAREN))
return;
if (isFixture) {
if (!skipFixtureParameter())
if (!skipParameter())
return;
} else {
if (!skipFunctionParameter())
return;
}
CatchTestCodeLocationAndType locationAndType
= locationAndTypeFromToken(m_tokens.at(m_currentIndex));
@@ -183,6 +180,9 @@ void CatchCodeParser::handleFixtureOrRegisteredTestCase(bool isFixture)
if (stoppedAt != T_RPAREN)
return;
if (isScenario)
testCaseName.prepend("Scenario: "); // use a flag?
locationAndType.m_name = testCaseName;
locationAndType.tags = parseTags(tagsString);
if (isFixture)
@@ -245,19 +245,12 @@ Kind CatchCodeParser::skipUntilCorrespondingRParen()
return T_ERROR;
}
bool CatchCodeParser::skipFixtureParameter()
{
if (!skipCommentsUntil(T_IDENTIFIER))
return false;
return skipCommentsUntil(T_COMMA);
}
bool CatchCodeParser::skipFunctionParameter()
bool CatchCodeParser::skipParameter()
{
if (!skipCommentsUntil(T_IDENTIFIER))
return false;
if (skipCommentsUntil(T_COLON_COLON))
return skipFunctionParameter();
return skipParameter();
return skipCommentsUntil(T_COMMA);
}

View File

@@ -22,13 +22,12 @@ private:
void handleIdentifier();
void handleTestCase(bool isScenario);
void handleParameterizedTestCase(bool isFixture);
void handleFixtureOrRegisteredTestCase(bool isFixture);
void handleFixtureOrRegisteredTestCase(bool isFixture, bool isScenario);
QString getStringLiteral(CPlusPlus::Kind &stoppedAtKind);
bool skipCommentsUntil(CPlusPlus::Kind nextExpectedKind); // moves currentIndex if succeeds
CPlusPlus::Kind skipUntilCorrespondingRParen(); // moves currentIndex
bool skipFixtureParameter();
bool skipFunctionParameter();
bool skipParameter();
const QByteArray &m_source;
const CPlusPlus::LanguageFeatures &m_features;

View File

@@ -29,7 +29,9 @@ static bool isCatchTestCaseMacro(const QString &macroName)
QStringLiteral("TEMPLATE_TEST_CASE_SIG"), QStringLiteral("TEMPLATE_PRODUCT_TEST_CASE_SIG"),
QStringLiteral("TEST_CASE_METHOD"), QStringLiteral("TEMPLATE_TEST_CASE_METHOD"),
QStringLiteral("TEMPLATE_PRODUCT_TEST_CASE_METHOD"),
QStringLiteral("TEST_CASE_METHOD"), QStringLiteral("TEMPLATE_TEST_CASE_METHOD_SIG"),
QStringLiteral("TEST_CASE_METHOD"),
QStringLiteral("SCENARIO_METHOD"),
QStringLiteral("TEMPLATE_TEST_CASE_METHOD_SIG"),
QStringLiteral("TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG"),
QStringLiteral("TEMPLATE_TEST_CASE_METHOD"),
QStringLiteral("TEMPLATE_LIST_TEST_CASE_METHOD"),
@@ -105,7 +107,7 @@ bool CatchTestParser::processDocument(QPromise<TestParseResultPtr> &promise,
if (!hasCatchNames(doc)) {
static const QRegularExpression regex("\\b(CATCH_)?"
"(SCENARIO|(TEMPLATE_(PRODUCT_)?)?TEST_CASE(_METHOD)?|"
"(SCENARIO(_METHOD)?|(TEMPLATE_(PRODUCT_)?)?TEST_CASE(_METHOD)?|"
"TEMPLATE_TEST_CASE(_METHOD)?_SIG|"
"TEMPLATE_PRODUCT_TEST_CASE(_METHOD)?_SIG|"
"TEMPLATE_LIST_TEST_CASE_METHOD|METHOD_AS_TEST_CASE|"

View File

@@ -55,6 +55,7 @@ TestCodeParser::TestCodeParser()
m_qmlEditorRev.remove(filePath);
});
m_reparseTimer.setSingleShot(true);
m_reparseTimer.setInterval(1000);
connect(&m_reparseTimer, &QTimer::timeout, this, &TestCodeParser::parsePostponedFiles);
connect(&m_taskTreeRunner, &TaskTreeRunner::aboutToStart, this, [this](TaskTree *taskTree) {
if (m_withTaskProgress) {
@@ -238,27 +239,10 @@ bool TestCodeParser::postponed(const QSet<FilePath> &filePaths)
if (filePaths.size() == 1) {
if (m_reparseTimerTimedOut)
return false;
const FilePath filePath = *filePaths.begin();
switch (m_postponedFiles.size()) {
case 0:
m_postponedFiles.insert(filePath);
m_reparseTimer.setInterval(1000);
m_postponedFiles.insert(*filePaths.begin());
m_reparseTimer.start();
return true;
case 1:
if (m_postponedFiles.contains(filePath)) {
m_reparseTimer.start();
return true;
}
Q_FALLTHROUGH();
default:
m_postponedFiles.insert(filePath);
m_reparseTimer.stop();
m_reparseTimer.setInterval(0);
m_reparseTimerTimedOut = false;
m_reparseTimer.start();
return true;
}
}
return false;
case PartialParse:
@@ -377,7 +361,7 @@ void TestCodeParser::scanForTests(const QSet<FilePath> &filePaths,
return true;
return cppSnapshot.contains(fn);
});
m_withTaskProgress = filteredFiles.size() > 5;
m_withTaskProgress = isFullParse || filteredFiles.size() > 20;
qCDebug(LOG) << "Starting scan of" << filteredFiles.size() << "(" << files.size() << ")"
<< "files with" << codeParsers.size() << "parsers";

View File

@@ -2,18 +2,18 @@
<qresource prefix="/axivion">
<file>images/axivion.png</file>
<file>images/axivion@2x.png</file>
<file>images/button-av.png</file>
<file>images/button-av@2x.png</file>
<file>images/button-cl.png</file>
<file>images/button-cl@2x.png</file>
<file>images/button-cy.png</file>
<file>images/button-cy@2x.png</file>
<file>images/button-de.png</file>
<file>images/button-de@2x.png</file>
<file>images/button-mv.png</file>
<file>images/button-mv@2x.png</file>
<file>images/button-sv.png</file>
<file>images/button-sv@2x.png</file>
<file>images/button-AV.png</file>
<file>images/button-AV@2x.png</file>
<file>images/button-CL.png</file>
<file>images/button-CL@2x.png</file>
<file>images/button-CY.png</file>
<file>images/button-CY@2x.png</file>
<file>images/button-DE.png</file>
<file>images/button-DE@2x.png</file>
<file>images/button-MV.png</file>
<file>images/button-MV@2x.png</file>
<file>images/button-SV.png</file>
<file>images/button-SV@2x.png</file>
<file>images/sortAsc.png</file>
<file>images/sortAsc@2x.png</file>
<file>images/sortDesc.png</file>

View File

@@ -334,6 +334,8 @@ IssuesWidget::IssuesWidget(QWidget *parent)
connect(m_pathGlobFilter, &QLineEdit::textEdited, this, &IssuesWidget::onSearchParameterChanged);
m_issuesView = new BaseTreeView(this);
m_issuesView->setFrameShape(QFrame::StyledPanel); // Bring back Qt default
m_issuesView->setFrameShadow(QFrame::Sunken); // Bring back Qt default
m_headerView = new IssueHeaderView(this);
connect(m_headerView, &IssueHeaderView::sortTriggered,
this, &IssuesWidget::onSearchParameterChanged);
@@ -562,7 +564,7 @@ void IssuesWidget::updateBasicProjectInfo(std::optional<Dto::ProjectInfoDto> inf
int buttonId = 0;
for (const Dto::IssueKindInfoDto &kind : issueKinds) {
auto button = new QToolButton(this);
button->setIcon(iconForIssue(kind.prefix));
button->setIcon(iconForIssue(kind.getOptionalPrefixEnum()));
button->setToolTip(kind.nicePluralName);
button->setCheckable(true);
connect(button, &QToolButton::clicked, this, [this, prefix = kind.prefix]{

View File

@@ -63,18 +63,21 @@ using namespace Utils;
namespace Axivion::Internal {
QIcon iconForIssue(const QString &prefix)
QIcon iconForIssue(const std::optional<Dto::IssueKind> &issueKind)
{
static QHash<QString, QIcon> prefixToIcon;
auto it = prefixToIcon.find(prefix);
if (!issueKind)
return {};
if (it == prefixToIcon.end()) {
Icon icon({{FilePath::fromString(":/axivion/images/button-" + prefix.toLower() + ".png"),
Theme::PaletteButtonText}},
Icon::Tint);
it = prefixToIcon.insert(prefix, icon.icon());
}
return it.value();
static QHash<Dto::IssueKind, QIcon> prefixToIcon;
auto it = prefixToIcon.constFind(*issueKind);
if (it != prefixToIcon.constEnd())
return *it;
const QLatin1String prefix = Dto::IssueKindMeta::enumToStr(*issueKind);
const Icon icon({{FilePath::fromString(":/axivion/images/button-" + prefix + ".png"),
Theme::PaletteButtonText}}, Icon::Tint);
return prefixToIcon.insert(*issueKind, icon.icon()).value();
}
QString anyToSimpleString(const Dto::Any &any)
@@ -122,11 +125,30 @@ static QString credentialKey()
QString escaped = string;
return escaped.replace('\\', "\\\\").replace('@', "\\@");
};
return escape(settings().server.dashboard) + '@' + escape(settings().server.username);
return escape(settings().server.username) + '@' + escape(settings().server.dashboard);
}
static DashboardInfo toDashboardInfo(const QUrl &source, const Dto::DashboardInfoDto &infoDto)
template <typename DtoType>
struct GetDtoStorage
{
QUrl url;
std::optional<QByteArray> credential;
std::optional<DtoType> dtoData;
};
template <typename DtoType>
struct PostDtoStorage
{
QUrl url;
std::optional<QByteArray> credential;
QByteArray csrfToken;
QByteArray writeData;
std::optional<DtoType> dtoData;
};
static DashboardInfo toDashboardInfo(const GetDtoStorage<Dto::DashboardInfoDto> &dashboardStorage)
{
const Dto::DashboardInfoDto &infoDto = *dashboardStorage.dtoData;
const QVersionNumber versionNumber = infoDto.dashboardVersionNumber
? QVersionNumber::fromString(*infoDto.dashboardVersionNumber) : QVersionNumber();
@@ -139,7 +161,7 @@ static DashboardInfo toDashboardInfo(const QUrl &source, const Dto::DashboardInf
projectUrls.insert(project.name, project.url);
}
}
return {source, versionNumber, projects, projectUrls, infoDto.checkCredentialsUrl};
return {dashboardStorage.url, versionNumber, projects, projectUrls, infoDto.checkCredentialsUrl};
}
QString IssueListSearch::toQuery() const
@@ -226,7 +248,7 @@ public:
const QString markText = issue.description;
const QString id = issue.kind + QString::number(issue.id.value_or(-1));
setToolTip(id + '\n' + markText);
setIcon(iconForIssue(issue.kind));
setIcon(iconForIssue(issue.getOptionalKindEnum()));
if (color)
setColor(*color);
setPriority(TextMark::NormalPriority);
@@ -323,18 +345,26 @@ void AxivionPluginPrivate::onStartupProjectChanged(Project *project)
static QUrl urlForProject(const QString &projectName)
{
return QUrl(settings().server.dashboard).resolved(QString("api/projects/")).resolved(projectName);
if (!dd->m_dashboardInfo)
return {};
return dd->m_dashboardInfo->source.resolved(QString("api/projects/")).resolved(projectName);
}
static constexpr int httpStatusCodeOk = 200;
constexpr char s_htmlContentType[] = "text/html";
constexpr char s_jsonContentType[] = "application/json";
static bool isServerAccessEstablished()
{
return dd->m_serverAccess == ServerAccess::NoAuthorization
|| (dd->m_serverAccess == ServerAccess::WithAuthorization && dd->m_apiToken);
}
static Group fetchHtmlRecipe(const QUrl &url, const std::function<void(const QByteArray &)> &handler)
{
// TODO: Refactor so that it's a common code with fetchDataRecipe().
const auto onQuerySetup = [url](NetworkQuery &query) {
if (dd->m_serverAccess == ServerAccess::Unknown)
if (!isServerAccessEstablished())
return SetupResult::StopWithError; // TODO: start authorizationRecipe()?
QNetworkRequest request(url);
@@ -367,24 +397,6 @@ static Group fetchHtmlRecipe(const QUrl &url, const std::function<void(const QBy
return {NetworkQueryTask(onQuerySetup, onQueryDone)};
}
template <typename DtoType>
struct GetDtoStorage
{
QUrl url;
std::optional<QByteArray> credential;
std::optional<DtoType> dtoData;
};
template <typename DtoType>
struct PostDtoStorage
{
QUrl url;
std::optional<QByteArray> credential;
QByteArray csrfToken;
QByteArray writeData;
std::optional<DtoType> dtoData;
};
template <typename DtoType, template <typename> typename DtoStorageType>
static Group dtoRecipe(const Storage<DtoStorageType<DtoType>> &dtoStorage)
{
@@ -410,7 +422,8 @@ static Group dtoRecipe(const Storage<DtoStorageType<DtoType>> &dtoStorage)
query.setNetworkAccessManager(&dd->m_networkAccessManager);
};
const auto onNetworkQueryDone = [storage](const NetworkQuery &query, DoneWith doneWith) {
const auto onNetworkQueryDone = [storage, dtoStorage](const NetworkQuery &query,
DoneWith doneWith) {
QNetworkReply *reply = query.reply();
const QNetworkReply::NetworkError error = reply->error();
const int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
@@ -423,6 +436,7 @@ static Group dtoRecipe(const Storage<DtoStorageType<DtoType>> &dtoStorage)
if (doneWith == DoneWith::Success && statusCode == httpStatusCodeOk
&& contentType == s_jsonContentType) {
*storage = reply->readAll();
dtoStorage->url = reply->url();
return DoneResult::Success;
}
@@ -503,7 +517,7 @@ static Group authorizationRecipe()
{
const Storage<GetDtoStorage<Dto::DashboardInfoDto>> unauthorizedDashboardStorage;
const auto onUnauthorizedGroupSetup = [unauthorizedDashboardStorage] {
if (dd->m_serverAccess != ServerAccess::NoAuthorization)
if (isServerAccessEstablished())
return SetupResult::StopWithSuccess;
unauthorizedDashboardStorage->url = QUrl(settings().server.dashboard);
@@ -512,8 +526,7 @@ static Group authorizationRecipe()
const auto onUnauthorizedGroupDone = [unauthorizedDashboardStorage] {
if (unauthorizedDashboardStorage->dtoData) {
dd->m_serverAccess = ServerAccess::NoAuthorization;
dd->m_dashboardInfo = toDashboardInfo(settings().server.dashboard,
*unauthorizedDashboardStorage->dtoData);
dd->m_dashboardInfo = toDashboardInfo(*unauthorizedDashboardStorage);
} else {
dd->m_serverAccess = ServerAccess::WithAuthorization;
}
@@ -540,7 +553,7 @@ static Group authorizationRecipe()
const Storage<QString> passwordStorage;
const Storage<GetDtoStorage<Dto::DashboardInfoDto>> dashboardStorage;
const auto onDashboardGroupSetup = [passwordStorage, dashboardStorage] {
const auto onPasswordGroupSetup = [passwordStorage, dashboardStorage] {
if (dd->m_apiToken)
return SetupResult::StopWithSuccess;
@@ -563,8 +576,7 @@ static Group authorizationRecipe()
if (!dashboardStorage->dtoData)
return SetupResult::StopWithSuccess;
dd->m_dashboardInfo = toDashboardInfo(settings().server.dashboard,
*dashboardStorage->dtoData);
dd->m_dashboardInfo = toDashboardInfo(*dashboardStorage);
const Dto::DashboardInfoDto &dashboardDto = *dashboardStorage->dtoData;
if (!dashboardDto.userApiTokenUrl)
@@ -572,7 +584,7 @@ static Group authorizationRecipe()
apiTokenStorage->credential = dashboardStorage->credential;
apiTokenStorage->url
= QUrl(settings().server.dashboard).resolved(*dashboardDto.userApiTokenUrl);
= dd->m_dashboardInfo->source.resolved(*dashboardDto.userApiTokenUrl);
apiTokenStorage->csrfToken = dashboardDto.csrfToken.toUtf8();
const Dto::ApiTokenCreationRequestDto requestDto{*passwordStorage, "IdePlugin",
apiTokenDescription(), 0};
@@ -598,6 +610,29 @@ static Group authorizationRecipe()
return DoneResult::Success;
};
const auto onDashboardGroupSetup = [dashboardStorage] {
if (dd->m_dashboardInfo || dd->m_serverAccess != ServerAccess::WithAuthorization
|| !dd->m_apiToken) {
return SetupResult::StopWithSuccess; // Unauthorized access should have collect dashboard before
}
dashboardStorage->credential = "AxToken " + *dd->m_apiToken;
dashboardStorage->url = QUrl(settings().server.dashboard);
return SetupResult::Continue;
};
const auto onDeleteCredentialSetup = [dashboardStorage](CredentialQuery &credential) {
if (dashboardStorage->dtoData) {
dd->m_dashboardInfo = toDashboardInfo(*dashboardStorage);
return SetupResult::StopWithSuccess;
}
dd->m_apiToken = {};
MessageManager::writeFlashing(QString("Axivion: %1")
.arg(Tr::tr("The stored ApiToken is not valid anymore, removing it.")));
credential.setOperation(CredentialOperation::Delete);
credential.setService(s_axivionKeychainService);
credential.setKey(credentialKey());
return SetupResult::Continue;
};
return {
Group {
unauthorizedDashboardStorage,
@@ -611,7 +646,7 @@ static Group authorizationRecipe()
Group {
passwordStorage,
dashboardStorage,
onGroupSetup(onDashboardGroupSetup),
onGroupSetup(onPasswordGroupSetup),
Group { // GET DashboardInfoDto
finishAllAndSuccess,
dtoRecipe(dashboardStorage)
@@ -622,6 +657,13 @@ static Group authorizationRecipe()
dtoRecipe(apiTokenStorage),
CredentialQueryTask(onSetCredentialSetup, onSetCredentialDone, CallDoneIf::Error)
}
},
Group {
finishAllAndSuccess,
dashboardStorage,
onGroupSetup(onDashboardGroupSetup),
dtoRecipe(dashboardStorage),
CredentialQueryTask(onDeleteCredentialSetup)
}
}
};
@@ -633,9 +675,10 @@ static Group fetchDataRecipe(const QUrl &url, const std::function<void(const Dto
const Storage<GetDtoStorage<DtoType>> dtoStorage;
const auto onDtoSetup = [dtoStorage, url] {
if (!dd->m_apiToken)
if (!isServerAccessEstablished())
return SetupResult::StopWithError;
if (dd->m_serverAccess == ServerAccess::WithAuthorization && dd->m_apiToken)
dtoStorage->credential = "AxToken " + *dd->m_apiToken;
dtoStorage->url = url;
return SetupResult::Continue;
@@ -661,27 +704,22 @@ Group dashboardInfoRecipe(const DashboardInfoHandler &handler)
{
const auto onSetup = [handler] {
if (dd->m_dashboardInfo) {
if (handler)
handler(*dd->m_dashboardInfo);
return SetupResult::StopWithSuccess;
}
return SetupResult::Continue;
};
const auto onDone = [handler] {
if (handler)
handler(make_unexpected(QString("Error"))); // TODO: Collect error message in the storage.
};
const auto resultHandler = [handler](const Dto::DashboardInfoDto &data) {
dd->m_dashboardInfo = toDashboardInfo(settings().server.dashboard, data);
if (handler)
const auto onDone = [handler](DoneWith result) {
if (result == DoneWith::Success && dd->m_dashboardInfo)
handler(*dd->m_dashboardInfo);
else
handler(make_unexpected(QString("Error"))); // TODO: Collect error message in the storage.
};
const Group root {
onGroupSetup(onSetup), // Stops if cache exists.
fetchDataRecipe<Dto::DashboardInfoDto>(settings().server.dashboard, resultHandler),
onGroupDone(onDone, CallDoneIf::Error)
authorizationRecipe(),
onGroupDone(onDone)
};
return root;
}
@@ -754,13 +792,13 @@ void AxivionPluginPrivate::fetchProjectInfo(const QString &projectName)
handleOpenedDocs();
};
const QUrl url(settings().server.dashboard);
taskTree.setRecipe(fetchDataRecipe<Dto::ProjectInfoDto>(url.resolved(*it), handler));
taskTree.setRecipe(
fetchDataRecipe<Dto::ProjectInfoDto>(m_dashboardInfo->source.resolved(*it), handler));
return SetupResult::Continue;
};
const Group root {
dashboardInfoRecipe(),
authorizationRecipe(),
TaskTreeTask(onTaskTreeSetup)
};
m_taskTreeRunner.start(root);

View File

@@ -23,6 +23,8 @@ namespace Utils { class FilePath; }
namespace Axivion::Internal {
constexpr int DefaultSearchLimit = 2048;
struct IssueListSearch
{
QString kind;
@@ -33,7 +35,7 @@ struct IssueListSearch
QString filter_path;
QString sort;
int offset = 0;
int limit = 150;
int limit = DefaultSearchLimit;
bool computeTotalRowCount = false;
QString toQuery() const;
@@ -71,7 +73,7 @@ void fetchProjectInfo(const QString &projectName);
std::optional<Dto::ProjectInfoDto> projectInfo();
bool handleCertificateIssue();
QIcon iconForIssue(const QString &prefix);
QIcon iconForIssue(const std::optional<Dto::IssueKind> &issueKind);
QString anyToSimpleString(const Dto::Any &any);
void fetchIssueInfo(const QString &id);

View File

@@ -3,6 +3,7 @@
#include "dynamiclistmodel.h"
#include "axivionplugin.h"
#include "axiviontr.h"
#include <utils/qtcassert.h>
@@ -10,8 +11,6 @@
namespace Axivion::Internal {
constexpr int pageSize = 150;
DynamicListModel::DynamicListModel(QObject *parent)
: QAbstractItemModel(parent)
{
@@ -161,7 +160,7 @@ QModelIndex DynamicListModel::indexForItem(const ListItem *item) const
void DynamicListModel::onNeedFetch(int row)
{
m_fetchStart = row;
m_fetchEnd = row + pageSize;
m_fetchEnd = row + DefaultSearchLimit;
if (m_fetchStart < 0)
return;
m_fetchMoreTimer.start();
@@ -171,14 +170,14 @@ void DynamicListModel::fetchNow()
{
const int old = m_lastFetch;
m_lastFetch = m_fetchStart; // we need the "original" fetch request to avoid endless loop
m_lastFetchEnd = m_fetchStart + pageSize;
m_lastFetchEnd = m_fetchStart + DefaultSearchLimit;
if (old != -1) {
const int diff = old - m_fetchStart;
if (0 < diff && diff < pageSize) {
m_fetchStart = qMax(old - pageSize, 0);
} else if (0 > diff && diff > -pageSize) {
m_fetchStart = old + pageSize;
if (0 < diff && diff < DefaultSearchLimit) {
m_fetchStart = qMax(old - DefaultSearchLimit, 0);
} else if (0 > diff && diff > - DefaultSearchLimit) {
m_fetchStart = old + DefaultSearchLimit;
if (m_expectedRowCount && m_fetchStart > *m_expectedRowCount)
m_fetchStart = *m_expectedRowCount;
}
@@ -186,7 +185,7 @@ void DynamicListModel::fetchNow()
QTC_CHECK(m_expectedRowCount ? m_fetchStart <= *m_expectedRowCount
: m_fetchStart >= m_children.size());
emit fetchRequested(m_fetchStart, pageSize);
emit fetchRequested(m_fetchStart, DefaultSearchLimit);
m_fetchStart = -1;
m_fetchEnd = -1;
}

View File

Before

Width:  |  Height:  |  Size: 179 B

After

Width:  |  Height:  |  Size: 179 B

View File

Before

Width:  |  Height:  |  Size: 241 B

After

Width:  |  Height:  |  Size: 241 B

View File

Before

Width:  |  Height:  |  Size: 207 B

After

Width:  |  Height:  |  Size: 207 B

View File

Before

Width:  |  Height:  |  Size: 263 B

After

Width:  |  Height:  |  Size: 263 B

View File

Before

Width:  |  Height:  |  Size: 205 B

After

Width:  |  Height:  |  Size: 205 B

View File

Before

Width:  |  Height:  |  Size: 241 B

After

Width:  |  Height:  |  Size: 241 B

View File

Before

Width:  |  Height:  |  Size: 212 B

After

Width:  |  Height:  |  Size: 212 B

View File

Before

Width:  |  Height:  |  Size: 397 B

After

Width:  |  Height:  |  Size: 397 B

View File

Before

Width:  |  Height:  |  Size: 173 B

After

Width:  |  Height:  |  Size: 173 B

View File

Before

Width:  |  Height:  |  Size: 230 B

After

Width:  |  Height:  |  Size: 230 B

View File

Before

Width:  |  Height:  |  Size: 175 B

After

Width:  |  Height:  |  Size: 175 B

View File

Before

Width:  |  Height:  |  Size: 318 B

After

Width:  |  Height:  |  Size: 318 B

View File

@@ -61,8 +61,10 @@ void IssueHeaderView::mousePressEvent(QMouseEvent *event)
if (y > 1 && y < height() - 2) { // TODO improve
const int pos = position.x();
const int logical = logicalIndexAt(pos);
const int end = sectionViewportPosition(logical) + sectionSize(logical);
const int start = end - ICON_SIZE - 2;
m_lastToggleLogicalPos = logical;
const int margin = style()->pixelMetric(QStyle::PM_HeaderGripMargin, nullptr, this);
const int end = sectionViewportPosition(logical) + sectionSize(logical) - margin;
const int start = end - ICON_SIZE;
m_maybeToggleSort = start < pos && end > pos;
}
}
@@ -79,7 +81,8 @@ void IssueHeaderView::mouseReleaseEvent(QMouseEvent *event)
const QPoint position = event->position().toPoint();
const int y = position.y();
const int logical = logicalIndexAt(position.x());
if (logical > -1 && logical < m_sortableColumns.size()) {
if (logical == m_lastToggleLogicalPos
&& logical > -1 && logical < m_sortableColumns.size()) {
if (m_sortableColumns.at(logical)) { // ignore non-sortable
if (y < height() / 2) // TODO improve
onToggleSort(logical, SortOrder::Ascending);
@@ -88,6 +91,7 @@ void IssueHeaderView::mouseReleaseEvent(QMouseEvent *event)
}
}
}
m_lastToggleLogicalPos = -1;
QHeaderView::mouseReleaseEvent(event);
}
@@ -118,8 +122,10 @@ QSize IssueHeaderView::sectionSizeFromContents(int logicalIndex) const
const QSize oldSize = QHeaderView::sectionSizeFromContents(logicalIndex);
const QSize newSize = logicalIndex < m_columnWidths.size()
? QSize(qMax(m_columnWidths.at(logicalIndex), oldSize.width()), oldSize.height()) : oldSize;
// add icon size and margin (2)
return QSize{newSize.width() + ICON_SIZE + 2, qMax(newSize.height(), ICON_SIZE)};
const int margin = style()->pixelMetric(QStyle::PM_HeaderGripMargin, nullptr, this);
// add icon size and margin (default resize handle margin + 1)
return QSize{newSize.width() + ICON_SIZE + margin, qMax(newSize.height(), ICON_SIZE)};
}
void IssueHeaderView::paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const
@@ -132,9 +138,10 @@ void IssueHeaderView::paintSection(QPainter *painter, const QRect &rect, int log
if (!m_sortableColumns.at(logicalIndex))
return;
const int margin = style()->pixelMetric(QStyle::PM_HeaderGripMargin, nullptr, this);
const QIcon icon = iconForSorted(logicalIndex == m_currentSortIndex ? m_currentSortOrder : SortOrder::None);
const int offset = qMax((rect.height() - ICON_SIZE), 0) / 2;
const QRect iconRect(rect.left() + rect.width() - ICON_SIZE - 2, offset, ICON_SIZE, ICON_SIZE);
const QRect iconRect(rect.left() + rect.width() - ICON_SIZE - margin, offset, ICON_SIZE, ICON_SIZE);
icon.paint(painter, iconRect);
}

View File

@@ -35,6 +35,7 @@ private:
void onToggleSort(int index, SortOrder order);
bool m_dragging = false;
bool m_maybeToggleSort = false;
int m_lastToggleLogicalPos = -1;
int m_currentSortIndex = -1;
SortOrder m_currentSortOrder = SortOrder::None;
QList<bool> m_sortableColumns;

View File

@@ -59,9 +59,14 @@ clang::format::FormatStyle calculateQtcStyle()
style.AllowShortFunctionsOnASingleLine = FormatStyle::SFS_Inline;
style.AllowShortIfStatementsOnASingleLine = FormatStyle::SIS_Never;
style.AllowShortLoopsOnASingleLine = false;
style.AlwaysBreakAfterReturnType = FormatStyle::RTBS_None;
style.AlwaysBreakBeforeMultilineStrings = false;
#if LLVM_VERSION_MAJOR >= 19
style.BreakAfterReturnType = FormatStyle::RTBS_None;
style.BreakTemplateDeclarations = FormatStyle::BTDS_Yes;
#else
style.AlwaysBreakAfterReturnType = FormatStyle::RTBS_None;
style.AlwaysBreakTemplateDeclarations = FormatStyle::BTDS_Yes;
#endif
style.BinPackArguments = false;
style.BinPackParameters = false;
style.BraceWrapping.AfterClass = true;

View File

@@ -301,8 +301,24 @@ static CMakeBuildTarget toBuildTarget(const TargetDetails &t,
continue;
const FilePath buildDir = relativeLibs ? buildDirectory : currentBuildDir;
FilePath tmp = buildDir.resolvePath(part);
std::optional<QString> dllName;
if (buildDir.osType() == OsTypeWindows && (f.role == "libraries")) {
// Skip object libraries on Windows. This case can happen with static qml plugins
if (part.endsWith(".obj") || part.endsWith(".o"))
continue;
// Only consider dlls, not static libraries
for (const QString &suffix :
{QString(".lib"), QString(".dll.a"), QString(".a")}) {
if (part.endsWith(suffix) && !dllName)
dllName = FilePath::fromUserInput(
part.chopped(suffix.length()).append(".dll"))
.fileName();
}
}
FilePath tmp = buildDir.resolvePath(part);
if (f.role == "libraries")
tmp = tmp.parentDir();
@@ -312,19 +328,20 @@ static CMakeBuildTarget toBuildTarget(const TargetDetails &t,
// "/usr/local/lib" since these are usually in the standard search
// paths. There probably are more, but the naming schemes are arbitrary
// so we'd need to ask the linker ("ld --verbose | grep SEARCH_DIR").
if (buildDir.osType() == OsTypeWindows
|| !isChildOf(tmp,
{"/lib",
"/lib64",
"/usr/lib",
"/usr/lib64",
"/usr/local/lib"})) {
if (buildDir.osType() != OsTypeWindows
&& !isChildOf(tmp,
{"/lib", "/lib64", "/usr/lib", "/usr/lib64", "/usr/local/lib"}))
librarySeachPaths.append(tmp);
if (buildDir.osType() == OsTypeWindows && dllName) {
if (tmp.pathAppended(*dllName).exists())
librarySeachPaths.append(tmp);
// Libraries often have their import libs in ../lib and the
// actual dll files in ../bin on windows. Qt is one example of that.
if (tmp.fileName() == "lib" && buildDir.osType() == OsTypeWindows) {
const FilePath path = tmp.parentDir().pathAppended("bin");
if (path.isDir())
if (path.isDir() && path.pathAppended(*dllName).exists())
librarySeachPaths.append(path);
}
}

View File

@@ -1,5 +1,9 @@
<RCC>
<qresource prefix="/core">
<file>images/expandarrow.png</file>
<file>images/expandarrow@2x.png</file>
<file>images/search.png</file>
<file>images/search@2x.png</file>
<file>images/settingscategory_core.png</file>
<file>images/settingscategory_core@2x.png</file>
<file>images/settingscategory_design.png</file>

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 B

View File

@@ -3,25 +3,8 @@
#include "iwelcomepage.h"
#include <utils/icon.h>
#include <utils/theme/theme.h>
#include <utils/stylehelper.h>
#include <QHBoxLayout>
#include <QLabel>
#include <QPainter>
#include <QPainterPath>
#include <QPixmap>
#include <QUrl>
#include <qdrawutil.h>
using namespace Utils;
namespace Core {
const char WITHACCENTCOLOR_PROPERTY_NAME[] = "_withAccentColor";
static QList<IWelcomePage *> g_welcomePages;
const QList<IWelcomePage *> IWelcomePage::allWelcomePages()
@@ -39,171 +22,4 @@ IWelcomePage::~IWelcomePage()
g_welcomePages.removeOne(this);
}
QPalette WelcomePageFrame::buttonPalette(bool isActive, bool isCursorInside, bool forText)
{
QPalette pal;
pal.setBrush(QPalette::Window, {});
pal.setBrush(QPalette::WindowText, {});
Theme *theme = Utils::creatorTheme();
if (isActive) {
if (forText) {
pal.setColor(QPalette::Window, theme->color(Theme::Welcome_ForegroundPrimaryColor));
pal.setColor(QPalette::WindowText, theme->color(Theme::Welcome_BackgroundPrimaryColor));
} else {
pal.setColor(QPalette::Window, theme->color(Theme::Welcome_AccentColor));
pal.setColor(QPalette::WindowText, theme->color(Theme::Welcome_AccentColor));
}
} else {
if (isCursorInside) {
if (forText) {
pal.setColor(QPalette::Window, theme->color(Theme::Welcome_HoverColor));
pal.setColor(QPalette::WindowText, theme->color(Theme::Welcome_TextColor));
} else {
pal.setColor(QPalette::Window, theme->color(Theme::Welcome_HoverColor));
pal.setColor(QPalette::WindowText, theme->color(Theme::Welcome_ForegroundSecondaryColor));
}
} else {
if (forText) {
pal.setColor(QPalette::Window, theme->color(Theme::Welcome_ForegroundPrimaryColor));
pal.setColor(QPalette::WindowText, theme->color(Theme::Welcome_TextColor));
} else {
pal.setColor(QPalette::Window, theme->color(Theme::Welcome_BackgroundPrimaryColor));
pal.setColor(QPalette::WindowText, theme->color(Theme::Welcome_ForegroundSecondaryColor));
}
}
}
return pal;
}
WelcomePageFrame::WelcomePageFrame(QWidget *parent)
: QWidget(parent)
{
setContentsMargins(1, 1, 1, 1);
}
void WelcomePageFrame::paintEvent(QPaintEvent *event)
{
QWidget::paintEvent(event);
QPainter p(this);
qDrawPlainRect(&p, rect(), palette().color(QPalette::WindowText), 1);
if (property(WITHACCENTCOLOR_PROPERTY_NAME).toBool()) {
const int accentRectWidth = 10;
const QRect accentRect = rect().adjusted(width() - accentRectWidth, 0, 0, 0);
p.fillRect(accentRect, creatorTheme()->color(Theme::Welcome_AccentColor));
}
}
class WelcomePageButtonPrivate
{
public:
explicit WelcomePageButtonPrivate(WelcomePageButton *parent)
: q(parent) {}
bool isActive() const;
void doUpdate(bool cursorInside);
WelcomePageButton *q;
QHBoxLayout *m_layout = nullptr;
QLabel *m_label = nullptr;
std::function<void()> onClicked;
std::function<bool()> activeChecker;
};
WelcomePageButton::WelcomePageButton(QWidget *parent)
: WelcomePageFrame(parent), d(new WelcomePageButtonPrivate(this))
{
setAutoFillBackground(true);
setPalette(buttonPalette(false, false, false));
setContentsMargins(0, 1, 0, 1);
d->m_label = new QLabel(this);
d->m_label->setPalette(buttonPalette(false, false, true));
d->m_label->setAlignment(Qt::AlignCenter);
d->m_layout = new QHBoxLayout;
d->m_layout->setSpacing(0);
d->m_layout->addWidget(d->m_label);
setSize(SizeLarge);
setLayout(d->m_layout);
}
WelcomePageButton::~WelcomePageButton()
{
delete d;
}
void WelcomePageButton::mousePressEvent(QMouseEvent *)
{
if (d->onClicked)
d->onClicked();
}
void WelcomePageButton::enterEvent(QEnterEvent *)
{
d->doUpdate(true);
}
void WelcomePageButton::leaveEvent(QEvent *)
{
d->doUpdate(false);
}
bool WelcomePageButtonPrivate::isActive() const
{
return activeChecker && activeChecker();
}
void WelcomePageButtonPrivate::doUpdate(bool cursorInside)
{
const bool active = isActive();
q->setPalette(WelcomePageFrame::buttonPalette(active, cursorInside, false));
const QPalette lpal = WelcomePageFrame::buttonPalette(active, cursorInside, true);
m_label->setPalette(lpal);
q->update();
}
void WelcomePageButton::setText(const QString &text)
{
d->m_label->setText(text);
}
void WelcomePageButton::setSize(Size size)
{
const int hMargin = size == SizeSmall ? 12 : 26;
const int vMargin = size == SizeSmall ? 2 : 4;
d->m_layout->setContentsMargins(hMargin, vMargin, hMargin, vMargin);
}
void WelcomePageButton::setWithAccentColor(bool withAccent)
{
setProperty(WITHACCENTCOLOR_PROPERTY_NAME, withAccent);
}
void WelcomePageButton::setActiveChecker(const std::function<bool ()> &value)
{
d->activeChecker = value;
}
void WelcomePageButton::recheckActive()
{
bool isActive = d->isActive();
d->doUpdate(isActive);
}
void WelcomePageButton::click()
{
if (d->onClicked)
d->onClicked();
}
void WelcomePageButton::setOnClicked(const std::function<void ()> &value)
{
d->onClicked = value;
if (d->isActive())
click();
}
} // namespace Core

View File

@@ -7,13 +7,10 @@
#include <utils/id.h>
#include <QWidget>
#include <QObject>
#include <functional>
QT_BEGIN_NAMESPACE
class QPixmap;
class QWidget;
QT_END_NAMESPACE
namespace Core {
@@ -37,43 +34,4 @@ public:
static const QList<IWelcomePage *> allWelcomePages();
};
class WelcomePageButtonPrivate;
class CORE_EXPORT WelcomePageFrame : public QWidget
{
public:
WelcomePageFrame(QWidget *parent);
void paintEvent(QPaintEvent *event) override;
static QPalette buttonPalette(bool isActive, bool isCursorInside, bool forText);
};
class CORE_EXPORT WelcomePageButton : public WelcomePageFrame
{
public:
enum Size {
SizeSmall,
SizeLarge,
};
explicit WelcomePageButton(QWidget *parent = nullptr);
~WelcomePageButton() override;
void mousePressEvent(QMouseEvent *) override;
void enterEvent(QEnterEvent *) override;
void leaveEvent(QEvent *) override;
void setText(const QString &text);
void setSize(enum Size);
void setWithAccentColor(bool withAccent);
void setOnClicked(const std::function<void ()> &value);
void setActiveChecker(const std::function<bool ()> &value);
void recheckActive();
void click();
private:
WelcomePageButtonPrivate *d;
};
} // Core

View File

@@ -559,10 +559,16 @@ private:
const QString timestamp = QDateTime::currentDateTime().toString("HH:mm:ss.zzz");
auto append = [this, timestamp, type, category, msg] {
if (rowCount() >= 1000000) // limit log to 1000000 items
destroyItem(itemForIndex(index(0, 0)));
appendItem(LogEntry{timestamp, messageTypeToString(type), category, msg});
};
if (QThread::currentThread() != thread())
QMetaObject::invokeMethod(this, append, Qt::QueuedConnection);
else
append();
}
private:

View File

@@ -267,6 +267,11 @@ QDateTime SessionManager::lastActiveTime(const QString &session)
return d->m_lastActiveTimes.value(session);
}
int SessionManager::sessionsCount()
{
return d->m_sessions.count();
}
FilePath SessionManager::sessionNameToFileName(const QString &session)
{
return ICore::userResourcePath(session + ".qws");

View File

@@ -34,6 +34,7 @@ public:
static QStringList sessions();
static QDateTime sessionDateTime(const QString &session);
static QDateTime lastActiveTime(const QString &session);
static int sessionsCount();
static bool createSession(const QString &session);

View File

@@ -7,6 +7,7 @@
#include <utils/algorithm.h>
#include <utils/fancylineedit.h>
#include <utils/icon.h>
#include <utils/layoutbuilder.h>
#include <utils/qtcassert.h>
#include <utils/stylehelper.h>
@@ -26,36 +27,34 @@
#include <qdrawutil.h>
QT_BEGIN_NAMESPACE
void qt_blurImage(QImage &blurImage, qreal radius, bool quality, int transposed = 0);
QT_END_NAMESPACE
using namespace Utils;
namespace Core {
using namespace WelcomePageHelpers;
using namespace StyleHelper::SpacingTokens;
static QColor themeColor(Theme::Color role)
{
return creatorTheme()->color(role);
}
static QFont sizedFont(int size, const QWidget *widget)
{
QFont f = widget->font();
f.setPixelSize(size);
return f;
}
namespace WelcomePageHelpers {
QWidget *panelBar(QWidget *parent)
void setBackgroundColor(QWidget *widget, Theme::Color colorRole)
{
auto frame = new QWidget(parent);
frame->setAutoFillBackground(true);
frame->setMinimumWidth(WelcomePageHelpers::HSpacing);
QPalette pal;
pal.setBrush(QPalette::Window, {});
pal.setColor(QPalette::Window, themeColor(Theme::Welcome_BackgroundPrimaryColor));
frame->setPalette(pal);
return frame;
QPalette palette = creatorTheme()->palette();
const QPalette::ColorRole role = QPalette::Window;
palette.setBrush(role, {});
palette.setColor(role, creatorTheme()->color(colorRole));
widget->setPalette(palette);
widget->setBackgroundRole(role);
widget->setAutoFillBackground(true);
}
void drawCardBackground(QPainter *painter, const QRectF &rect,
@@ -77,30 +76,351 @@ void drawCardBackground(QPainter *painter, const QRectF &rect,
painter->restore();
}
QWidget *createRule(Qt::Orientation orientation, QWidget *parent)
{
auto rule = new QWidget(parent);
if (orientation == Qt::Horizontal)
rule->setFixedHeight(1);
else
rule->setFixedWidth(1);
setBackgroundColor(rule, Theme::Token_Stroke_Subtle);
return rule;
}
} // namespace WelcomePageHelpers
SearchBox::SearchBox(QWidget *parent)
: WelcomePageFrame(parent)
enum WidgetState {
WidgetStateDefault,
WidgetStateChecked,
WidgetStateHovered,
};
static const TextFormat &buttonTF(Button::Role role, WidgetState state)
{
setAutoFillBackground(true);
using namespace WelcomePageHelpers;
static const TextFormat mediumPrimaryTF
{Theme::Token_Basic_White, StyleHelper::UiElement::UiElementButtonMedium,
Qt::AlignCenter | Qt::TextDontClip};
static const TextFormat mediumSecondaryTF
{Theme::Token_Text_Default, mediumPrimaryTF.uiElement, mediumPrimaryTF.drawTextFlags};
static const TextFormat smallPrimaryTF
{mediumPrimaryTF.themeColor, StyleHelper::UiElement::UiElementButtonSmall,
mediumPrimaryTF.drawTextFlags};
static const TextFormat smallSecondaryTF
{mediumSecondaryTF.themeColor, smallPrimaryTF.uiElement, smallPrimaryTF.drawTextFlags};
static const TextFormat smallListDefaultTF
{Theme::Token_Text_Default, StyleHelper::UiElement::UiElementIconStandard,
Qt::AlignLeft | Qt::AlignVCenter | Qt::TextDontClip};
static const TextFormat smallListCheckedTF
{smallListDefaultTF.themeColor, StyleHelper::UiElement::UiElementIconActive,
smallListDefaultTF.drawTextFlags};
static const TextFormat smallLinkDefaultTF
{Theme::Token_Text_Default, smallListDefaultTF.uiElement, smallListDefaultTF.drawTextFlags};
static const TextFormat smallLinkHoveredTF
{Theme::Token_Accent_Default, smallListCheckedTF.uiElement,
smallLinkDefaultTF.drawTextFlags};
m_lineEdit = new FancyLineEdit;
m_lineEdit->setFiltering(true);
m_lineEdit->setFrame(false);
m_lineEdit->setMinimumHeight(33);
m_lineEdit->setAttribute(Qt::WA_MacShowFocusRect, false);
switch (role) {
case Button::MediumPrimary: return mediumPrimaryTF;
case Button::MediumSecondary: return mediumSecondaryTF;
case Button::SmallPrimary: return smallPrimaryTF;
case Button::SmallSecondary: return smallSecondaryTF;
case Button::SmallList: return (state == WidgetStateDefault) ? smallListDefaultTF
: smallListCheckedTF;
case Button::SmallLink: return (state == WidgetStateDefault) ? smallLinkDefaultTF
: smallLinkHoveredTF;
}
return mediumPrimaryTF;
}
QPalette pal = buttonPalette(false, false, true);
// for the margins
pal.setColor(QPalette::Window, m_lineEdit->palette().color(QPalette::Base));
// for macOS dark mode
pal.setColor(QPalette::WindowText, themeColor(Theme::Welcome_ForegroundPrimaryColor));
pal.setColor(QPalette::Text, themeColor(Theme::Welcome_TextColor));
Button::Button(const QString &text, Role role, QWidget *parent)
: QPushButton(text, parent)
, m_role(role)
{
// Prevent QMacStyle::subElementRect(SE_PushButtonLayoutItem) from changing our geometry
setFlat(true);
updateMargins();
if (m_role == SmallList)
setCheckable(true);
else if (m_role == SmallLink)
setCursor(Qt::PointingHandCursor);
}
QSize Button::minimumSizeHint() const
{
const TextFormat &tf = buttonTF(m_role, WidgetStateHovered);
const QFontMetrics fm(tf.font());
const QSize textS = fm.size(Qt::TextShowMnemonic, text());
const QMargins margins = contentsMargins();
return {margins.left() + textS.width() + margins.right(),
margins.top() + tf.lineHeight() + margins.bottom()};
}
void Button::paintEvent(QPaintEvent *event)
{
// Without pixmap
// +----------------+----------------+----------------+
// | |(VPadding[S|XS])| |
// | +----------------+----------------+
// |(HPadding[S|XS])| <label> |(HPadding[S|XS])|
// | +----------------+----------------+
// | |(VPadding[S|XS])| |
// +----------------+---------------------------------+
//
// With pixmap
// +--------+------------+---------------------------------+
// | | |(VPadding[S|XS])| |
// | | +----------------+ |
// |<pixmap>|(HGap[S|XS])| <label> |(HPadding[S|XS])|
// | | +----------------+ |
// | | |(VPadding[S|XS])| |
// +--------+------------+----------------+----------------+
using namespace WelcomePageHelpers;
const bool hovered = underMouse();
const WidgetState state = isChecked() ? WidgetStateChecked : hovered ? WidgetStateHovered
: WidgetStateDefault;
const TextFormat &tf = buttonTF(m_role, state);
const QMargins margins = contentsMargins();
const QRect bgR = rect();
QPainter p(this);
const qreal brRectRounding = 3.75;
switch (m_role) {
case MediumPrimary:
case SmallPrimary: {
const QBrush fill(creatorTheme()->color(isDown()
? Theme::Token_Accent_Subtle
: hovered ? Theme::Token_Accent_Muted
: Theme::Token_Accent_Default));
drawCardBackground(&p, bgR, fill, QPen(Qt::NoPen), brRectRounding);
break;
}
case MediumSecondary:
case SmallSecondary: {
const QPen outline(creatorTheme()->color(Theme::Token_Text_Default), hovered ? 2 : 1);
drawCardBackground(&p, bgR, QBrush(Qt::NoBrush), outline, brRectRounding);
break;
}
case SmallList: {
if (isChecked() || hovered) {
const QBrush fill(creatorTheme()->color(isChecked() ? Theme::Token_Foreground_Muted
: Theme::Token_Foreground_Subtle));
drawCardBackground(&p, bgR, fill, QPen(Qt::NoPen), brRectRounding);
}
break;
}
case SmallLink:
break;
}
if (!m_pixmap.isNull()) {
const int pixmapHeight = int(m_pixmap.deviceIndependentSize().height());
const int pixmapY = (bgR.height() - pixmapHeight) / 2;
p.drawPixmap(0, pixmapY, m_pixmap);
}
const int availableLabelWidth = event->rect().width() - margins.left() - margins.right();
const QFont font = tf.font();
const QFontMetrics fm(font);
const QString elidedLabelText = fm.elidedText(text(), Qt::ElideRight, availableLabelWidth);
const QRect labelR(margins.left(), margins.top(), availableLabelWidth, tf.lineHeight());
p.setFont(font);
p.setPen(tf.color());
p.drawText(labelR, tf.drawTextFlags, elidedLabelText);
}
void Button::setPixmap(const QPixmap &pixmap)
{
m_pixmap = pixmap;
updateMargins();
}
void Button::updateMargins()
{
const bool tokenSizeS = m_role == MediumPrimary || m_role == MediumSecondary
|| m_role == SmallList || m_role == SmallLink;
const int gap = tokenSizeS ? HGapS : HGapXs;
const int hPaddingR = tokenSizeS ? HPaddingS : HPaddingXs;
const int hPaddingL = m_pixmap.isNull() ? hPaddingR
: int(m_pixmap.deviceIndependentSize().width()) + gap;
const int vPadding = tokenSizeS ? VPaddingS : VPaddingXs;
setContentsMargins(hPaddingL, vPadding, hPaddingR, vPadding);
}
Label::Label(const QString &text, Role role, QWidget *parent)
: QLabel(text, parent)
, m_role(role)
{
using namespace WelcomePageHelpers;
static const TextFormat primaryTF
{Theme::Token_Text_Muted, StyleHelper::UiElement::UiElementH3};
static const TextFormat secondaryTF
{primaryTF.themeColor, StyleHelper::UiElement::UiElementH6Capital};
const TextFormat &tF = m_role == Primary ? primaryTF : secondaryTF;
const int vPadding = m_role == Primary ? ExPaddingGapM : VPaddingS;
setFixedHeight(vPadding + tF.lineHeight() + vPadding);
setFont(tF.font());
QPalette pal = palette();
pal.setColor(QPalette::WindowText, tF.color());
setPalette(pal);
}
constexpr TextFormat searchBoxTextTF
{Theme::Token_Text_Default, StyleHelper::UiElement::UiElementBody2};
constexpr TextFormat searchBoxPlaceholderTF
{Theme::Token_Text_Muted, searchBoxTextTF.uiElement};
static const QPixmap &searchBoxIcon()
{
static const QPixmap icon = Icon({{FilePath::fromString(":/core/images/search"),
Theme::Token_Text_Muted}}, Icon::Tint).pixmap();
return icon;
}
SearchBox::SearchBox(QWidget *parent)
: QLineEdit(parent)
{
setAttribute(Qt::WA_MacShowFocusRect, false);
setAutoFillBackground(false);
setFont(searchBoxTextTF.font());
setFrame(false);
setMouseTracking(true);
QPalette pal = palette();
pal.setColor(QPalette::Base, Qt::transparent);
pal.setColor(QPalette::PlaceholderText, searchBoxPlaceholderTF.color());
pal.setColor(QPalette::Text, searchBoxTextTF.color());
setPalette(pal);
auto box = new QHBoxLayout(this);
box->setContentsMargins(10, 0, 1, 0);
box->addWidget(m_lineEdit);
const QSize iconSize = searchBoxIcon().deviceIndependentSize().toSize();
setContentsMargins({HPaddingXs, ExPaddingGapM,
HPaddingXs + iconSize.width() + HPaddingXs, ExPaddingGapM});
setFixedHeight(ExPaddingGapM + searchBoxTextTF.lineHeight() + ExPaddingGapM);
}
QSize SearchBox::minimumSizeHint() const
{
const QFontMetrics fm(searchBoxTextTF.font());
const QSize textS = fm.size(Qt::TextSingleLine, text());
const QMargins margins = contentsMargins();
return {margins.left() + textS.width() + margins.right(),
margins.top() + searchBoxTextTF.lineHeight() + margins.bottom()};
}
void SearchBox::enterEvent(QEnterEvent *event)
{
QLineEdit::enterEvent(event);
update();
}
void SearchBox::leaveEvent(QEvent *event)
{
QLineEdit::leaveEvent(event);
update();
}
static void paintCommonBackground(QPainter *p, const QRectF &rect, const QWidget *widget)
{
const QBrush fill(creatorTheme()->color(Theme::Token_Background_Muted));
const Theme::Color c = widget->hasFocus() ? Theme::Token_Stroke_Strong :
widget->underMouse() ? Theme::Token_Stroke_Muted
: Theme::Token_Stroke_Subtle;
const QPen pen(creatorTheme()->color(c));
drawCardBackground(p, rect, fill, pen);
}
void SearchBox::paintEvent(QPaintEvent *event)
{
// +------------+---------------+------------+------+------------+
// | |(ExPaddingGapM)| | | |
// | +---------------+ | | |
// |(HPaddingXs)| <lineEdit> |(HPaddingXs)|<icon>|(HPaddingXs)|
// | +---------------+ | | |
// | |(ExPaddingGapM)| | | |
// +------------+---------------+------------+------+------------+
QPainter p(this);
paintCommonBackground(&p, rect(), this);
const QPixmap icon = searchBoxIcon();
const QSize iconS = icon.deviceIndependentSize().toSize();
const QPoint iconPos(width() - HPaddingXs - iconS.width(), (height() - iconS.height()) / 2);
p.drawPixmap(iconPos, icon);
QLineEdit::paintEvent(event);
}
constexpr TextFormat ComboBoxTf
{Theme::Token_Text_Muted, StyleHelper::UiElementIconActive,
Qt::AlignLeft | Qt::AlignVCenter | Qt::TextDontClip};
static const QPixmap &comboBoxIcon()
{
static const QPixmap icon = Icon({{FilePath::fromString(":/core/images/expandarrow"),
ComboBoxTf.themeColor}}, Icon::Tint).pixmap();
return icon;
}
ComboBox::ComboBox(QWidget *parent)
: QComboBox(parent)
{
setFont(ComboBoxTf.font());
setMouseTracking(true);
const QSize iconSize = comboBoxIcon().deviceIndependentSize().toSize();
setContentsMargins({HPaddingXs, VPaddingXs,
HGapXxs + iconSize.width() + HPaddingXs, VPaddingXs});
}
QSize ComboBox::sizeHint() const
{
const QSize parentS = QComboBox::sizeHint();
const QMargins margins = contentsMargins();
return {margins.left() + parentS.width() + margins.right(),
margins.top() + ComboBoxTf.lineHeight() + margins.bottom()};
}
void ComboBox::enterEvent(QEnterEvent *event)
{
QComboBox::enterEvent(event);
update();
}
void ComboBox::leaveEvent(QEvent *event)
{
QComboBox::leaveEvent(event);
update();
}
void ComboBox::paintEvent(QPaintEvent *)
{
// +------------+-------------+---------+-------+------------+
// | | (VPaddingXs)| | | |
// | +-------------+ | | |
// |(HPaddingXs)|<currentItem>|(HGapXxs)|<arrow>|(HPaddingXs)|
// | +-------------+ | | |
// | | (VPaddingXs)| | | |
// +------------+-------------+---------+-------+------------+
QPainter p(this);
paintCommonBackground(&p, rect(), this);
const QMargins margins = contentsMargins();
const QRect textR(margins.left(), margins.top(),
width() - margins.right(), ComboBoxTf.lineHeight());
p.setFont(ComboBoxTf.font());
p.setPen(ComboBoxTf.color());
p.drawText(textR, ComboBoxTf.drawTextFlags, currentText());
const QPixmap icon = comboBoxIcon();
const QSize iconS = icon.deviceIndependentSize().toSize();
const QPoint iconPos(width() - HPaddingXs - iconS.width(), (height() - iconS.height()) / 2);
p.drawPixmap(iconPos, icon);
}
GridView::GridView(QWidget *parent)
@@ -115,7 +435,7 @@ GridView::GridView(QWidget *parent)
setUniformItemSizes(true);
QPalette pal;
pal.setColor(QPalette::Base, themeColor(Theme::Welcome_BackgroundSecondaryColor));
pal.setColor(QPalette::Base, themeColor(Theme::Token_Background_Default));
setPalette(pal); // Makes a difference on Mac.
}
@@ -147,10 +467,11 @@ bool SectionGridView::hasHeightForWidth() const
int SectionGridView::heightForWidth(int width) const
{
const int columnCount = qMax(1, width / Core::WelcomePageHelpers::GridItemWidth);
const QSize itemSize = ListItemDelegate::itemSize();
const int columnCount = qMax(1, width / itemSize.width());
const int rowCount = (model()->rowCount() + columnCount - 1) / columnCount;
const int maxRowCount = m_maxRows ? std::min(*m_maxRows, rowCount) : rowCount;
return maxRowCount * Core::WelcomePageHelpers::GridItemHeight;
return maxRowCount * itemSize.height();
}
void SectionGridView::wheelEvent(QWheelEvent *e)
@@ -165,8 +486,9 @@ bool SectionGridView::event(QEvent *e)
{
if (e->type() == QEvent::Resize) {
const auto itemsFit = [this](const QSize &size) {
const int maxColumns = std::max(size.width() / WelcomePageHelpers::GridItemWidth, 1);
const int maxRows = std::max(size.height() / WelcomePageHelpers::GridItemHeight, 1);
const QSize itemSize = ListItemDelegate::itemSize();
const int maxColumns = std::max(size.width() / itemSize.width(), 1);
const int maxRows = std::max(size.height() / itemSize.height(), 1);
const int maxItems = maxColumns * maxRows;
const int items = model()->rowCount();
return maxItems >= items;
@@ -426,47 +748,123 @@ bool ListModelFilter::leaveFilterAcceptsRowBeforeFiltering(const ListItem *, boo
return false;
}
ListItemDelegate::ListItemDelegate()
: backgroundPrimaryColor(themeColor(Theme::Welcome_BackgroundPrimaryColor))
, backgroundSecondaryColor(themeColor(Theme::Welcome_BackgroundSecondaryColor))
, foregroundPrimaryColor(themeColor(Theme::Welcome_ForegroundPrimaryColor))
, foregroundSecondaryColor(themeColor(Theme::Welcome_ForegroundSecondaryColor))
, hoverColor(themeColor(Theme::Welcome_HoverColor))
, textColor(themeColor(Theme::Welcome_TextColor))
constexpr TextFormat titleTF {Theme::Token_Text_Default, StyleHelper::UiElementIconActive};
constexpr TextFormat descriptionTF {titleTF.themeColor, StyleHelper::UiElementCaption};
constexpr TextFormat tagsLabelTF {Theme::Token_Text_Muted, StyleHelper::UiElementCaptionStrong};
constexpr TextFormat tagsTF {Theme::Token_Accent_Default, tagsLabelTF.uiElement};
constexpr qreal itemOutlineWidth = 1;
constexpr qreal itemCornerRounding = 6;
constexpr int thumbnailAreaBorderWidth = 1;
constexpr QSize thumbnailAreaSize =
WelcomeThumbnailSize.grownBy({thumbnailAreaBorderWidth, thumbnailAreaBorderWidth,
thumbnailAreaBorderWidth, thumbnailAreaBorderWidth});
constexpr int tagsRowsCount = 1;
constexpr int tagsHGap = ExPaddingGapM;
constexpr QEasingCurve::Type hoverEasing = QEasingCurve::OutCubic;
constexpr std::chrono::milliseconds hoverDuration(300);
constexpr int hoverBlurRadius = 50;
constexpr qreal hoverBlurOpacity = 0.175;
QSize ListItemDelegate::itemSize()
{
const int tagsTfLineHeight = tagsTF.lineHeight();
const int width =
ExPaddingGapL
+ thumbnailAreaSize.width()
+ ExPaddingGapL;
const int height =
ExPaddingGapL
+ thumbnailAreaSize.height()
+ VGapL
+ titleTF.lineHeight()
+ VGapL
+ tagsTfLineHeight
+ (tagsTfLineHeight + ExPaddingGapS) * (tagsRowsCount - 1) // If more than one row
+ ExPaddingGapL;
return {width + ExVPaddingGapXl, height + ExVPaddingGapXl};
}
void ListItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
// Unhovered tile
// +---------------+------------------+---------------+-----------------+
// | | (ExPaddingGapL) | | |
// | +------------------+ | |
// | | <thumbnail> | | |
// | +------------------+ | |
// | | (VGapL) | | |
// | +------------------+ | |
// |(ExPaddingGapL)| <title> |(ExPaddingGapL)|(ExVPaddingGapXs)|
// | +------------------+ | |
// | | (VGapL) | | |
// | +-----------+------+ | |
// | |<tagsLabel>|<tags>| | |
// | +-----------+------+ | |
// | | (ExPaddingGapL) | | |
// +---------------+------------------+---------------+-----------------+
// | (ExVPaddingGapXs) |
// +--------------------------------------------------------------------+
//
// Hovered, final animation state of the are above tagsLabel
// +---------------+------------------+---------------+
// | | (ExPaddingGapL) | |
// | +------------------+ |
// | | <title> | |
// | +------------------+ |
// | | (ExPaddingGapS) | |
// |(ExPaddingGapL)+------------------+(ExPaddingGapL)| ...
// | | <hr> | |
// | +------------------+ |
// | | (ExPaddingGapS) | |
// | +------------------+ |
// | | <description> | |
// +---------------+------------------+---------------+
// ...
const ListItem *item = index.data(ListModel::ItemRole).value<Core::ListItem *>();
const QRect rc = option.rect;
const QRect tileRect(0, 0, rc.width() - GridItemGap, rc.height() - GridItemGap);
const QSize thumbnailBgSize = GridItemImageSize.grownBy(QMargins(1, 1, 1, 1));
const QRect thumbnailBgRect((tileRect.width() - thumbnailBgSize.width()) / 2, GridItemGap,
thumbnailBgSize.width(), thumbnailBgSize.height());
const QRect textArea = tileRect.adjusted(GridItemGap, GridItemGap, -GridItemGap, -GridItemGap);
const QFont tagsLabelFont = tagsLabelTF.font();
const QFontMetrics tagsLabelFM(tagsLabelFont);
const QFont descriptionFont = descriptionTF.font();
const QFontMetrics descriptionFM(descriptionFont);
const QRect bgRGlobal = option.rect.adjusted(0, 0, -ExVPaddingGapXl, -ExVPaddingGapXl);
const QRect bgR = bgRGlobal.translated(-option.rect.topLeft());
const QRect thumbnailAreaR(bgR.left() + ExPaddingGapL, bgR.top() + ExPaddingGapL,
thumbnailAreaSize.width(), thumbnailAreaSize.height());
const QRect titleR(thumbnailAreaR.left(), thumbnailAreaR.bottom() + VGapL + 1,
thumbnailAreaR.width(), titleTF.lineHeight());
const QString tagsLabelText = Tr::tr("Tags:");
const int tagsLabelTextWidth = tagsLabelFM.horizontalAdvance(tagsLabelText);
const QRect tagsLabelR(titleR.left(), titleR.bottom() + VGapL + 1,
tagsLabelTextWidth, tagsTF.lineHeight());
const QRect tagsR(tagsLabelR.right() + 1 + tagsHGap, tagsLabelR.top(),
bgR.right() - ExPaddingGapL - tagsLabelR.right() - tagsHGap,
tagsLabelR.height()
+ (tagsLabelR.height() + ExPaddingGapS) * (tagsRowsCount - 1));
QTC_CHECK(option.rect.height() == tagsR.bottom() + 1 + ExPaddingGapL + ExVPaddingGapXl);
QTC_CHECK(option.rect.width() == tagsR.right() + 1 + ExPaddingGapL + ExVPaddingGapXl);
QTextOption wrapTO;
wrapTO.setWrapMode(QTextOption::WordWrap);
const bool hovered = option.state & QStyle::State_MouseOver;
constexpr int TagsSeparatorY = GridItemHeight - GridItemGap - 52;
constexpr int tagsBase = TagsSeparatorY + 17;
constexpr int shiftY = TagsSeparatorY - 16;
constexpr int nameY = TagsSeparatorY - 20;
const QRect textRect = textArea.translated(0, nameY);
const QFont descriptionFont = sizedFont(11, option.widget);
painter->save();
painter->translate(rc.topLeft());
painter->translate(bgRGlobal.topLeft());
painter->fillRect(tileRect, hovered ? hoverColor : backgroundPrimaryColor);
const QColor fill(themeColor(hovered ? Theme::Token_Foreground_Muted
: Theme::Token_Background_Muted));
const QPen pen(themeColor(hovered ? Theme::Token_Foreground_Muted
: Theme::Token_Stroke_Subtle), itemOutlineWidth);
WelcomePageHelpers::drawCardBackground(painter, bgR, fill, pen, itemCornerRounding);
QTextOption wrapped;
wrapped.setWrapMode(QTextOption::WordWrap);
const int shiftY = thumbnailAreaR.bottom();
int offset = 0;
float animationProgress = 0; // Linear increase from 0.0 to 1.0 during hover animation
qreal animationProgress = 0; // Linear increase from 0.0 to 1.0 during hover animation
if (hovered) {
if (index != m_previousIndex) {
m_previousIndex = index;
@@ -476,12 +874,12 @@ void ListItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opti
m_currentWidget = qobject_cast<QAbstractItemView *>(
const_cast<QWidget *>(option.widget));
}
constexpr float hoverAnimationDuration = 260;
animationProgress = m_startTime.elapsed() / hoverAnimationDuration;
animationProgress = qreal(m_startTime.elapsed()) / hoverDuration.count();
if (animationProgress < 1) {
static const QEasingCurve animationCurve(QEasingCurve::OutCubic);
static const QEasingCurve animationCurve(hoverEasing);
offset = animationCurve.valueForProgress(animationProgress) * shiftY;
QTimer::singleShot(10, this, &ListItemDelegate::goon);
using namespace std::chrono_literals;
QTimer::singleShot(10ms, this, &ListItemDelegate::goon);
} else {
offset = shiftY;
}
@@ -489,124 +887,125 @@ void ListItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opti
m_previousIndex = QModelIndex();
}
const QRect shiftedTextRect = textRect.adjusted(0, -offset, 0, -offset);
// The pixmap.
const QPixmap pm = index.data(ListModel::ItemImageRole).value<QPixmap>();
QPoint thumbnailPos = thumbnailBgRect.center();
QPoint thumbnailPos = thumbnailAreaR.center();
if (!pm.isNull()) {
painter->fillRect(thumbnailBgRect, backgroundSecondaryColor);
painter->fillRect(thumbnailAreaR, themeColor(Theme::Token_Background_Default));
thumbnailPos.rx() -= pm.width() / pm.devicePixelRatio() / 2 - 1;
thumbnailPos.ry() -= pm.height() / pm.devicePixelRatio() / 2 - 1;
painter->drawPixmap(thumbnailPos, pm);
painter->setPen(foregroundPrimaryColor);
drawPixmapOverlay(item, painter, option, thumbnailBgRect);
painter->setPen(titleTF.color());
drawPixmapOverlay(item, painter, option, thumbnailAreaR);
} else {
// The description text as fallback.
painter->setPen(textColor);
painter->setFont(descriptionFont);
painter->drawText(textArea, item->description, wrapped);
painter->setPen(descriptionTF.color());
painter->setFont(descriptionTF.font());
painter->drawText(thumbnailAreaR, item->description, wrapTO);
}
// The description background
QRect backgroundPortionR = bgR;
if (offset) {
QRect backgroundPortionRect = tileRect;
backgroundPortionRect.setTop(shiftY - offset);
backgroundPortionR.setTop(shiftY - offset);
if (!pm.isNull()) {
if (m_blurredThumbnail.isNull()) {
constexpr int blurRadius = 50;
QImage thumbnail(tileRect.size() + QSize(blurRadius, blurRadius) * 2,
constexpr int filterMargin = hoverBlurRadius;
QImage thumbnail(bgR.size() + QSize(filterMargin, filterMargin) * 2,
QImage::Format_ARGB32_Premultiplied);
thumbnail.fill(hoverColor);
thumbnail.fill(themeColor(Theme::Token_Foreground_Muted));
QPainter thumbnailPainter(&thumbnail);
thumbnailPainter.translate(blurRadius, blurRadius);
thumbnailPainter.fillRect(thumbnailBgRect, backgroundSecondaryColor);
thumbnailPainter.translate(filterMargin, filterMargin);
thumbnailPainter.fillRect(thumbnailAreaR,
themeColor(Theme::Token_Background_Default));
thumbnailPainter.drawPixmap(thumbnailPos, pm);
thumbnailPainter.setPen(foregroundPrimaryColor);
drawPixmapOverlay(item, &thumbnailPainter, option, thumbnailBgRect);
thumbnailPainter.setPen(titleTF.color());
drawPixmapOverlay(item, &thumbnailPainter, option, thumbnailAreaR);
thumbnailPainter.setOpacity(1.0 - hoverBlurOpacity);
thumbnailPainter.fillRect(thumbnail.rect(),
themeColor(Theme::Token_Foreground_Muted));
thumbnailPainter.end();
qt_blurImage(thumbnail, hoverBlurRadius, false, false);
m_blurredThumbnail = QPixmap(tileRect.size());
QPainter blurredThumbnailPainter(&m_blurredThumbnail);
blurredThumbnailPainter.translate(-blurRadius, -blurRadius);
qt_blurImage(&blurredThumbnailPainter, thumbnail, blurRadius, false, false);
blurredThumbnailPainter.setOpacity(0.825);
blurredThumbnailPainter.fillRect(tileRect, hoverColor);
QImage mask(thumbnail.size(), QImage::Format_Grayscale8);
mask.fill(Qt::black);
QPainter maskPainter(&mask);
const QRect maskR = bgR.translated(filterMargin, filterMargin)
.adjusted(1, 1, -1, -1);
WelcomePageHelpers::drawCardBackground(&maskPainter, maskR,
Qt::white, Qt::NoPen, itemCornerRounding);
thumbnail.setAlphaChannel(mask);
m_blurredThumbnail = QPixmap::fromImage(
thumbnail.copy({filterMargin, filterMargin, bgR.width(), bgR.height()}));
}
const QPixmap thumbnailPortionPM = m_blurredThumbnail.copy(backgroundPortionRect);
painter->drawPixmap(backgroundPortionRect.topLeft(), thumbnailPortionPM);
const QPixmap thumbnailPortionPM = m_blurredThumbnail.copy(backgroundPortionR);
painter->drawPixmap(backgroundPortionR.topLeft(), thumbnailPortionPM);
} else {
painter->fillRect(backgroundPortionRect, hoverColor);
painter->fillRect(thumbnailAreaR, themeColor(Theme::Token_Foreground_Muted));
}
}
// The description Text (unhovered or hovered)
painter->setPen(textColor);
painter->setFont(sizedFont(13, option.widget)); // Title font
painter->setPen(titleTF.color());
painter->setFont(titleTF.font());
if (offset) {
// The title of the example
const QRectF nameRect = painter->boundingRect(shiftedTextRect, item->name, wrapped);
painter->drawText(nameRect, item->name, wrapped);
const QRect shiftedTitleR = thumbnailAreaR.translated(backgroundPortionR.topLeft());
const QRect titleR = painter->boundingRect(shiftedTitleR, item->name, wrapTO).toRect();
painter->drawText(shiftedTitleR, item->name, wrapTO);
painter->setOpacity(animationProgress); // "fade in" separator line and description
// The separator line below the example title.
const int ll = nameRect.height() + 3;
const QLine line = QLine(0, ll, textArea.width(), ll).translated(shiftedTextRect.topLeft());
painter->setPen(foregroundSecondaryColor);
painter->setOpacity(animationProgress); // "fade in" separator line and description
painter->drawLine(line);
const QRect hrR(titleR.x(), titleR.bottom() + 1 + ExPaddingGapS, thumbnailAreaR.width(), 1);
painter->fillRect(hrR, themeColor(Theme::Token_Stroke_Muted));
// The description text.
const int dd = ll + 5;
const QRect descRect = shiftedTextRect.adjusted(0, dd, 0, dd);
painter->setPen(textColor);
painter->setFont(descriptionFont);
painter->drawText(descRect, item->description, wrapped);
const QRect descriptionR(hrR.x(), hrR.bottom() + 1 + ExPaddingGapS,
thumbnailAreaR.width(), shiftY);
painter->setPen(descriptionTF.color());
painter->setFont(descriptionTF.font());
painter->drawText(descriptionR, item->description, wrapTO);
painter->setOpacity(1);
} else {
// The title of the example
const QString elidedName = painter->fontMetrics()
.elidedText(item->name, Qt::ElideRight, textRect.width());
painter->drawText(textRect, elidedName);
.elidedText(item->name, Qt::ElideRight, titleR.width());
painter->drawText(titleR, titleTF.drawTextFlags, elidedName);
}
// Separator line between text and 'Tags:' section
painter->setPen(foregroundSecondaryColor);
painter->drawLine(QLineF(textArea.topLeft(), textArea.topRight())
.translated(0, TagsSeparatorY));
// The 'Tags:' section
painter->setPen(foregroundPrimaryColor);
const QFont tagsFont = sizedFont(10, option.widget);
painter->setFont(tagsFont);
const QFontMetrics fm = painter->fontMetrics();
const QString tagsLabelText = Tr::tr("Tags:");
constexpr int tagsHorSpacing = 5;
const QRect tagsLabelRect =
QRect(0, 0, fm.horizontalAdvance(tagsLabelText) + tagsHorSpacing, fm.height())
.translated(textArea.x(), tagsBase);
painter->drawText(tagsLabelRect, tagsLabelText);
painter->setPen(tagsLabelTF.color());
painter->setFont(tagsLabelTF.font());
painter->drawText(tagsLabelR, tagsLabelTF.drawTextFlags, tagsLabelText);
painter->setPen(themeColor(Theme::Welcome_LinkColor));
int emptyTagRowsLeft = 2;
const QFontMetrics fm = painter->fontMetrics();
painter->setPen(tagsTF.color());
painter->setFont(tagsTF.font());
int emptyTagRowsLeft = tagsRowsCount;
int xx = 0;
int yy = 0;
const bool populateTagsRects = m_currentTagRects.empty();
for (const QString &tag : item->tags) {
const int ww = fm.horizontalAdvance(tag) + tagsHorSpacing;
if (xx + ww > textArea.width() - tagsLabelRect.width()) {
const int ww = fm.horizontalAdvance(tag);
if (xx + ww > tagsR.width()) {
if (--emptyTagRowsLeft == 0)
break;
yy += fm.lineSpacing();
xx = 0;
}
const QRect tagRect = QRect(xx, yy, ww, tagsLabelRect.height())
.translated(tagsLabelRect.topRight());
painter->drawText(tagRect, tag);
if (populateTagsRects)
m_currentTagRects.append({ tag, tagRect });
xx += ww;
const QRect tagRect = QRect(xx, yy, ww, tagsLabelR.height()).translated(tagsR.topLeft());
painter->drawText(tagRect, tagsTF.drawTextFlags, tag);
if (populateTagsRects) {
constexpr int grow = tagsHGap / 2;
const QRect tagMouseArea = tagRect.adjusted(-grow, -grow, grow, grow);
m_currentTagRects.append({ tag, tagMouseArea });
}
xx += ww + tagsHGap;
}
painter->restore();
@@ -642,7 +1041,7 @@ bool ListItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *model,
QSize ListItemDelegate::sizeHint(const QStyleOptionViewItem &, const QModelIndex &) const
{
return {GridItemWidth, GridItemHeight};
return itemSize();
}
void ListItemDelegate::drawPixmapOverlay(const ListItem *, QPainter *,
@@ -682,6 +1081,7 @@ SectionedGridView::SectionedGridView(QWidget *parent)
auto sectionedView = new QWidget;
auto layout = new QVBoxLayout;
layout->setSpacing(0);
layout->setContentsMargins(0, 0, 0, 0);
layout->addStretch(1);
sectionedView->setLayout(layout);
@@ -744,24 +1144,24 @@ void SectionedGridView::setSearchString(const QString &searchString)
filterModel->setSearchString(searchString);
}
static QWidget *createSeparator(QWidget *parent)
static QLabel *createTitleLabel(const QString &text, QWidget *parent = nullptr)
{
QWidget *line = Layouting::createHr(parent);
QSizePolicy linePolicy(QSizePolicy::Expanding, QSizePolicy::Ignored);
linePolicy.setHorizontalStretch(2);
line->setSizePolicy(linePolicy);
QPalette pal = line->palette();
pal.setColor(QPalette::Dark, Qt::transparent);
pal.setColor(QPalette::Light, themeColor(Theme::Welcome_ForegroundSecondaryColor));
line->setPalette(pal);
return line;
constexpr TextFormat headerTitleTF {Theme::Token_Text_Muted, StyleHelper::UiElementH4};
auto link = new QLabel(text, parent);
link->setFont(headerTitleTF.font());
QPalette pal = link->palette();
pal.setColor(QPalette::WindowText, headerTitleTF.color());
link->setPalette(pal);
return link;
}
static QLabel *createLinkLabel(const QString &text, QWidget *parent)
{
const QString linkColor = themeColor(Theme::Welcome_LinkColor).name();
constexpr TextFormat headerLinkTF {Theme::Token_Accent_Default, StyleHelper::UiElementH6};
const QString linkColor = themeColor(headerLinkTF.themeColor).name();
auto link = new QLabel("<a href=\"link\" style=\"color: " + linkColor + ";\">"
+ text + "</a>", parent);
link->setFont(headerLinkTF.font());
return link;
}
@@ -797,14 +1197,13 @@ ListModel *SectionedGridView::addSection(const Section &section, const QList<Lis
connect(seeAllLink, &QLabel::linkActivated, this, [this, section] { zoomInSection(section); });
using namespace Layouting;
QWidget *sectionLabel = Row {
section.name,
createSeparator(this),
createTitleLabel(section.name),
st,
seeAllLink,
Space(HSpacing),
noMargin
Space(ExVPaddingGapXl),
customMargin({0, ExPaddingGapL, 0, VPaddingL}),
}.emerge();
m_sectionLabels.append(sectionLabel);
sectionLabel->setContentsMargins(0, ItemGap, 0, 0);
auto scrollArea = qobject_cast<QScrollArea *>(widget(0));
auto vbox = qobject_cast<QVBoxLayout *>(scrollArea->widget()->layout());
@@ -858,6 +1257,7 @@ void SectionedGridView::zoomInSection(const Section &section)
auto zoomedInWidget = new QWidget(this);
auto layout = new QVBoxLayout;
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(0);
zoomedInWidget->setLayout(layout);
QLabel *backLink = createLinkLabel("&lt; " + Tr::tr("Back"), this);
@@ -868,13 +1268,12 @@ void SectionedGridView::zoomInSection(const Section &section)
});
using namespace Layouting;
QWidget *sectionLabel = Row {
section.name,
createSeparator(this),
createTitleLabel(section.name),
st,
backLink,
Space(HSpacing),
noMargin
Space(ExVPaddingGapXl),
customMargin({0, ExPaddingGapL, 0, VPaddingL}),
}.emerge();
sectionLabel->setContentsMargins(0, ItemGap, 0, 0);
auto gridView = new GridView(zoomedInWidget);
gridView->setItemDelegate(m_itemDelegate);

View File

@@ -6,10 +6,17 @@
#include "core_global.h"
#include "iwelcomepage.h"
#include <utils/fancylineedit.h>
#include <utils/stylehelper.h>
#include <utils/theme/theme.h>
#include <QComboBox>
#include <QElapsedTimer>
#include <QLabel>
#include <QListView>
#include <QPen>
#include <QPointer>
#include <QPushButton>
#include <QSortFilterProxyModel>
#include <QStackedWidget>
#include <QStyledItemDelegate>
@@ -24,31 +31,111 @@ namespace Core {
namespace WelcomePageHelpers {
constexpr int HSpacing = 20;
constexpr int ItemGap = 4;
constexpr QSize WelcomeThumbnailSize(214, 160);
constexpr int GridItemGap = 3 * ItemGap;
constexpr int GridItemWidth = 240 + GridItemGap; // Extra GridItemGap as "spacing"
constexpr int GridItemHeight = GridItemWidth;
constexpr QSize GridItemImageSize(GridItemWidth - GridItemGap
- 2 * (GridItemGap + 1), // Horizontal margins + 1 pixel
GridItemHeight - GridItemGap
- GridItemGap - 1 // Upper margin + 1 pixel
- 67); // Bottom margin (for title + tags)
class CORE_EXPORT TextFormat {
public:
QColor color() const
{
return Utils::creatorTheme()->color(themeColor);
}
CORE_EXPORT QWidget *panelBar(QWidget *parent = nullptr);
QFont font(bool underlined = false) const
{
QFont result = Utils::StyleHelper::uiFont(uiElement);
result.setUnderline(underlined);
return result;
}
int lineHeight() const
{
return Utils::StyleHelper::uiFontLineHeight(uiElement);
}
const Utils::Theme::Color themeColor;
const Utils::StyleHelper::UiElement uiElement;
const int drawTextFlags = Qt::AlignLeft | Qt::AlignBottom | Qt::TextDontClip;
};
CORE_EXPORT void setBackgroundColor(QWidget *widget, Utils::Theme::Color colorRole);
constexpr qreal defaultCardBackgroundRounding = 3.75;
CORE_EXPORT void drawCardBackground(QPainter *painter, const QRectF &rect,
const QBrush &fill, const QPen &pen = QPen(Qt::NoPen),
qreal rounding = 5.0);
qreal rounding = defaultCardBackgroundRounding);
CORE_EXPORT QWidget *createRule(Qt::Orientation orientation, QWidget *parent = nullptr);
} // namespace WelcomePageHelpers
class CORE_EXPORT SearchBox : public WelcomePageFrame
class CORE_EXPORT Button : public QPushButton
{
public:
enum Role {
MediumPrimary,
MediumSecondary,
SmallPrimary,
SmallSecondary,
SmallList,
SmallLink,
};
explicit Button(const QString &text, Role role, QWidget *parent = nullptr);
QSize minimumSizeHint() const override;
void setPixmap(const QPixmap &newPixmap);
protected:
void paintEvent(QPaintEvent *event) override;
private:
void updateMargins();
const Role m_role = MediumPrimary;
QPixmap m_pixmap;
};
class CORE_EXPORT Label : public QLabel
{
public:
enum Role {
Primary,
Secondary,
};
explicit Label(const QString &text, Role role, QWidget *parent = nullptr);
private:
const Role m_role = Primary;
};
class CORE_EXPORT SearchBox : public QLineEdit
{
public:
explicit SearchBox(QWidget *parent = nullptr);
Utils::FancyLineEdit *m_lineEdit = nullptr;
QSize minimumSizeHint() const override;
protected:
void paintEvent(QPaintEvent *event) override;
protected:
void enterEvent(QEnterEvent *event) override;
void leaveEvent(QEvent *event) override;
};
class CORE_EXPORT ComboBox : public QComboBox
{
public:
explicit ComboBox(QWidget *parent = nullptr);
QSize sizeHint() const override;
protected:
void paintEvent(QPaintEvent *event) override;
protected:
void enterEvent(QEnterEvent *event) override;
void leaveEvent(QEvent *event) override;
};
class CORE_EXPORT GridView : public QListView
@@ -146,7 +233,9 @@ class CORE_EXPORT ListItemDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
ListItemDelegate();
ListItemDelegate() = default;
static QSize itemSize();
void paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const override;
@@ -165,13 +254,6 @@ protected:
void goon();
const QColor backgroundPrimaryColor;
const QColor backgroundSecondaryColor;
const QColor foregroundPrimaryColor;
const QColor foregroundSecondaryColor;
const QColor hoverColor;
const QColor textColor;
private:
mutable QPersistentModelIndex m_previousIndex;
mutable QElapsedTimer m_startTime;

View File

@@ -242,27 +242,6 @@ void CppHighlighter::highlightBlock(const QString &text)
TextDocumentLayout::setFoldingIndent(currentBlock(), foldingIndent);
// optimization: if only the brace depth changes, we adjust subsequent blocks
// to have QSyntaxHighlighter stop the rehighlighting
int currentState = currentBlockState();
if (currentState != -1) {
int oldState = currentState & 0xff;
int oldBraceDepth = currentState >> 8;
if (oldState == tokenize.state() && oldBraceDepth != braceDepth) {
TextDocumentLayout::FoldValidator foldValidor;
foldValidor.setup(qobject_cast<TextDocumentLayout *>(document()->documentLayout()));
int delta = braceDepth - oldBraceDepth;
QTextBlock block = currentBlock().next();
while (block.isValid() && block.userState() != -1) {
TextDocumentLayout::changeBraceDepth(block, delta);
TextDocumentLayout::changeFoldingIndent(block, delta);
foldValidor.process(block);
block = block.next();
}
foldValidor.finalize();
}
}
setCurrentBlockState((braceDepth << 8) | tokenize.state());
TextDocumentLayout::setExpectedRawStringSuffix(currentBlock(),
tokenize.expectedRawStringSuffix());

View File

@@ -20,10 +20,11 @@
#include <projectexplorer/projectexplorer.h>
#include <projectexplorer/projectmanager.h>
#include <texteditor/texteditor.h>
#include <texteditor/codeassist/iassistproposal.h>
#include <texteditor/codeassist/iassistproposalmodel.h>
#include <texteditor/storagesettings.h>
#include <texteditor/syntaxhighlighterrunner.h>
#include <texteditor/texteditor.h>
#include <utils/environment.h>
#include <utils/fileutils.h>
@@ -221,6 +222,17 @@ bool TestCase::openCppEditor(const FilePath &filePath, TextEditor::BaseTextEdito
s.m_addFinalNewLine = false;
e->textDocument()->setStorageSettings(s);
}
if (!QTest::qWaitFor(
[e] {
return e->editorWidget()
->textDocument()
->syntaxHighlighterRunner()
->syntaxInfoUpdated();
},
5000))
return false;
if (editorWidget) {
if (CppEditorWidget *w = dynamic_cast<CppEditorWidget *>(e->editorWidget())) {
*editorWidget = w;

View File

@@ -396,6 +396,12 @@ F2TestCase::F2TestCase(CppEditorAction action,
BaseTextEditor *currentTextEditor = dynamic_cast<BaseTextEditor*>(currentEditor);
QVERIFY(currentTextEditor);
if (useClangd) {
QEXPECT_FAIL("matchFunctionSignatureFuzzy1Forward", "clangd returns decl loc", Abort);
QEXPECT_FAIL("matchFunctionSignatureFuzzy2Forward", "clangd returns decl loc", Abort);
QEXPECT_FAIL("matchFunctionSignatureFuzzy1Backward", "clangd returns def loc", Abort);
QEXPECT_FAIL("matchFunctionSignatureFuzzy2Backward", "clangd returns def loc", Abort);
}
QCOMPARE(currentTextEditor->document()->filePath(), targetTestFile->filePath());
int expectedLine, expectedColumn;
if (useClangd && expectedVirtualFunctionProposal.size() == 1) {
@@ -494,8 +500,11 @@ void FollowSymbolTest::initTestCase()
return;
// Find suitable kit.
// Qt is not actually required for the tests, but we need it for consistency with
// configureAsExampleProject().
// FIXME: Make configureAsExampleProject() work with non-Qt kits.
F2TestCase::m_testKit = Utils::findOr(KitManager::kits(), nullptr, [](const Kit *k) {
return k->isValid();
return k->isValid() && !k->hasWarning() && k->value("QtSupport.QtInformation").isValid();
});
if (!F2TestCase::m_testKit)
QSKIP("This test requires at least one kit to be present");

View File

@@ -29,14 +29,6 @@ using namespace Utils;
namespace ExtensionManager::Internal {
static QWidget *createVr(QWidget *parent = nullptr)
{
auto vr = new QWidget(parent);
vr->setFixedWidth(1);
setBackgroundColor(vr, Theme::Token_Stroke_Subtle);
return vr;
}
class CollapsingWidget : public QWidget
{
public:
@@ -79,13 +71,13 @@ ExtensionManagerWidget::ExtensionManagerWidget()
using namespace Layouting;
Row {
createVr(),
WelcomePageHelpers::createRule(Qt::Vertical),
m_secondaryDescription,
noMargin(), spacing(0),
}.attachTo(m_secondarDescriptionWidget);
Row {
createVr(),
WelcomePageHelpers::createRule(Qt::Vertical),
Row {
m_primaryDescription,
noMargin(),
@@ -95,13 +87,13 @@ ExtensionManagerWidget::ExtensionManagerWidget()
}.attachTo(descriptionColumns);
Row {
Space(WelcomePageHelpers::HSpacing),
Space(StyleHelper::SpacingTokens::ExVPaddingGapXl),
m_leftColumn,
descriptionColumns,
noMargin(), spacing(0),
}.attachTo(this);
setBackgroundColor(this, Theme::Token_Background_Default);
WelcomePageHelpers::setBackgroundColor(this, Theme::Token_Background_Default);
connect(m_leftColumn, &ExtensionsBrowser::itemSelected,
this, &ExtensionManagerWidget::updateView);
@@ -132,7 +124,7 @@ void ExtensionManagerWidget::updateView(const QModelIndex &current,
"margin-left: %3px; margin-right: %3px;")
.arg(creatorTheme()->color(Theme::Token_Text_Default).name())
.arg(creatorTheme()->color(Theme::Token_Background_Muted).name())
.arg(WelcomePageHelpers::HSpacing);
.arg(StyleHelper::SpacingTokens::ExVPaddingGapXl);
const QString htmlStart = QString(R"(
<html>
<body style="%1">

View File

@@ -41,7 +41,7 @@ using PluginSpecList = QList<const PluginSpec *>;
using Tags = QStringList;
constexpr QSize itemSize = {330, 86};
constexpr int gapSize = 2 * WelcomePageHelpers::GridItemGap;
constexpr int gapSize = StyleHelper::SpacingTokens::ExVPaddingGapXl;
constexpr QSize cellSize = {itemSize.width() + gapSize, itemSize.height() + gapSize};
enum Role {
@@ -62,16 +62,6 @@ ItemData itemData(const QModelIndex &index)
};
}
void setBackgroundColor(QWidget *widget, Theme::Color colorRole)
{
QPalette palette = creatorTheme()->palette();
palette.setColor(QPalette::Window,
creatorTheme()->color(colorRole));
widget->setPalette(palette);
widget->setBackgroundRole(QPalette::Window);
widget->setAutoFillBackground(true);
}
static QColor colorForExtensionName(const QString &name)
{
const size_t hash = qHash(name);
@@ -390,7 +380,7 @@ public:
}
{
constexpr int textX = 80;
constexpr int rightMargin = 2 * WelcomePageHelpers::ItemGap;
constexpr int rightMargin = StyleHelper::SpacingTokens::ExVPaddingGapXl;
constexpr int maxTextWidth = itemSize.width() - textX - rightMargin;
constexpr Qt::TextElideMode elideMode = Qt::ElideRight;
@@ -440,8 +430,7 @@ ExtensionsBrowser::ExtensionsBrowser()
m_searchBox = new Core::SearchBox;
m_searchBox->setFixedWidth(itemSize.width());
m_updateButton = new WelcomePageButton;
m_updateButton->setText(Tr::tr("Install..."));
m_updateButton = new Button(Tr::tr("Install..."), Button::MediumPrimary);
m_filterProxyModel = new QSortFilterProxyModel(this);
m_filterProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
@@ -469,9 +458,10 @@ ExtensionsBrowser::ExtensionsBrowser()
noMargin(), spacing(0),
}.attachTo(this);
setBackgroundColor(this, Theme::Token_Background_Default);
setBackgroundColor(m_extensionsView, Theme::Token_Background_Default);
setBackgroundColor(m_extensionsView->viewport(), Theme::Token_Background_Default);
WelcomePageHelpers::setBackgroundColor(this, Theme::Token_Background_Default);
WelcomePageHelpers::setBackgroundColor(m_extensionsView, Theme::Token_Background_Default);
WelcomePageHelpers::setBackgroundColor(m_extensionsView->viewport(),
Theme::Token_Background_Default);
auto updateModel = [this] {
m_model.reset(extensionsModel());
@@ -488,7 +478,7 @@ ExtensionsBrowser::ExtensionsBrowser()
connect(ExtensionSystem::PluginManager::instance(),
&ExtensionSystem::PluginManager::pluginsChanged, this, updateModel);
connect(m_searchBox->m_lineEdit, &Utils::FancyLineEdit::textChanged,
connect(m_searchBox, &QLineEdit::textChanged,
m_filterProxyModel, &QSortFilterProxyModel::setFilterWildcard);
}

View File

@@ -10,7 +10,9 @@
QT_BEGIN_NAMESPACE
class QItemSelectionModel;
class QLineEdit;
class QListView;
class QPushButton;
class QSortFilterProxyModel;
QT_END_NAMESPACE
@@ -19,11 +21,6 @@ namespace ExtensionSystem
class PluginSpec;
}
namespace Core {
class SearchBox;
class WelcomePageButton;
}
namespace ExtensionManager::Internal {
using PluginSpecList = QList<const ExtensionSystem::PluginSpec *>;
@@ -42,7 +39,6 @@ struct ItemData {
};
ItemData itemData(const QModelIndex &index);
void setBackgroundColor(QWidget *widget, Utils::Theme::Color colorRole);
class ExtensionsBrowser final : public QWidget
{
@@ -61,8 +57,8 @@ private:
int extraListViewWidth() const; // Space for scrollbar, etc.
QScopedPointer<QStandardItemModel> m_model;
Core::SearchBox *m_searchBox;
Core::WelcomePageButton *m_updateButton;
QLineEdit *m_searchBox;
QPushButton *m_updateButton;
QListView *m_extensionsView;
QItemSelectionModel *m_selectionModel = nullptr;
QSortFilterProxyModel *m_filterProxyModel;

View File

@@ -189,7 +189,6 @@ void GlslHighlighter::highlightBlock(const QString &text)
TextDocumentLayout::setParentheses(currentBlock(), parentheses);
// if the block is ifdefed out, we only store the parentheses, but
// do not adjust the brace depth.
if (TextDocumentLayout::ifdefedOut(currentBlock())) {
braceDepth = initialBraceDepth;
@@ -198,23 +197,6 @@ void GlslHighlighter::highlightBlock(const QString &text)
TextDocumentLayout::setFoldingIndent(currentBlock(), foldingIndent);
// optimization: if only the brace depth changes, we adjust subsequent blocks
// to have QSyntaxHighlighter stop the rehighlighting
int currentState = currentBlockState();
if (currentState != -1) {
int oldState = currentState & 0xff;
int oldBraceDepth = currentState >> 8;
if (oldState == lex.state() && oldBraceDepth != braceDepth) {
int delta = braceDepth - oldBraceDepth;
QTextBlock block = currentBlock().next();
while (block.isValid() && block.userState() != -1) {
TextDocumentLayout::changeBraceDepth(block, delta);
TextDocumentLayout::changeFoldingIndent(block, delta);
block = block.next();
}
}
}
setCurrentBlockState((braceDepth << 8) | lex.state());
}

View File

@@ -269,7 +269,7 @@ void SectionedProducts::onImageDownloadFinished(QNetworkReply *reply)
if (pixmap.loadFromData(data, imageFormat.toLatin1())) {
const QString url = imageUrl.toString();
const int dpr = qApp->devicePixelRatio();
pixmap = pixmap.scaled(WelcomePageHelpers::GridItemImageSize * dpr,
pixmap = pixmap.scaled(WelcomePageHelpers::WelcomeThumbnailSize * dpr,
Qt::KeepAspectRatio,
Qt::SmoothTransformation);
pixmap.setDevicePixelRatio(dpr);

View File

@@ -9,9 +9,10 @@
#include <coreplugin/welcomepagehelper.h>
#include <utils/fancylineedit.h>
#include <utils/layoutbuilder.h>
#include <utils/progressindicator.h>
#include <utils/theme/theme.h>
#include <utils/qtcassert.h>
#include <utils/theme/theme.h>
#include <QDesktopServices>
#include <QLabel>
@@ -44,38 +45,34 @@ class QtMarketplacePageWidget : public QWidget
public:
QtMarketplacePageWidget()
{
auto searchBox = new Core::SearchBox(this);
m_searcher = searchBox->m_lineEdit;
m_searcher = new Core::SearchBox(this);
m_searcher->setPlaceholderText(Tr::tr("Search in Marketplace..."));
auto vbox = new QVBoxLayout(this);
vbox->setContentsMargins(0, 0, 0, Core::WelcomePageHelpers::ItemGap);
vbox->setSpacing(Core::WelcomePageHelpers::ItemGap);
auto searchBar = Core::WelcomePageHelpers::panelBar();
auto hbox = new QHBoxLayout(searchBar);
hbox->setContentsMargins(Core::WelcomePageHelpers::HSpacing, 0,
Core::WelcomePageHelpers::HSpacing, 0);
hbox->addWidget(searchBox);
vbox->addWidget(searchBar);
m_errorLabel = new QLabel(this);
m_errorLabel->setVisible(false);
vbox->addWidget(m_errorLabel);
auto resultWidget = new QWidget(this);
auto resultHBox = new QHBoxLayout(resultWidget);
resultHBox->setContentsMargins(Core::WelcomePageHelpers::HSpacing, 0, 0, 0);
m_sectionedProducts = new SectionedProducts(this);
auto progressIndicator = new Utils::ProgressIndicator(ProgressIndicatorSize::Large, this);
progressIndicator->attachToWidget(m_sectionedProducts);
progressIndicator->hide();
resultHBox->addWidget(m_sectionedProducts);
vbox->addWidget(resultWidget);
using namespace StyleHelper::SpacingTokens;
using namespace Layouting;
Column {
Row {
m_searcher,
customMargin({0, 0, ExVPaddingGapXl, 0}),
},
m_sectionedProducts,
spacing(VPaddingL),
customMargin({ExVPaddingGapXl, ExVPaddingGapXl, 0, 0}),
}.attachTo(this);
connect(m_sectionedProducts, &SectionedProducts::toggleProgressIndicator,
progressIndicator, &Utils::ProgressIndicator::setVisible);
connect(m_sectionedProducts, &SectionedProducts::errorOccurred, this,
[this, progressIndicator, searchBox](int, const QString &message) {
[this, progressIndicator](int, const QString &message) {
progressIndicator->hide();
progressIndicator->deleteLater();
m_errorLabel->setAlignment(Qt::AlignHCenter);
@@ -89,7 +86,7 @@ public:
"</p><br/><p><small><i>Error: %1</i></small></p>").arg(message);
m_errorLabel->setText(txt);
m_errorLabel->setVisible(true);
searchBox->setVisible(false);
m_searcher->setVisible(false);
connect(m_errorLabel, &QLabel::linkActivated,
this, []() { QDesktopServices::openUrl(QUrl("https://marketplace.qt.io")); });
});

View File

@@ -21,6 +21,7 @@
#include <utils/algorithm.h>
#include <utils/fileutils.h>
#include <utils/icon.h>
#include <utils/layoutbuilder.h>
#include <utils/qtcassert.h>
#include <utils/stringutils.h>
#include <utils/stylehelper.h>
@@ -28,9 +29,6 @@
#include <QAbstractItemDelegate>
#include <QAction>
#include <QBoxLayout>
#include <QDir>
#include <QFileInfo>
#include <QHeaderView>
#include <QHelpEvent>
#include <QLabel>
@@ -42,16 +40,52 @@
using namespace Core;
using namespace Core::WelcomePageHelpers;
using namespace Utils;
using namespace Utils::StyleHelper::SpacingTokens;
const int LINK_HEIGHT = 35;
const int TEXT_OFFSET_HORIZONTAL = 36;
const int SESSION_LINE_HEIGHT = 28;
const int SESSION_ARROW_RECT_WIDTH = 24;
const char PROJECT_BASE_ID[] = "Welcome.OpenRecentProject";
namespace ProjectExplorer {
namespace Internal {
constexpr TextFormat projectNameTF {Theme::Token_Accent_Default, StyleHelper::UiElementH5};
constexpr TextFormat projectPathTF {Theme::Token_Text_Muted, StyleHelper::UiElementIconActive};
constexpr TextFormat sessionNameTF {projectNameTF.themeColor, projectNameTF.uiElement};
constexpr TextFormat sessionProjetNameTF {Theme::Token_Text_Default, projectNameTF.uiElement};
constexpr TextFormat shortcutNumberTF {Theme::Token_Text_Default,
StyleHelper::UiElementCaptionStrong,
Qt::AlignCenter | Qt::TextDontClip};
constexpr TextFormat actionTF {Theme::Token_Text_Default, StyleHelper::UiElementIconActive,
Qt::AlignCenter | Qt::TextDontClip};
constexpr TextFormat actionDisabledTF {Theme::Token_Text_Subtle, actionTF.uiElement,
actionTF.drawTextFlags};
constexpr int shortcutNumberWidth = 16;
constexpr int actionSepWidth = 1;
constexpr int sessionScrollBarGap = HPaddingXs;
static int s(const int metric)
{
constexpr int shrinkWhenAbove = 150; // Above this session count, increasingly reduce scale
constexpr qreal maxScale = 1.0; // Spacings as defined by design
constexpr qreal minScale = 0.2; // Maximum "condensed" layout
const int sessionsCount = SessionManager::sessionsCount();
const qreal scaling = sessionsCount < shrinkWhenAbove
? maxScale
: qMax(minScale,
maxScale - (sessionsCount - shrinkWhenAbove) * 0.065);
return int(qMax(1.0, scaling * metric));
}
static int itemSpacing()
{
return qMax(int(s(VGapL)), VGapS);
}
static bool withIcon()
{
return s(100) > 60; // Hide icons if spacings are scaled to below 60%
}
ProjectModel::ProjectModel(QObject *parent)
: QAbstractListModel(parent)
{
@@ -61,7 +95,7 @@ ProjectModel::ProjectModel(QObject *parent)
int ProjectModel::rowCount(const QModelIndex &) const
{
return m_projects.count();
return int(m_projects.count());
}
QVariant ProjectModel::data(const QModelIndex &index, int role) const
@@ -190,21 +224,24 @@ static QColor themeColor(Theme::Color role)
return Utils::creatorTheme()->color(role);
}
static QFont sizedFont(int size, const QWidget *widget,
bool underline = false)
{
QFont f = widget->font();
f.setPixelSize(size);
f.setUnderline(underline);
return f;
}
static QPixmap pixmap(const QString &id, const Theme::Color &color)
static QPixmap pixmap(const QString &id, const Theme::Color color)
{
const QString fileName = QString(":/welcome/images/%1.png").arg(id);
return Icon({{FilePath::fromString(fileName), color}}, Icon::Tint).pixmap();
}
static void drawBackgroundRect(QPainter *painter, const QRectF &rect, bool hovered)
{
const QColor fill(themeColor(hovered ? Theme::Token_Foreground_Muted
: Theme::Token_Background_Muted));
const QPen pen(themeColor(hovered ? Theme::Token_Foreground_Muted
: Theme::Token_Stroke_Subtle));
const qreal rounding = s(defaultCardBackgroundRounding * 1000) / 1000.0;
const qreal saneRounding = rounding <= 2 ? 0 : rounding;
WelcomePageHelpers::drawCardBackground(painter, rect, fill, pen, saneRounding);
}
class BaseDelegate : public QAbstractItemDelegate
{
protected:
@@ -244,6 +281,11 @@ protected:
class SessionDelegate : public BaseDelegate
{
protected:
bool expanded(const QModelIndex &idx) const
{
return m_expandedSessions.contains(idx.data(Qt::DisplayRole).toString());
}
QString entryType() override
{
return Tr::tr("session", "Appears in \"Open session <name>\"");
@@ -252,150 +294,287 @@ protected:
{
// in expanded state bottom contains 'Clone', 'Rename', etc links, where the tool tip
// would be confusing
const bool expanded = m_expandedSessions.contains(idx.data(Qt::DisplayRole).toString());
return expanded ? itemRect.adjusted(0, 0, 0, -LINK_HEIGHT) : itemRect;
return expanded(idx) ? itemRect.adjusted(0, 0, 0, -actionButtonHeight()) : itemRect;
}
int shortcutRole() const override
{
return SessionModel::ShortcutRole;
}
static int actionButtonHeight()
{
return s(VPaddingXxs) + actionTF.lineHeight() + s(VPaddingXxs);
}
static const QPixmap &icon()
{
static const QPixmap icon = pixmap("session", Theme::Token_Text_Muted);
return icon;
}
int shortcutRole() const override { return SessionModel::ShortcutRole; }
public:
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &idx) const final
void paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &idx) const final
{
static const QPixmap sessionIcon = pixmap("session", Theme::Welcome_ForegroundSecondaryColor);
const QRect rc = option.rect;
const QString sessionName = idx.data(Qt::DisplayRole).toString();
// visible on withIcon() Gap + arrow visible on hover Extra margin right of project item
// | | |
// +----------+----------+ +--------+-------+ +----------+----------+
// | | | | | |
//
// +------------+--------+--------+------------+--------+-------------+--------+-------+------------+---------------------+ --+
// | | | |(VPaddingXs)| |(VPaddingXs) | | | | | |
// | | | +------------+ +-------------+ | | | | |
// |(HPaddingXs)|<number>|(HGapXs)| <icon> |(HGapXs)|<sessionName>|(HGapXs)|<arrow>| | | +-- Header
// | |(16x16) | +------------+ +-------------+ | | | | |
// | | | |(VPaddingXs)| |(VPaddingXs) | | | | | |
// |------------+--------+--------+------------+--------+-------------+--------+-------+ | | --+
// | +-- | (VPaddingXs) | | | |
// | | +------------------------------+(HPaddingXs)| | |
// | | | <projectName> | | | |
// | | +------------------------------+ | | |
// | Per project in session --+ | (EXSPaddingGapS) | |(sessionScrollBarGap)| |
// | | +------------------------------+ | | |
// | | | <projectPath> | | | |
// | | +------------------------------+ | | +-- Expansion
// | +-- | (VPaddingXs) | | | |
// +----------------------------------------------+------------------------------------+------------+ | |
// | (VPaddingXs) | | |
// +----------------------------------------+--------------+----------------------------------------+ | |
// +-- | <cloneButton>|<renameButton>|<deleteButton> | | |
// | +----------------------------------------+--------------+----------------------------------------+ | |
// | | (VPaddingXs) | | |
// | +------------------------------------------------------------------------------------------------+---------------------+ --+
// | | (VGapL) | +-- Gap between session items
// | +----------------------------------------------------------------------------------------------------------------------+ --+
// |
// \ session action "buttons" and dividers
// +-----------------------------------------------+--------+---------+--------+
// | (VGapXs) | | | |
// +----------------+-------------+----------------+ | | |
// |(EXSPaddingGapM)|<buttonLabel>|(EXSPaddingGapM)|(HGapXs)|<divider>|(HGapXs)|
// +----------------+-------------+----------------+ |(w:1) | |
// | (VGapXs) | | | |
// +-----------------------------------------------+--------+---------+--------+
//
// | |
// +-------------+-------------+
// |
// omitted after last button
const QPoint mousePos = option.widget->mapFromGlobal(QCursor::pos());
//const bool hovered = option.state & QStyle::State_MouseOver;
const bool hovered = option.rect.contains(mousePos);
const bool expanded = m_expandedSessions.contains(sessionName);
painter->fillRect(rc, themeColor(Theme::Welcome_BackgroundSecondaryColor));
painter->fillRect(rc.adjusted(0, 0, 0, -ItemGap),
hovered ? hoverColor : backgroundPrimaryColor);
const bool expanded = this->expanded(idx);
const int x = rc.x();
const int x1 = x + TEXT_OFFSET_HORIZONTAL;
const int y = rc.y();
const int firstBase = y + 18;
const QRect bgR = option.rect.adjusted(0, 0, -sessionScrollBarGap, -itemSpacing());
const QRect hdR(bgR.topLeft(), QSize(bgR.width(), expanded ? headerHeight()
: bgR.height()));
painter->drawPixmap(x + 11, y + 6, sessionIcon);
const QSize iconS = icon().deviceIndependentSize().toSize();
static const QPixmap arrow = Icon({{FilePath::fromString(":/core/images/expandarrow"),
Theme::Token_Text_Muted}}, Icon::Tint).pixmap();
const QSize arrowS = arrow.deviceIndependentSize().toSize();
const bool arrowVisible = hovered || expanded;
if (hovered && !expanded) {
const QRect arrowRect = rc.adjusted(rc.width() - SESSION_ARROW_RECT_WIDTH, 0, 0, 0);
const bool arrowRectHovered = arrowRect.contains(mousePos);
painter->fillRect(arrowRect.adjusted(0, 0, 0, -ItemGap),
arrowRectHovered ? hoverColor : backgroundPrimaryColor);
const QString sessionName = idx.data(Qt::DisplayRole).toString();
const int x = bgR.x();
const int y = bgR.y();
const int numberX = x + s(HPaddingXs);
const int iconX = numberX + shortcutNumberWidth + s(HGapXs);
const int arrowX = bgR.right() - s(HPaddingXs) - arrowS.width();
const QRect arrowHoverR(arrowX - s(HGapXs) + 1, y,
s(HGapXs) + arrowS.width() + s(HPaddingXs), hdR.height());
const int textX = withIcon() ? iconX + iconS.width() + s(HGapXs) : iconX;
const int iconY = y + (hdR.height() - iconS.height()) / 2;
const int arrowY = y + (hdR.height() - arrowS.height()) / 2;
{
drawBackgroundRect(painter, bgR, hovered);
}
if (hovered || expanded) {
static const QPixmap arrowUp = pixmap("expandarrow",Theme::Welcome_ForegroundSecondaryColor);
static const QPixmap arrowDown = QPixmap::fromImage(arrowUp.toImage().mirrored(false, true));
painter->drawPixmap(rc.right() - 19, y + 6, expanded ? arrowDown : arrowUp);
}
if (idx.row() < 9) {
painter->setPen(foregroundSecondaryColor);
painter->setFont(sizedFont(10, option.widget));
painter->drawText(x + 3, firstBase, QString::number(idx.row() + 1));
painter->setPen(shortcutNumberTF.color());
painter->setFont(shortcutNumberTF.font());
const QRect numberR(numberX, y, shortcutNumberWidth, hdR.height());
const QString numberString = QString::number(idx.row() + 1);
painter->drawText(numberR, shortcutNumberTF.drawTextFlags, numberString);
}
if (withIcon()) {
painter->drawPixmap(iconX, iconY, icon());
}
{
const bool isLastSession = idx.data(SessionModel::LastSessionRole).toBool();
const bool isActiveSession = idx.data(SessionModel::ActiveSessionRole).toBool();
const bool isDefaultVirgin = SessionManager::isDefaultVirgin();
const int sessionNameWidth = hdR.right()
- (arrowVisible ? arrowHoverR.width(): s(HPaddingXs))
- textX;
const int sessionNameHeight = sessionNameTF.lineHeight();
const int sessionNameY = y + (hdR.height() - sessionNameHeight) / 2;
const QRect sessionNameR(textX, sessionNameY, sessionNameWidth, sessionNameHeight);
QString fullSessionName = sessionName;
if (isLastSession && isDefaultVirgin)
fullSessionName = Tr::tr("%1 (last session)").arg(fullSessionName);
if (isActiveSession && !isDefaultVirgin)
fullSessionName = Tr::tr("%1 (current session)").arg(fullSessionName);
const QRect switchRect = QRect(x, y, rc.width() - SESSION_ARROW_RECT_WIDTH, SESSION_LINE_HEIGHT);
const bool switchActive = switchRect.contains(mousePos);
const int textSpace = rc.width() - TEXT_OFFSET_HORIZONTAL - 6;
const int sessionNameTextSpace =
textSpace -(hovered || expanded ? SESSION_ARROW_RECT_WIDTH : 0);
painter->setPen(linkColor);
painter->setFont(sizedFont(13, option.widget, switchActive));
const QRect switchR(x, y, hdR.width() - arrowHoverR.width(),
hdR.height() + s(VGapL));
const bool switchActive = switchR.contains(mousePos);
painter->setPen(sessionNameTF.color());
painter->setFont(sessionNameTF.font(switchActive));
const QString fullSessionNameElided = painter->fontMetrics().elidedText(
fullSessionName, Qt::ElideRight, sessionNameTextSpace);
painter->drawText(x1, firstBase, fullSessionNameElided);
fullSessionName, Qt::ElideRight, sessionNameWidth);
painter->drawText(sessionNameR, sessionNameTF.drawTextFlags,
fullSessionNameElided);
if (switchActive)
m_activeSwitchToRect = switchRect;
m_activeSwitchToRect = switchR;
}
if (arrowVisible) {
if (arrowHoverR.adjusted(0, 0, 0, s(VGapL)).contains(mousePos)) {
m_activeExpandRect = arrowHoverR;
} else {
painter->save();
painter->setClipRect(arrowHoverR);
drawBackgroundRect(painter, bgR, false);
painter->restore();
}
static const QPixmap arrowDown =
QPixmap::fromImage(arrow.toImage().mirrored(false, true));
painter->drawPixmap(arrowX, arrowY, expanded ? arrowDown : arrow);
}
int yy = hdR.bottom();
if (expanded) {
painter->setPen(textColor);
painter->setFont(sizedFont(12, option.widget));
const FilePaths projects = ProjectManager::projectsForSessionName(sessionName);
int yy = firstBase + SESSION_LINE_HEIGHT - 3;
QFontMetrics fm(option.widget->font());
for (const FilePath &projectPath : projects) {
// Project name.
QString completeBase = projectPath.completeBaseName();
painter->setPen(textColor);
painter->drawText(x1, yy, fm.elidedText(completeBase, Qt::ElideMiddle, textSpace));
yy += 18;
const QFont projectNameFont = sessionProjetNameTF.font();
const QFontMetrics projectNameFm(projectNameFont);
const int projectNameLineHeight = sessionProjetNameTF.lineHeight();
const QFont projectPathFont = projectPathTF.font();
const QFontMetrics projectPathFm(projectPathFont);
const int projectPathLineHeight = projectPathTF.lineHeight();
const int textWidth = bgR.right() - s(HPaddingXs) - textX;
// Project path.
const FilePaths projects = ProjectManager::projectsForSessionName(sessionName);
for (const FilePath &projectPath : projects) {
yy += s(VPaddingXs);
{
painter->setFont(projectNameFont);
painter->setPen(sessionProjetNameTF.color());
const QRect projectNameR(textX, yy, textWidth, projectNameLineHeight);
const QString projectNameElided =
projectNameFm.elidedText(projectPath.completeBaseName(), Qt::ElideMiddle,
textWidth);
painter->drawText(projectNameR, sessionProjetNameTF.drawTextFlags,
projectNameElided);
yy += projectNameLineHeight;
yy += s(ExPaddingGapS);
}
{
const QString displayPath =
projectPath.osType() == OsTypeWindows ? projectPath.displayName()
: projectPath.withTildeHomePath();
painter->setPen(foregroundPrimaryColor);
painter->drawText(x1, yy, fm.elidedText(displayPath, Qt::ElideMiddle, textSpace));
yy += 22;
painter->setFont(projectPathFont);
painter->setPen(projectPathTF.color());
const QRect projectPathR(textX, yy, textWidth, projectPathLineHeight);
const QString projectPathElided =
projectPathFm.elidedText(displayPath, Qt::ElideMiddle, textWidth);
painter->drawText(projectPathR, projectPathTF.drawTextFlags,
projectPathElided);
yy += projectPathLineHeight;
}
yy += s(VPaddingXs);
}
yy += s(VGapXs);
yy += 3;
int xx = x1;
const QStringList actions = {
Tr::tr("Clone"),
Tr::tr("Rename"),
Tr::tr("Delete")
Tr::tr("Delete"),
};
for (int i = 0; i < 3; ++i) {
const QFont actionFont = actionTF.font();
const QFontMetrics actionFm(actionTF.font());
const int gapWidth = s(HGapXs) + actionSepWidth + s(HGapXs);
int actionsTotalWidth = gapWidth * int(actions.count() - 1); // dividers
const auto textWidths = Utils::transform(actions, [&] (const QString &action) {
const int width = actionFm.horizontalAdvance(action);
actionsTotalWidth += s(ExPaddingGapM) + width + s(ExPaddingGapM);
return width;
});
const int buttonHeight = this->actionButtonHeight();
int xx = (bgR.width() - actionsTotalWidth) / 2;
for (int i = 0; i < actions.count(); ++i) {
const QString &action = actions.at(i);
const int ww = fm.horizontalAdvance(action);
const int spacing = 7; // Between action link and separator line
const QRect actionRect =
QRect(xx, yy - 10, ww, 15).adjusted(-spacing, -spacing, spacing, spacing);
const bool isForcedDisabled = (i != 0 && sessionName == "default");
const bool isActive = actionRect.contains(mousePos) && !isForcedDisabled;
painter->setPen(isForcedDisabled ? disabledLinkColor : linkColor);
painter->setFont(sizedFont(12, option.widget, isActive));
painter->drawText(xx, yy, action);
if (i < 2) {
xx += ww + 2 * spacing;
int pp = xx - spacing;
painter->setPen(textColor);
painter->drawLine(pp, yy - 10, pp, yy);
const int ww = textWidths.at(i);
const QRect actionR(xx, yy, s(ExPaddingGapM) + ww + s(ExPaddingGapM), buttonHeight);
const bool isDisabled = i > 0 && SessionManager::isDefaultSession(sessionName);
const bool isActive = actionR.adjusted(-s(VPaddingXs), 0, s(VPaddingXs) + 1, 0)
.contains(mousePos) && !isDisabled;
if (isActive) {
WelcomePageHelpers::drawCardBackground(painter, actionR, Qt::transparent,
themeColor(Theme::Token_Text_Muted));
m_activeActionRects[i] = actionR;
}
if (isActive)
m_activeActionRects[i] = actionRect;
painter->setFont(actionFont);
painter->setPen((isDisabled ? actionDisabledTF : actionTF).color());
const QRect actionTextR = actionR.adjusted(0, 0, 0, -1);
painter->drawText(actionTextR, actionTF.drawTextFlags, action);
xx += actionR.width();
if (i < actions.count() - 1) {
const QRect dividerR(xx + s(HGapXs), yy, actionSepWidth, buttonHeight);
painter->fillRect(dividerR, themeColor(Theme::Token_Text_Muted));
}
xx += gapWidth;
}
yy += buttonHeight;
yy += s(VGapXs);
}
QTC_CHECK(option.rect.bottom() == yy + itemSpacing());
}
static int headerHeight()
{
const int paddingsHeight = s(VPaddingXs + VPaddingXs);
const int heightForSessionName = sessionNameTF.lineHeight() + paddingsHeight;
const int heightForIcon =
withIcon() ? int(icon().deviceIndependentSize().height()) + paddingsHeight : 0;
return qMax(heightForSessionName, heightForIcon);
}
QSize sizeHint(const QStyleOptionViewItem &, const QModelIndex &idx) const final
{
int h = SESSION_LINE_HEIGHT;
QString sessionName = idx.data(Qt::DisplayRole).toString();
if (m_expandedSessions.contains(sessionName)) {
int h = headerHeight();
if (expanded(idx)) {
const QString sessionName = idx.data(Qt::DisplayRole).toString();
const FilePaths projects = ProjectManager::projectsForSessionName(sessionName);
h += projects.size() * 40 + LINK_HEIGHT - 6;
const int projectEntryHeight =
s(VPaddingXs)
+ projectNameTF.lineHeight()
+ s(ExPaddingGapS)
+ projectPathTF.lineHeight()
+ s(VPaddingXs);
h += projects.size() * projectEntryHeight
+ s(VGapXs)
+ actionButtonHeight()
+ s(VGapXs);
}
return QSize(380, h + ItemGap);
return QSize(-1, h + itemSpacing());
}
bool editorEvent(QEvent *ev, QAbstractItemModel *model,
const QStyleOptionViewItem &option, const QModelIndex &idx) final
const QStyleOptionViewItem &, const QModelIndex &idx) final
{
if (ev->type() == QEvent::MouseButtonRelease) {
const QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(ev);
const Qt::MouseButtons button = mouseEvent->button();
const QPoint pos = static_cast<QMouseEvent *>(ev)->pos();
const QRect rc(option.rect.right() - SESSION_ARROW_RECT_WIDTH, option.rect.top(),
SESSION_ARROW_RECT_WIDTH, SESSION_LINE_HEIGHT);
const QString sessionName = idx.data(Qt::DisplayRole).toString();
if (rc.contains(pos) || button == Qt::RightButton) {
if (m_activeExpandRect.contains(pos) || button == Qt::RightButton) {
// The expand/collapse "button".
if (m_expandedSessions.contains(sessionName))
m_expandedSessions.removeOne(sessionName);
@@ -421,23 +600,15 @@ public:
}
if (ev->type() == QEvent::MouseMove) {
emit model->layoutChanged({QPersistentModelIndex(idx)}); // Somewhat brutish.
//update(option.rect);
return false;
}
return false;
}
private:
const QColor hoverColor = themeColor(Theme::Welcome_HoverColor);
const QColor textColor = themeColor(Theme::Welcome_TextColor);
const QColor linkColor = themeColor(Theme::Welcome_LinkColor);
const QColor disabledLinkColor = themeColor(Theme::Welcome_DisabledLinkColor);
const QColor backgroundPrimaryColor = themeColor(Theme::Welcome_BackgroundPrimaryColor);
const QColor foregroundPrimaryColor = themeColor(Theme::Welcome_ForegroundPrimaryColor);
const QColor foregroundSecondaryColor = themeColor(Theme::Welcome_ForegroundSecondaryColor);
QStringList m_expandedSessions;
mutable QRect m_activeExpandRect;
mutable QRect m_activeSwitchToRect;
mutable QRect m_activeActionRects[3];
};
@@ -453,58 +624,89 @@ class ProjectDelegate : public BaseDelegate
public:
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &idx) const final
{
QRect rc = option.rect;
// visible on withIcon() Extra margin right of project item
// | |
// +-------+-------+ +------+-----+
// | | | |
//
// +------------+--------+--------+------+--------+-------------+------------+------------+
// | | | | | | (VPaddingXs)| | |
// | | | | | +-------------+ | |
// | | | | | |<projectName>| | |
// | | | | | +-------------+ | |
// |(HPaddingXs)|<number>|(HGapXs)|<icon>|(HGapXs)| (VGapXs) |(HPaddingXs)|(HPaddingXs)|
// | |(16x16) | | | +-------------+ | |
// | | | | | |<projectPath>| | |
// | | | | | +-------------+ | |
// | | | | | | (VPaddingXs)| | |
// +------------+--------+--------+------+--------+-------------+------------+------------+ --+
// | (VGapL) | +-- Gap between project items
// +--------------------------------------------------------------------------------------+ --+
const bool hovered = option.widget->isActiveWindow() && option.state & QStyle::State_MouseOver;
const QRect bgRect = rc.adjusted(0, 0, -ItemGap, -ItemGap);
painter->fillRect(rc, themeColor(Theme::Welcome_BackgroundSecondaryColor));
painter->fillRect(bgRect, themeColor(hovered ? Theme::Welcome_HoverColor
: Theme::Welcome_BackgroundPrimaryColor));
const bool hovered = option.widget->isActiveWindow()
&& option.state & QStyle::State_MouseOver;
const int x = rc.x();
const int y = rc.y();
const int firstBase = y + 18;
const int secondBase = firstBase + 19;
const QRect bgR = option.rect.adjusted(0, 0, -s(HPaddingXs), -itemSpacing());
static const QPixmap projectIcon =
pixmap("project", Theme::Welcome_ForegroundSecondaryColor);
painter->drawPixmap(x + 11, y + 6, projectIcon);
static const QPixmap icon = pixmap("project", Theme::Token_Text_Muted);
const QSize iconS = icon.deviceIndependentSize().toSize();
QString projectName = idx.data(Qt::DisplayRole).toString();
FilePath projectPath = FilePath::fromVariant(idx.data(ProjectModel::FilePathRole));
const int x = bgR.x();
const int numberX = x + s(HPaddingXs);
const int iconX = numberX + shortcutNumberWidth + s(HGapXs);
const int iconWidth = iconS.width();
const int textX = withIcon() ? iconX + iconWidth + s(HGapXs) : iconX;
const int textWidth = bgR.width() - s(HPaddingXs) - textX;
painter->setPen(themeColor(Theme::Welcome_ForegroundSecondaryColor));
painter->setFont(sizedFont(10, option.widget));
const int y = bgR.y();
const int iconHeight = iconS.height();
const int iconY = y + (bgR.height() - iconHeight) / 2;
const int projectNameY = y + s(VPaddingXs);
const QRect projectNameR(textX, projectNameY, textWidth, projectNameTF.lineHeight());
const int projectPathY = projectNameY + projectNameR.height() + s(VGapXs);
const QRect projectPathR(textX, projectPathY, textWidth, projectPathTF.lineHeight());
if (idx.row() < 9)
painter->drawText(x + 3, firstBase, QString::number(idx.row() + 1));
QTC_CHECK(option.rect.bottom() == projectPathR.bottom() + s(VPaddingXs) + itemSpacing());
const int textSpace = rc.width() - TEXT_OFFSET_HORIZONTAL - ItemGap - 6;
painter->setPen(themeColor(Theme::Welcome_LinkColor));
painter->setFont(sizedFont(13, option.widget, hovered));
{
drawBackgroundRect(painter, bgR, hovered);
}
if (idx.row() < 9) {
painter->setPen(shortcutNumberTF.color());
painter->setFont(shortcutNumberTF.font());
const QRect numberR(numberX, y, shortcutNumberWidth, bgR.height());
const QString numberString = QString::number(idx.row() + 1);
painter->drawText(numberR, shortcutNumberTF.drawTextFlags, numberString);
}
if (withIcon()) {
painter->drawPixmap(iconX, iconY, icon);
}
{
painter->setPen(projectNameTF.color());
painter->setFont(projectNameTF.font(hovered));
const QString projectName = idx.data(Qt::DisplayRole).toString();
const QString projectNameElided =
painter->fontMetrics().elidedText(projectName, Qt::ElideRight, textSpace);
painter->drawText(x + TEXT_OFFSET_HORIZONTAL, firstBase, projectNameElided);
painter->setPen(themeColor(Theme::Welcome_ForegroundPrimaryColor));
painter->setFont(sizedFont(13, option.widget));
painter->fontMetrics().elidedText(projectName, Qt::ElideRight, textWidth);
painter->drawText(projectNameR, projectNameTF.drawTextFlags, projectNameElided);
}
{
painter->setPen(projectPathTF.color());
painter->setFont(projectPathTF.font());
const FilePath projectPath =
FilePath::fromVariant(idx.data(ProjectModel::FilePathRole));
const QString displayPath =
projectPath.osType() == OsTypeWindows ? projectPath.displayName()
: projectPath.withTildeHomePath();
const QString displayPathElided =
painter->fontMetrics().elidedText(displayPath, Qt::ElideMiddle, textSpace);
painter->drawText(x + TEXT_OFFSET_HORIZONTAL, secondBase, displayPathElided);
painter->fontMetrics().elidedText(displayPath, Qt::ElideMiddle, textWidth);
painter->drawText(projectPathR, projectPathTF.drawTextFlags, displayPathElided);
}
}
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &idx) const final
QSize sizeHint([[maybe_unused]] const QStyleOptionViewItem &option,
[[maybe_unused]] const QModelIndex &idx) const override
{
QString projectName = idx.data(Qt::DisplayRole).toString();
QString projectPath = idx.data(ProjectModel::FilePathRole).toString();
QFontMetrics fm(sizedFont(13, option.widget));
int width = std::max(fm.horizontalAdvance(projectName),
fm.horizontalAdvance(projectPath)) + TEXT_OFFSET_HORIZONTAL;
return QSize(width, 47 + ItemGap);
return QSize(-1, itemHeight() + itemSpacing());
}
bool editorEvent(QEvent *ev, QAbstractItemModel *model,
@@ -541,6 +743,18 @@ public:
}
return false;
}
private:
static int itemHeight()
{
const int height =
s(VPaddingXs)
+ projectNameTF.lineHeight()
+ s(VGapXs)
+ projectPathTF.lineHeight()
+ s(VPaddingXs);
return height;
}
};
class TreeView : public QTreeView
@@ -559,10 +773,7 @@ public:
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
setFocusPolicy(Qt::NoFocus);
QPalette pal;
pal.setColor(QPalette::Base, themeColor(Theme::Welcome_BackgroundSecondaryColor));
viewport()->setPalette(pal);
setBackgroundColor(viewport(), Theme::Token_Background_Default);
}
};
@@ -577,54 +788,63 @@ public:
if (!projectWelcomePage->m_projectModel)
projectWelcomePage->m_projectModel = new ProjectModel(this);
auto manageSessionsButton = new WelcomePageButton(this);
manageSessionsButton->setText(Tr::tr("Manage..."));
manageSessionsButton->setWithAccentColor(true);
manageSessionsButton->setOnClicked([] { SessionManager::showSessionManager(); });
auto sessionsLabel = new QLabel(this);
sessionsLabel->setText(Tr::tr("Sessions"));
auto recentProjectsLabel = new QLabel(this);
recentProjectsLabel->setText(Tr::tr("Projects"));
using namespace Layouting;
auto sessions = new QWidget;
{
auto sessionsLabel = new Label(Tr::tr("Sessions"), Label::Primary);
auto manageSessionsButton = new Button(Tr::tr("Manage..."), Button::MediumSecondary);
auto sessionsList = new TreeView(this, "Sessions");
sessionsList->setModel(projectWelcomePage->m_sessionModel);
sessionsList->header()->setSectionHidden(1, true); // The "last modified" column.
sessionsList->setItemDelegate(&m_sessionDelegate);
sessionsList->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
QSizePolicy sessionsSp(QSizePolicy::Expanding, QSizePolicy::Expanding);
sessionsSp.setHorizontalStretch(3);
sessions->setSizePolicy(sessionsSp);
Column {
Row {
sessionsLabel,
st,
manageSessionsButton,
customMargin({HPaddingS, 0, sessionScrollBarGap, 0}),
},
sessionsList,
spacing(ExPaddingGapL),
customMargin({ExVPaddingGapXl, ExVPaddingGapXl, 0, 0}),
}.attachTo(sessions);
connect(manageSessionsButton, &Button::clicked,
this, &SessionManager::showSessionManager);
}
auto projects = new QWidget;
{
auto projectsLabel = new Label(Tr::tr("Projects"), Label::Primary);
auto projectsList = new TreeView(this, "Recent Projects");
projectsList->setUniformRowHeights(true);
projectsList->setModel(projectWelcomePage->m_projectModel);
projectsList->setItemDelegate(&m_projectDelegate);
projectsList->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
QSizePolicy projectsSP(QSizePolicy::Expanding, QSizePolicy::Expanding);
projectsSP.setHorizontalStretch(5);
projects->setSizePolicy(projectsSP);
Column {
Row {
projectsLabel,
customMargin({HPaddingS, 0, 0, 0}),
},
projectsList,
spacing(ExPaddingGapL),
customMargin({ExVPaddingGapXl - sessionScrollBarGap, ExVPaddingGapXl, 0, 0}),
}.attachTo(projects);
}
auto sessionHeader = panelBar(this);
auto hbox11 = new QHBoxLayout(sessionHeader);
hbox11->setContentsMargins(12, 0, 0, 0);
hbox11->addWidget(sessionsLabel);
hbox11->addStretch(1);
hbox11->addWidget(manageSessionsButton);
auto projectsHeader = panelBar(this);
auto hbox21 = new QHBoxLayout(projectsHeader);
hbox21->setContentsMargins(hbox11->contentsMargins());
hbox21->addWidget(recentProjectsLabel);
auto grid = new QGridLayout(this);
grid->setContentsMargins(0, 0, 0, ItemGap);
grid->setHorizontalSpacing(0);
grid->setVerticalSpacing(ItemGap);
grid->addWidget(panelBar(this), 0, 0);
grid->addWidget(sessionHeader, 0, 1);
grid->addWidget(sessionsList, 1, 1);
grid->addWidget(panelBar(this), 0, 2);
grid->setColumnStretch(1, 9);
grid->setColumnMinimumWidth(1, 200);
grid->addWidget(projectsHeader, 0, 3);
grid->addWidget(projectsList, 1, 3);
grid->setColumnStretch(3, 20);
Row {
sessions,
projects,
spacing(0),
noMargin(),
}.attachTo(this);
}
SessionDelegate m_sessionDelegate;

View File

@@ -117,11 +117,11 @@ public:
if (!pipIsUsable(path)) {
result << BuildSystemTask(
Task::Warning,
Tr::tr("Python \"%1\" does not contain a usable pip. Pip is used to install "
"python "
"packages from the Python Package Index, like PySide and the python "
"language server. If you want to use any of that functionality "
"ensure pip is installed for that python.")
Tr::tr("Python \"%1\" does not contain a usable pip. pip is needed to install "
"Python "
"packages from the Python Package Index, like PySide and the Python "
"language server. To use any of that functionality "
"ensure that pip is installed for that Python.")
.arg(path.toUserOutput()));
}
if (!venvIsUsable(path)) {
@@ -130,7 +130,7 @@ public:
Tr::tr(
"Python \"%1\" does not contain a usable venv. venv is the recommended way "
"to isolate a development environment for a project from the globally "
"installed python.")
"installed Python.")
.arg(path.toUserOutput()));
}
}

View File

@@ -35,7 +35,7 @@ Tasks PythonProject::projectIssues(const Kit *k) const
return {};
return {
BuildSystemTask{Task::Error,
Tr::tr("No python interpreter set for kit \"%1\"").arg(k->displayName())}};
Tr::tr("No Python interpreter set for kit \"%1\"").arg(k->displayName())}};
}
PythonProjectNode::PythonProjectNode(const FilePath &path)

View File

@@ -56,6 +56,7 @@ void MakeFileParse::parseArgs(const QString &args, const QString &project,
static const QRegularExpression regExp(QLatin1String("^([^\\s\\+-]*)\\s*(\\+=|=|-=|~=)(.*)$"));
bool after = false;
bool ignoreNext = false;
bool nextIsQtConfArg = false;
m_unparsedArguments = args;
ProcessArgs::ArgIterator ait(&m_unparsedArguments);
while (ait.next()) {
@@ -63,11 +64,18 @@ void MakeFileParse::parseArgs(const QString &args, const QString &project,
// Ignoring
ignoreNext = false;
ait.deleteArg();
} else if (nextIsQtConfArg) {
nextIsQtConfArg = false;
m_qtConfFile = FilePath::fromUserInput(ait.value());
ait.deleteArg();
} else if (ait.value() == project) {
ait.deleteArg();
} else if (ait.value() == QLatin1String("-after")) {
after = true;
ait.deleteArg();
} else if (ait.value() == "-qtconf") {
nextIsQtConfArg = true;
ait.deleteArg();
} else if (ait.value().contains(QLatin1Char('='))) {
const QRegularExpressionMatch match = regExp.match(ait.value());
if (match.hasMatch()) {

View File

@@ -30,6 +30,7 @@ public:
MakefileState makeFileState() const;
Utils::FilePath qmakePath() const;
Utils::FilePath srcProFile() const;
Utils::FilePath qtConfPath() const { return m_qtConfFile;}
QMakeStepConfig config() const;
QString unparsedArguments() const;
@@ -59,6 +60,7 @@ private:
MakefileState m_state;
Utils::FilePath m_qmakePath;
Utils::FilePath m_srcProFile;
Utils::FilePath m_qtConfFile;
QmakeBuildConfig m_qmakeBuildConfig;
QMakeStepConfig m_config;

View File

@@ -490,7 +490,9 @@ QmakeBuildConfiguration::MakefileState QmakeBuildConfiguration::compareToImportF
return MakefileIncompatible;
}
if (version->qmakeFilePath() != parse.qmakePath()) {
if (version->qmakeFilePath() != parse.qmakePath()
&& (parse.qtConfPath().isEmpty() // QTCREATORBUG-30354
|| version->qmakeFilePath().parentDir() != parse.qtConfPath().parentDir())) {
qCDebug(logs) << "**Different Qt versions, buildconfiguration:" << version->qmakeFilePath()
<< " Makefile:" << parse.qmakePath();
return MakefileForWrongProject;

View File

@@ -250,7 +250,7 @@ static QPixmap fetchPixmapAndUpdatePixmapCache(const QString &url)
const int dpr = qApp->devicePixelRatio();
// boundedTo -> don't scale thumbnails up
const QSize scaledSize =
WelcomePageHelpers::GridItemImageSize.boundedTo(img.size()) * dpr;
WelcomePageHelpers::WelcomeThumbnailSize.boundedTo(img.size()) * dpr;
const QImage scaled = img.isNull() ? img
: img.scaled(scaledSize,
Qt::KeepAspectRatio,

View File

@@ -21,13 +21,13 @@
#include <utils/algorithm.h>
#include <utils/fileutils.h>
#include <utils/layoutbuilder.h>
#include <utils/pathchooser.h>
#include <utils/qtcassert.h>
#include <utils/stylehelper.h>
#include <utils/theme/theme.h>
#include <utils/winutils.h>
#include <QComboBox>
#include <QDesktopServices>
#include <QDialogButtonBox>
#include <QElapsedTimer>
@@ -249,7 +249,8 @@ protected:
painter->setFont(option.font);
painter->setCompositionMode(QPainter::CompositionMode_Difference);
painter->setPen(Qt::white);
painter->drawText(currentPixmapRect.translated(0, -WelcomePageHelpers::ItemGap),
painter->drawText(
currentPixmapRect.translated(0, -StyleHelper::SpacingTokens::VPaddingXxs),
exampleItem->videoLength, Qt::AlignBottom | Qt::AlignHCenter);
painter->restore();
static const QPixmap playOverlay =
@@ -274,29 +275,25 @@ public:
{
m_exampleDelegate.setShowExamples(isExamples);
auto searchBox = new SearchBox(this);
m_searcher = searchBox->m_lineEdit;
using namespace StyleHelper::SpacingTokens;
auto grid = new QGridLayout(this);
grid->setContentsMargins(0, 0, 0, WelcomePageHelpers::ItemGap);
grid->setHorizontalSpacing(0);
grid->setVerticalSpacing(WelcomePageHelpers::ItemGap);
using namespace Layouting;
Row titleRow {
customMargin({0, 0, ExVPaddingGapXl, 0}),
spacing(ExVPaddingGapXl),
};
auto searchBar = WelcomePageHelpers::panelBar(this);
auto hbox = new QHBoxLayout(searchBar);
hbox->setContentsMargins(0, 0, 0, 0);
m_searcher = new SearchBox;
if (m_isExamples) {
m_searcher->setPlaceholderText(Tr::tr("Search in Examples..."));
auto exampleSetSelector = new QComboBox(this);
QPalette pal = exampleSetSelector->palette();
// for macOS dark mode
pal.setColor(QPalette::Text, Utils::creatorTheme()->color(Theme::Welcome_TextColor));
exampleSetSelector->setPalette(pal);
exampleSetSelector->setMinimumWidth(Core::WelcomePageHelpers::GridItemWidth);
exampleSetSelector->setMaximumWidth(Core::WelcomePageHelpers::GridItemWidth);
auto exampleSetSelector = new ComboBox;
exampleSetSelector->setSizeAdjustPolicy(QComboBox::AdjustToContents);
exampleSetSelector->setMinimumWidth(ListItemDelegate::itemSize().width()
- ExVPaddingGapXl);
exampleSetSelector->setModel(s_exampleSetModel);
exampleSetSelector->setCurrentIndex(s_exampleSetModel->selectedExampleSet());
titleRow.addItem(exampleSetSelector);
connect(exampleSetSelector,
&QComboBox::activated,
s_exampleSetModel,
@@ -305,23 +302,23 @@ public:
&ExampleSetModel::selectedExampleSetChanged,
exampleSetSelector,
&QComboBox::setCurrentIndex);
hbox->setSpacing(Core::WelcomePageHelpers::HSpacing);
hbox->addWidget(exampleSetSelector);
} else {
m_searcher->setPlaceholderText(Tr::tr("Search in Tutorials..."));
}
hbox->addWidget(searchBox);
grid->addWidget(WelcomePageHelpers::panelBar(this), 0, 0);
grid->addWidget(searchBar, 0, 1);
grid->addWidget(WelcomePageHelpers::panelBar(this), 0, 2);
titleRow.addItem(m_searcher);
auto gridView = new SectionedGridView(this);
auto gridView = new SectionedGridView;
m_viewController
= new ExamplesViewController(s_exampleSetModel, gridView, m_searcher, isExamples, this);
gridView->setItemDelegate(&m_exampleDelegate);
grid->addWidget(gridView, 1, 1, 1, 2);
Column {
titleRow,
gridView,
spacing(ExVPaddingGapXl),
customMargin({ExVPaddingGapXl, ExVPaddingGapXl, 0, 0}),
}.attachTo(this);
connect(&m_exampleDelegate, &ExampleDelegate::tagClicked,
this, &ExamplesPageWidget::onTagClicked);

View File

@@ -155,11 +155,16 @@ GroupItem GenericDeployStep::transferTask(const Storage<FilesToTransfer> &storag
}
}
if (!m_emittedDowngradeWarning && transferMethod != preferredTransferMethod) {
addWarningMessage(Tr::tr("Transfer method was downgraded from \"%1\" to \"%2\". If "
const QString message
= Tr::tr("Transfer method was downgraded from \"%1\" to \"%2\". If "
"this is unexpected, please re-test device \"%3\".")
.arg(FileTransfer::transferMethodName(preferredTransferMethod),
FileTransfer::transferMethodName(transferMethod),
deviceConfiguration()->displayName()));
deviceConfiguration()->displayName());
if (transferMethod == FileTransferMethod::GenericCopy)
addWarningMessage(message);
else
addProgressMessage(message);
m_emittedDowngradeWarning = true;
}
transfer.setTransferMethod(transferMethod);

View File

@@ -98,6 +98,7 @@ GenericLinuxDeviceConfigurationWidget::GenericLinuxDeviceConfigurationWidget(
const int dmCount = dm->deviceCount();
for (int i = 0; i < dmCount; ++i) {
IDevice::ConstPtr dev = dm->deviceAt(i);
if (dev->id() != device->id())
m_linkDeviceComboBox->addItem(dev->displayName(), dev->id().toSetting());
}
@@ -307,13 +308,18 @@ void GenericLinuxDeviceConfigurationWidget::initGui()
Id linkDeviceId = Id::fromSetting(device()->extraData(Constants::LinkDevice));
auto dm = DeviceManager::instance();
int found = -1;
int minus = 0;
for (int i = 0, n = dm->deviceCount(); i < n; ++i) {
if (dm->deviceAt(i)->id() == linkDeviceId) {
const auto otherId = dm->deviceAt(i)->id();
if (otherId == linkDeviceId) {
found = i;
break;
} else if (otherId == device()->id()) {
// Since we ourselves do not appear in the combo box, we need to adjust the index.
minus = 1;
}
}
m_linkDeviceComboBox->setCurrentIndex(found + 1); // There's the "Direct" entry first.
m_linkDeviceComboBox->setCurrentIndex(found + 1 - minus); // There's the "Direct" entry first.
m_hostLineEdit->setText(sshParams.host());
m_sshPortSpinBox->setValue(sshParams.port());

View File

@@ -356,13 +356,14 @@ QFont FontSettings::font() const
{
QFont f(family(), fontSize());
f.setStyleStrategy(m_antialias ? QFont::PreferAntialias : QFont::NoAntialias);
f.setWeight(fontNormalWeight());
return f;
}
int FontSettings::fontNormalWeight() const
QFont::Weight FontSettings::fontNormalWeight() const
{
// TODO: Fix this when we upgrade "Source Code Pro" to a version greater than 2.0.30
int weight = QFont::Normal;
QFont::Weight weight = QFont::Normal;
if (Utils::HostOsInfo::isMacHost() && m_family == g_sourceCodePro)
weight = QFont::Medium;
return weight;

View File

@@ -61,7 +61,7 @@ public:
void setRelativeLineSpacing(int relativeLineSpacing);
QFont font() const;
int fontNormalWeight() const;
QFont::Weight fontNormalWeight() const;
bool antialias() const;
void setAntialias(bool antialias);

View File

@@ -81,6 +81,10 @@ void SyntaxHighlighter::delayedRehighlight()
if (!d->rehighlightPending)
return;
d->rehighlightPending = false;
if (document()->isEmpty())
return;
rehighlight();
}
@@ -197,6 +201,10 @@ void SyntaxHighlighterPrivate::reformatBlocks(int from, int charsRemoved, int ch
QList<SyntaxHighlighter::Result> vecRes;
SyntaxHighlighter::Result resStart;
resStart.m_state = SyntaxHighlighter::State::Start;
vecRes << resStart;
while (block.isValid() && (block.position() < endPosition || forceHighlightOfNextBlock)) {
if (QThread::currentThread()->isInterruptionRequested())
break;
@@ -758,7 +766,10 @@ void SyntaxHighlighter::setExtraFormats(const QTextBlock &block,
SyntaxHighlighter::Result res;
res.m_formatRanges = block.layout()->formats();
res.fillByBlock(block);
emit resultsReady({res});
res.m_state = SyntaxHighlighter::State::Extras;
SyntaxHighlighter::Result resDone;
resDone.m_state = SyntaxHighlighter::State::Done;
emit resultsReady({res, resDone});
document()->markContentsDirty(block.position(), blockLength - 1);
d->inReformatBlocks = wasInReformatBlocks;
@@ -784,7 +795,10 @@ void SyntaxHighlighter::clearExtraFormats(const QTextBlock &block)
SyntaxHighlighter::Result res;
res.m_formatRanges = block.layout()->formats();
res.fillByBlock(block);
emit resultsReady({res});
res.m_state = SyntaxHighlighter::State::Extras;
SyntaxHighlighter::Result resDone;
resDone.m_state = SyntaxHighlighter::State::Done;
emit resultsReady({res, resDone});
document()->markContentsDirty(block.position(), blockLength - 1);
d->inReformatBlocks = wasInReformatBlocks;

View File

@@ -54,7 +54,9 @@ public:
TextEditor::FontSettings fontSettings() const;
enum State {
Start,
InProgress,
Extras,
Done
};
@@ -71,7 +73,6 @@ public:
m_hasBlockUserData = true;
m_foldingIndent = userDate->foldingIndent();
m_folded = userDate->folded();
m_ifdefedOut = userDate->ifdefedOut();
m_foldingStartIncluded = userDate->foldingStartIncluded();
m_foldingEndIncluded = userDate->foldingEndIncluded();
@@ -83,10 +84,11 @@ public:
{
block.setUserState(m_userState);
if (m_hasBlockUserData) {
if (!m_hasBlockUserData)
return;
TextBlockUserData *data = TextDocumentLayout::userData(block);
data->setExpectedRawStringSuffix(m_expectedRawStringSuffix);
data->setFolded(m_folded);
data->setFoldingIndent(m_foldingIndent);
data->setFoldingStartIncluded(m_foldingStartIncluded);
data->setFoldingEndIncluded(m_foldingEndIncluded);
@@ -98,13 +100,11 @@ public:
data->setParentheses(m_parentheses);
}
}
int m_blockNumber;
bool m_hasBlockUserData = false;
int m_foldingIndent : 16;
uint m_folded : 1;
uint m_ifdefedOut : 1;
uint m_foldingStartIncluded : 1;
uint m_foldingEndIncluded : 1;

View File

@@ -100,7 +100,6 @@ public:
SyntaxHighlighter *m_highlighter = nullptr;
QTextDocument *m_document = nullptr;
signals:
void resultsReady(const QList<SyntaxHighlighter::Result> &result);
@@ -130,6 +129,8 @@ SyntaxHighlighterRunner::SyntaxHighlighterRunner(SyntaxHighlighter *highlighter,
&QTextDocument::contentsChange,
this,
&SyntaxHighlighterRunner::changeDocument);
m_foldValidator.setup(qobject_cast<TextDocumentLayout *>(document->documentLayout()));
} else {
connect(d,
&SyntaxHighlighterRunnerPrivate::resultsReady,
@@ -169,7 +170,12 @@ void SyntaxHighlighterRunner::applyFormatRanges(const QList<SyntaxHighlighter::R
for (const SyntaxHighlighter::Result &result : results) {
m_syntaxInfoUpdated = result.m_state;
if (m_syntaxInfoUpdated == SyntaxHighlighter::State::Start) {
m_foldValidator.reset();
continue;
}
if (m_syntaxInfoUpdated == SyntaxHighlighter::State::Done) {
m_foldValidator.finalize();
emit highlightingFinished();
return;
}
@@ -181,12 +187,11 @@ void SyntaxHighlighterRunner::applyFormatRanges(const QList<SyntaxHighlighter::R
result.copyToBlock(docBlock);
if (result.m_formatRanges != docBlock.layout()->formats()) {
TextDocumentLayout::FoldValidator foldValidator;
foldValidator.setup(qobject_cast<TextDocumentLayout *>(m_document->documentLayout()));
docBlock.layout()->setFormats(result.m_formatRanges);
m_document->markContentsDirty(docBlock.position(), docBlock.length());
foldValidator.process(docBlock);
}
if (m_syntaxInfoUpdated != SyntaxHighlighter::State::Extras)
m_foldValidator.process(docBlock);
}
}

View File

@@ -56,6 +56,7 @@ private:
bool m_useGenericHighlighter = false;
QString m_definitionName;
std::optional<QThread> m_thread;
TextDocumentLayout::FoldValidator m_foldValidator;
};
} // namespace TextEditor

View File

@@ -3492,13 +3492,36 @@ QByteArray TextEditorWidget::saveState() const
return state;
}
bool TextEditorWidget::singleShotAfterHighlightingDone(std::function<void()> &&f)
{
if (d->m_document->syntaxHighlighterRunner()
&& !d->m_document->syntaxHighlighterRunner()->syntaxInfoUpdated()) {
connect(d->m_document->syntaxHighlighterRunner(),
&SyntaxHighlighterRunner::highlightingFinished,
this,
[f = std::move(f)] { f(); }, Qt::SingleShotConnection);
return true;
}
return false;
}
void TextEditorWidget::restoreState(const QByteArray &state)
{
if (state.isEmpty()) {
const auto callFoldLicenseHeader = [this] {
auto callFold = [this] {
if (d->m_displaySettings.m_autoFoldFirstComment)
d->foldLicenseHeader();
};
if (!singleShotAfterHighlightingDone(callFold))
callFold();
};
if (state.isEmpty()) {
callFoldLicenseHeader();
return;
}
int version;
int vval;
int hval;
@@ -3514,6 +3537,7 @@ void TextEditorWidget::restoreState(const QByteArray &state)
if (version >= 1) {
QList<int> collapsedBlocks;
stream >> collapsedBlocks;
auto foldingRestore = [this, collapsedBlocks] {
QTextDocument *doc = document();
bool layoutChanged = false;
for (const int blockNumber : std::as_const(collapsedBlocks)) {
@@ -3524,14 +3548,16 @@ void TextEditorWidget::restoreState(const QByteArray &state)
}
}
if (layoutChanged) {
auto documentLayout = qobject_cast<TextDocumentLayout*>(doc->documentLayout());
QTC_ASSERT(documentLayout, return );
auto documentLayout = qobject_cast<TextDocumentLayout *>(doc->documentLayout());
QTC_ASSERT(documentLayout, return);
documentLayout->requestUpdate();
documentLayout->emitDocumentSizeChanged();
}
};
if (!singleShotAfterHighlightingDone(foldingRestore))
foldingRestore();
} else {
if (d->m_displaySettings.m_autoFoldFirstComment)
d->foldLicenseHeader();
callFoldLicenseHeader();
}
d->m_lastCursorChangeWasInteresting = false; // avoid adding last position to history
@@ -6642,6 +6668,9 @@ void TextEditorWidget::ensureCursorVisible()
void TextEditorWidget::ensureBlockIsUnfolded(QTextBlock block)
{
if (singleShotAfterHighlightingDone([this, block] { ensureBlockIsUnfolded(block); }))
return;
if (!block.isVisible()) {
auto documentLayout = qobject_cast<TextDocumentLayout*>(document()->documentLayout());
QTC_ASSERT(documentLayout, return);
@@ -8215,6 +8244,9 @@ void TextEditorWidget::foldCurrentBlock()
void TextEditorWidget::fold(const QTextBlock &block)
{
if (singleShotAfterHighlightingDone([this, block] { fold(block); }))
return;
QTextDocument *doc = document();
auto documentLayout = qobject_cast<TextDocumentLayout*>(doc->documentLayout());
QTC_ASSERT(documentLayout, return);
@@ -8235,6 +8267,9 @@ void TextEditorWidget::fold(const QTextBlock &block)
void TextEditorWidget::unfold(const QTextBlock &block)
{
if (singleShotAfterHighlightingDone([this, block] { unfold(block); }))
return;
QTextDocument *doc = document();
auto documentLayout = qobject_cast<TextDocumentLayout*>(doc->documentLayout());
QTC_ASSERT(documentLayout, return);
@@ -8254,6 +8289,9 @@ void TextEditorWidget::unfoldCurrentBlock()
void TextEditorWidget::unfoldAll()
{
if (singleShotAfterHighlightingDone([this] { unfoldAll(); }))
return;
QTextDocument *doc = document();
auto documentLayout = qobject_cast<TextDocumentLayout*>(doc->documentLayout());
QTC_ASSERT(documentLayout, return);

View File

@@ -645,6 +645,7 @@ private:
friend class Internal::TextEditorOverlay;
friend class RefactorOverlay;
bool singleShotAfterHighlightingDone(std::function<void()> &&f);
void updateVisualWrapColumn();
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 B

After

Width:  |  Height:  |  Size: 175 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 B

After

Width:  |  Height:  |  Size: 225 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 B

After

Width:  |  Height:  |  Size: 192 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 161 B

After

Width:  |  Height:  |  Size: 255 B

Some files were not shown because too many files have changed in this diff Show More