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` for searching in `Files in File System`
* Added `Copy to Clipboard` to the `About Qt Creator` dialog * Added `Copy to Clipboard` to the `About Qt Creator` dialog
([QTCREATORBUG-29886](https://bugreports.qt.io/browse/QTCREATORBUG-29886)) ([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 Editing
------- -------
@@ -78,6 +80,8 @@ Editing
* Clangd * Clangd
* Fixed that `Follow Symbol Under Cursor` only worked for exact matches * Fixed that `Follow Symbol Under Cursor` only worked for exact matches
([QTCREATORBUG-29814](https://bugreports.qt.io/browse/QTCREATORBUG-29814)) ([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 ### QML
@@ -133,6 +137,8 @@ Projects
([QTCREATORBUG-29530](https://bugreports.qt.io/browse/QTCREATORBUG-29530)) ([QTCREATORBUG-29530](https://bugreports.qt.io/browse/QTCREATORBUG-29530))
* Added a file wizard for Qt translation (`.ts`) files * Added a file wizard for Qt translation (`.ts`) files
([QTCREATORBUG-29775](https://bugreports.qt.io/browse/QTCREATORBUG-29775)) ([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 * Improved the environment settings by making the changes explicit in a
separate, text-based editor separate, text-based editor
* Increased the maximum width of the target selector * Increased the maximum width of the target selector
@@ -184,6 +190,9 @@ Debugging
### C++ ### C++
* Added a pretty printer for `std::tuple` * 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 * Fixed that breakpoints were not hit while the message dialog about missing
debug information was shown debug information was shown
([QTCREATORBUG-30168](https://bugreports.qt.io/browse/QTCREATORBUG-30168)) ([QTCREATORBUG-30168](https://bugreports.qt.io/browse/QTCREATORBUG-30168))
@@ -270,6 +279,7 @@ Andre Hartmann
André Pönitz André Pönitz
Andreas Loth Andreas Loth
Artem Sokolovskii Artem Sokolovskii
Assam Boudjelthia
Brook Cronin Brook Cronin
Burak Hancerli Burak Hancerli
Christian Kandeler Christian Kandeler
@@ -306,6 +316,7 @@ Robert Löhning
Sami Shalayel Sami Shalayel
Samuel Jose Raposo Vieira Mira Samuel Jose Raposo Vieira Mira
Samuel Mira Samuel Mira
Semih Yavuz
Serg Kryvonos Serg Kryvonos
Shrief Gabr Shrief Gabr
Sivert Krøvel 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 \section2 Supported GDB Versions
Use GDB 7.5, or later, with the Python scripting extension and Python version 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 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. 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. On Linux, the minimum supported version is LLDB 3.8.
\omit \section2 GDB Run Modes
\section2 GDB Adapter Modes
[Advanced Topic]
The GDB native debugger used internally by the debugger plugin runs in The GDB native debugger used internally by the debugger plugin runs in
different adapter modes to cope with the variety of supported platforms and different modes to cope with the variety of supported platforms and
environments. All GDB adapters inherit from AbstractGdbAdapter: environments:
\list \list
\li PlainGdbAdapter debugs locally started GUI processes. It is \li Plain mode debugs locally started processes that do not need console input.
physically split into parts that are relevant only when Python is
available, parts relevant only when Python is not available, and
mixed code.
\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 \endlist
\endomit
\section1 Installing Native Debuggers \section1 Installing Native Debuggers
The following sections describe 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} \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}, \sa {Manage Language Servers}{How To: Manage Language Servers},
{Enabling and Disabling Messages}, {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, application or the \uicontrol {Open Terminal} button to open a terminal,
it opens as an output view. it opens as an output view.
To open the terminal in a separate window, select \preferences > To open the terminal in a separate window, go to \preferences >
\uicontrol Terminal, and deselet the \uicontrol {Use internal terminal} \uicontrol Terminal, and clear \uicontrol {Use internal terminal}.
check box.
On Linux and \macos, you can set the terminal to open by selecting On Linux and \macos, you can set the terminal to open by selecting
\preferences > \uicontrol Environment > \uicontrol System. \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, \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 \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. \l Projects view, hover the mouse over them, and press \key Ctrl.

View File

@@ -309,10 +309,11 @@
\section2 Working with Branches \section2 Working with Branches
To work with Git branches, select \uicontrol {Branches}. The To work with Git branches, select \uicontrol {Branches}. The
\uicontrol {Git Branches} sidebar view shows the checked out \uicontrol {Git Branches} view shows a list of branches, as well
branch in bold and underlined in the list of branches. 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 Old entries and tags are filtered out of the list of branches
by default. To include them, select \inlineimage icons/filtericon.png 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 To refresh the list of branches, select \inlineimage icons/reload_gray.png
(\uicontrol Refresh). (\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: The context menu for a branch has the following functions:
\table \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())))) 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): class FakeVoidType(cdbext.Type):
@@ -84,10 +84,9 @@ class Dumper(DumperBase):
self.check(isinstance(nativeValue, cdbext.Value)) self.check(isinstance(nativeValue, cdbext.Value))
val = self.Value(self) val = self.Value(self)
val.name = nativeValue.name() val.name = nativeValue.name()
val._type = self.fromNativeType(nativeValue.type())
# There is no cdb api for the size of bitfields. # There is no cdb api for the size of bitfields.
# Workaround this issue by parsing the native debugger text for integral types. # 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: try:
integerString = nativeValue.nativeDebuggerValue() integerString = nativeValue.nativeDebuggerValue()
except UnicodeDecodeError: except UnicodeDecodeError:
@@ -106,16 +105,18 @@ class Dumper(DumperBase):
base = 16 base = 16
else: else:
base = 10 base = 10
signed = not val._type.name.startswith('unsigned') signed = not nativeValue.type().name().startswith('unsigned')
try: 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) byteorder='little', signed=signed)
except: except:
# read raw memory in case the integerString can not be interpreted # read raw memory in case the integerString can not be interpreted
pass pass
if val._type.code == TypeCode.Enum: if nativeValue.type().code() == TypeCode.Enum:
val.ldisplay = self.enumValue(nativeValue) 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.nativeValue = nativeValue
val.laddress = nativeValue.address() val.laddress = nativeValue.address()
val.lbitsize = nativeValue.bitsize() val.lbitsize = nativeValue.bitsize()
@@ -136,6 +137,9 @@ class Dumper(DumperBase):
for f in nativeType.fields()]) for f in nativeType.fields()])
return typeId return typeId
def nativeValueType(self, nativeValue):
return self.fromNativeType(nativeValue.type())
def fromNativeType(self, nativeType): def fromNativeType(self, nativeType):
self.check(isinstance(nativeType, cdbext.Type)) self.check(isinstance(nativeType, cdbext.Type))
typeId = self.nativeTypeId(nativeType) typeId = self.nativeTypeId(nativeType)
@@ -150,51 +154,66 @@ class Dumper(DumperBase):
if nativeType.name().startswith('<function>'): if nativeType.name().startswith('<function>'):
code = TypeCode.Function code = TypeCode.Function
elif nativeType.targetName() != nativeType.name(): elif nativeType.targetName() != nativeType.name():
targetType = self.lookupType(nativeType.targetName(), nativeType.moduleId()) return self.createPointerType(nativeType.targetName())
if targetType is not None and targetType is not nativeType:
return self.createPointerType(targetType)
if code == TypeCode.Array: if code == TypeCode.Array:
# cdb reports virtual function tables as arrays those ar handled separetly by # cdb reports virtual function tables as arrays those ar handled separetly by
# the DumperBase. Declare those types as structs prevents a lookup to a # the DumperBase. Declare those types as structs prevents a lookup to a
# none existing type # none existing type
if not nativeType.name().startswith('__fptr()') and not nativeType.name().startswith('<gentype '): if not nativeType.name().startswith('__fptr()') and not nativeType.name().startswith('<gentype '):
targetType = self.lookupType(nativeType.targetName(), nativeType.moduleId()) targetName = nativeType.targetName()
if targetType is not None: count = nativeType.arrayElements()
return self.createArrayType(targetType, 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 code = TypeCode.Struct
tdata = self.TypeData(self, typeId) tdata = self.TypeData(self, typeId)
tdata.name = nativeType.name() tdata.name = nativeType.name()
tdata.lbitsize = nativeType.bitsize() tdata.lbitsize = lambda: nativeType.bitsize()
tdata.code = code tdata.code = code
tdata.moduleName = nativeType.module() tdata.moduleName = lambda: nativeType.module()
if code == TypeCode.Struct: if code == TypeCode.Struct:
tdata.lfields = lambda value: \ tdata.lfields = lambda value: \
self.listFields(nativeType, value) self.listFields(nativeType, value)
tdata.lalignment = lambda: \ tdata.lalignment = lambda: \
self.nativeStructAlignment(nativeType) self.nativeStructAlignment(nativeType)
if code == TypeCode.Enum: tdata.enumDisplay = lambda intval, addr, form: \
tdata.enumDisplay = lambda intval, addr, form: \ self.nativeTypeEnumDisplay(nativeType, intval, form)
self.nativeTypeEnumDisplay(nativeType, intval, form)
tdata.templateArguments = lambda: \ tdata.templateArguments = lambda: \
self.listTemplateParameters(nativeType.name()) self.listTemplateParameters(nativeType.name())
return self.Type(self, typeId) return self.Type(self, typeId)
def listFields(self, nativeType, value): def listNativeValueChildren(self, nativeValue):
if value.address() is None or value.address() == 0:
raise Exception("")
nativeValue = value.nativeValue
if nativeValue is None:
nativeValue = cdbext.createValue(value.address(), nativeType)
index = 0 index = 0
nativeMember = nativeValue.childFromIndex(index) nativeMember = nativeValue.childFromIndex(index)
while nativeMember is not None: while nativeMember:
if nativeMember.address() != 0: if nativeMember.address() != 0:
yield self.fromNativeValue(nativeMember) yield self.fromNativeValue(nativeMember)
index += 1 index += 1
nativeMember = nativeValue.childFromIndex(index) 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): def nativeStructAlignment(self, nativeType):
#DumperBase.warn("NATIVE ALIGN FOR %s" % nativeType.name) #DumperBase.warn("NATIVE ALIGN FOR %s" % nativeType.name)
def handleItem(nativeFieldType, align): def handleItem(nativeFieldType, align):
@@ -307,6 +326,7 @@ class Dumper(DumperBase):
namespaceIndex = name.find('::') namespaceIndex = name.find('::')
if namespaceIndex > 0: if namespaceIndex > 0:
namespace = name[:namespaceIndex + 2] namespace = name[:namespaceIndex + 2]
self.qtNamespace = lambda: namespace
self.qtCustomEventFunc = self.parseAndEvaluate( self.qtCustomEventFunc = self.parseAndEvaluate(
'%s!%sQObject::customEvent' % '%s!%sQObject::customEvent' %
(self.qtCoreModuleName(), namespace)).address() (self.qtCoreModuleName(), namespace)).address()
@@ -498,7 +518,7 @@ class Dumper(DumperBase):
else: else:
val = self.Value(self) val = self.Value(self)
val.laddress = value.pointer() val.laddress = value.pointer()
val._type = value.type.dereference() val._type = DumperBase.Type(self, value.type.targetName)
val.nativeValue = value.nativeValue val.nativeValue = value.nativeValue
return val return val
@@ -519,3 +539,424 @@ class Dumper(DumperBase):
def symbolAddress(self, symbolName): def symbolAddress(self, symbolName):
res = self.nativeParseAndEvaluate(symbolName) res = self.nativeParseAndEvaluate(symbolName)
return None if res is None else res.address() 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.startswith('unsigned ') \
or self.type.name.find(' unsigned ') != -1 or self.type.name.find(' unsigned ') != -1
if bitsize is None: if bitsize is None:
bitsize = self.type.bitsize() bitsize = self.type.lbitsize
return self.extractInteger(bitsize, unsigned) return self.extractInteger(bitsize, unsigned)
def floatingPoint(self): def floatingPoint(self):
@@ -3512,26 +3512,40 @@ class DumperBase():
tdata.moduleName = self.moduleName tdata.moduleName = self.moduleName
return tdata return tdata
@property
def bitsize(self):
if callable(self.lbitsize):
self.lbitsize = self.lbitsize()
return self.lbitsize
class Type(): class Type():
def __init__(self, dumper, typeId): def __init__(self, dumper, typeId):
self.typeId = typeId self.typeId = typeId.replace('@', dumper.qtNamespace())
self.dumper = dumper self.dumper = dumper
self.tdata = dumper.typeData.get(typeId, None) self.initialized = False
if self.tdata is None:
#DumperBase.warn('USING : %s' % self.typeId)
self.dumper.lookupType(self.typeId)
self.tdata = self.dumper.typeData.get(self.typeId)
def __str__(self): def __str__(self):
#return self.typeId #return self.typeId
return self.stringify() 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 @property
def name(self): def name(self):
tdata = self.dumper.typeData.get(self.typeId) return self.typeId if self.tdata is None else self.tdata.name
if tdata is None:
return self.typeId
return tdata.name
@property @property
def code(self): def code(self):
@@ -3539,7 +3553,7 @@ class DumperBase():
@property @property
def lbitsize(self): def lbitsize(self):
return self.tdata.lbitsize return self.tdata.bitsize
@property @property
def lbitpos(self): def lbitpos(self):
@@ -3547,15 +3561,25 @@ class DumperBase():
@property @property
def ltarget(self): def ltarget(self):
if isinstance(self.tdata.ltarget, str):
self.tdata.ltarget = self.dumper.createType(self.tdata.ltarget)
return 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 @property
def moduleName(self): def moduleName(self):
if callable(self.tdata.moduleName):
self.tdata.moduleName = self.tdata.moduleName()
return self.tdata.moduleName return self.tdata.moduleName
def stringify(self): def stringify(self):
return 'Type(name="%s",bsize=%s,code=%s)' \ 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): def __getitem__(self, index):
if self.dumper.isInt(index): if self.dumper.isInt(index):
@@ -3659,7 +3683,7 @@ class DumperBase():
def alignment(self): def alignment(self):
if self.tdata.code == TypeCode.Typedef: 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.code in (TypeCode.Integral, TypeCode.Float, TypeCode.Enum):
if self.tdata.name in ('double', 'long long', 'unsigned long long'): if self.tdata.name in ('double', 'long long', 'unsigned long long'):
# Crude approximation. # Crude approximation.
@@ -3678,7 +3702,7 @@ class DumperBase():
return self.dumper.createPointerType(self) return self.dumper.createPointerType(self)
def target(self): def target(self):
return self.tdata.ltarget return self.ltarget
def stripTypedefs(self): def stripTypedefs(self):
if isinstance(self, self.dumper.Type) and self.code != TypeCode.Typedef: if isinstance(self, self.dumper.Type) and self.code != TypeCode.Typedef:
@@ -3687,7 +3711,7 @@ class DumperBase():
return self.ltarget return self.ltarget
def size(self): def size(self):
bs = self.bitsize() bs = self.lbitsize
if bs % 8 != 0: if bs % 8 != 0:
DumperBase.warn('ODD SIZE: %s' % self) DumperBase.warn('ODD SIZE: %s' % self)
return (7 + bs) >> 3 return (7 + bs) >> 3
@@ -3797,12 +3821,12 @@ class DumperBase():
return val return val
def createPointerType(self, targetType): def createPointerType(self, targetType):
if not isinstance(targetType, self.Type): if not isinstance(targetType, (str, self.Type)):
raise RuntimeError('Expected type in createPointerType(), got %s' raise RuntimeError('Expected type or str in createPointerType(), got %s'
% type(targetType)) % type(targetType))
typeId = targetType.typeId + ' *' typeId = (targetType if isinstance(targetType, str) else targetType.typeId) + ' *'
tdata = self.TypeData(self, 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.lbitsize = 8 * self.ptrSize()
tdata.code = TypeCode.Pointer tdata.code = TypeCode.Pointer
tdata.ltarget = targetType tdata.ltarget = targetType
@@ -3927,7 +3951,7 @@ class DumperBase():
tdata = self.typeData.get(typish, None) tdata = self.typeData.get(typish, None)
if tdata is not None: if tdata is not None:
if tdata.lbitsize 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) return self.Type(self, typish)
knownType = self.lookupType(typish) knownType = self.lookupType(typish)
@@ -3944,7 +3968,7 @@ class DumperBase():
if typish.endswith('*'): if typish.endswith('*'):
tdata.code = TypeCode.Pointer tdata.code = TypeCode.Pointer
tdata.lbitsize = 8 * self.ptrSize() tdata.lbitsize = 8 * self.ptrSize()
tdata.ltarget = self.createType(typish[:-1].strip()) tdata.ltarget = typish[:-1].strip()
typeobj = self.Type(self, tdata.typeId) typeobj = self.Type(self, tdata.typeId)
#DumperBase.warn('CREATE TYPE: %s' % typeobj.stringify()) #DumperBase.warn('CREATE TYPE: %s' % typeobj.stringify())

View File

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

View File

@@ -1,4 +1,5 @@
[General] [General]
Includes=dark.figmatokens
ThemeName=Dark ThemeName=Dark
PreferredStyles= PreferredStyles=
DefaultTextEditorColorScheme=dark.xml DefaultTextEditorColorScheme=dark.xml
@@ -406,39 +407,6 @@ Debugger_WatchItem_ValueChanged=ffff6666
Debugger_Breakpoint_TextMarkColor=ffff4040 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_TextColor=text
Timeline_BackgroundColor1=normalBackground Timeline_BackgroundColor1=normalBackground
Timeline_BackgroundColor2=ff444444 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] [General]
Includes=light.figmatokens
ThemeName=Classic ThemeName=Classic
PreferredStyles= PreferredStyles=
@@ -398,39 +399,6 @@ Debugger_WatchItem_ValueChanged=ffc80000
Debugger_Breakpoint_TextMarkColor=ffff4040 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_TextColor=darkText
Timeline_BackgroundColor1=ffffffff Timeline_BackgroundColor1=ffffffff
Timeline_BackgroundColor2=fff6f6f6 Timeline_BackgroundColor2=fff6f6f6

View File

@@ -1,4 +1,5 @@
[General] [General]
Includes=light.figmatokens
ThemeName=Design Light ThemeName=Design Light
PreferredStyles= PreferredStyles=
@@ -410,39 +411,6 @@ Debugger_WatchItem_ValueChanged=ffbf0303
Debugger_Breakpoint_TextMarkColor=ffff4040 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_TextColor=text
Timeline_BackgroundColor1=normalBackground Timeline_BackgroundColor1=normalBackground
Timeline_BackgroundColor2=fff6f6f6 Timeline_BackgroundColor2=fff6f6f6

View File

@@ -1,4 +1,5 @@
[General] [General]
Includes=dark.figmatokens
ThemeName=Design Dark ThemeName=Design Dark
PreferredStyles= PreferredStyles=
DefaultTextEditorColorScheme=creator-dark.xml DefaultTextEditorColorScheme=creator-dark.xml
@@ -414,39 +415,6 @@ Debugger_WatchItem_ValueChanged=ffff6666
Debugger_Breakpoint_TextMarkColor=ffff4040 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_TextColor=text
Timeline_BackgroundColor1=normalBackground Timeline_BackgroundColor1=normalBackground
Timeline_BackgroundColor2=ff444444 Timeline_BackgroundColor2=ff444444

View File

@@ -1,4 +1,5 @@
[General] [General]
Includes=dark.figmatokens
ThemeName=Flat Dark ThemeName=Flat Dark
PreferredStyles= PreferredStyles=
DefaultTextEditorColorScheme=creator-dark.xml DefaultTextEditorColorScheme=creator-dark.xml
@@ -410,39 +411,6 @@ Debugger_WatchItem_ValueChanged=ffff6666
Debugger_Breakpoint_TextMarkColor=ffff4040 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_TextColor=text
Timeline_BackgroundColor1=normalBackground Timeline_BackgroundColor1=normalBackground
Timeline_BackgroundColor2=ff444444 Timeline_BackgroundColor2=ff444444

View File

@@ -1,4 +1,5 @@
[General] [General]
Includes=light.figmatokens
ThemeName=Flat Light ThemeName=Flat Light
PreferredStyles= PreferredStyles=
@@ -407,39 +408,6 @@ Debugger_WatchItem_ValueChanged=ffbf0303
Debugger_Breakpoint_TextMarkColor=ffff4040 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_TextColor=text
Timeline_BackgroundColor1=normalBackground Timeline_BackgroundColor1=normalBackground
Timeline_BackgroundColor2=fff6f6f6 Timeline_BackgroundColor2=fff6f6f6

View File

@@ -1,4 +1,5 @@
[General] [General]
Includes=light.figmatokens
ThemeName=Flat ThemeName=Flat
PreferredStyles= PreferredStyles=
@@ -405,39 +406,6 @@ Debugger_WatchItem_ValueChanged=ffbf0303
Debugger_Breakpoint_TextMarkColor=ffff4040 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_TextColor=text
Timeline_BackgroundColor1=normalBackground Timeline_BackgroundColor1=normalBackground
Timeline_BackgroundColor2=fff6f6f6 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; std::string typeName = typeNameIn;
if (typeName.back() == '*') { if (typeName.back() == '*') {
typeName.pop_back(); typeName.pop_back();
trimBack(typeName);
if (endsWith(typeName, "const"))
typeName = typeName.erase(typeName.size() - 5, 5);
} else { } else {
const auto arrayPosition = typeName.find_first_of('['); const auto arrayPosition = typeName.find_first_of('[');
if (arrayPosition != std::string::npos if (arrayPosition != std::string::npos
@@ -296,35 +299,19 @@ int PyType::code() const
return std::nullopt; return std::nullopt;
}; };
if (!resolve()) if (m_tag >= 0) {
return parseTypeName(name()).value_or(TypeCodeUnresolvable); switch (m_tag) {
case SymTagUDT: return TypeCodeStruct;
if (m_tag < 0) { case SymTagEnum: return TypeCodeEnum;
if (const std::optional<TypeCodes> typeCode = parseTypeName(name())) case SymTagTypedef: return TypeCodeTypedef;
return *typeCode; case SymTagFunctionType: return TypeCodeFunction;
case SymTagPointerType: return TypeCodePointer;
IDebugSymbolGroup2 *sg = 0; case SymTagArrayType: return TypeCodeArray;
if (FAILED(ExtensionCommandContext::instance()->symbols()->CreateSymbolGroup2(&sg))) case SymTagBaseType: return isIntegralType(name()) ? TypeCodeIntegral : TypeCodeFloat;
return TypeCodeStruct; default: break;
}
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();
} }
switch (m_tag) { return parseTypeName(name()).value_or(TypeCodeStruct);
case SymTagUDT: return TypeCodeStruct;
case SymTagEnum: return TypeCodeEnum;
case SymTagTypedef: return TypeCodeTypedef;
case SymTagFunctionType: return TypeCodeFunction;
case SymTagPointerType: return TypeCodePointer;
case SymTagArrayType: return TypeCodeArray;
case SymTagBaseType: return isIntegralType(name()) ? TypeCodeIntegral : TypeCodeFloat;
default: break;
}
return TypeCodeStruct;
} }
PyType PyType::target() const 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_RET_STD_STRING(module, PY_OBJ_NAME)
PY_FUNC(moduleId, PY_OBJ_NAME, "K") PY_FUNC(moduleId, PY_OBJ_NAME, "K")
PY_FUNC(arrayElements, 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_FUNC_DECL(templateArguments, PY_OBJ_NAME)
{ {
PY_IMPL_GUARD; PY_IMPL_GUARD;
@@ -568,6 +556,8 @@ static PyMethodDef typeMethods[] = {
"Returns the number of elements in an array or 0 for non array types"}, "Returns the number of elements in an array or 0 for non array types"},
{"templateArguments", PyCFunction(templateArguments), METH_NOARGS, {"templateArguments", PyCFunction(templateArguments), METH_NOARGS,
"Returns all template arguments."}, "Returns all template arguments."},
{"resolved", PyCFunction(resolved), METH_NOARGS,
"Returns whether the type is resolved"},
{NULL} /* Sentinel */ {NULL} /* Sentinel */
}; };

View File

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

View File

@@ -933,7 +933,17 @@ QColor StyleHelper::ensureReadableOn(const QColor &background, const QColor &des
return foreground; 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 static QStringList families = []{
const int id = QFontDatabase::addApplicationFont(":/studiofonts/TitilliumWeb-Regular.ttf"); 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::UiElementH5, {14, 16, QFont::DemiBold}},
{StyleHelper::UiElementH6, {12, 14, QFont::DemiBold}}, {StyleHelper::UiElementH6, {12, 14, QFont::DemiBold}},
{StyleHelper::UiElementH6Capital, {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::UiElementCaptionStrong, {10, 12, QFont::DemiBold}},
{StyleHelper::UiElementCaption, {10, 12, QFont::Normal}}, {StyleHelper::UiElementCaption, {10, 12, QFont::Normal}},
{StyleHelper::UIElementIconStandard, {12, 16, QFont::Normal}}, {StyleHelper::UiElementIconStandard, {12, 16, QFont::Medium}},
{StyleHelper::UIElementIconActive, {12, 16, QFont::DemiBold}}, {StyleHelper::UiElementIconActive, {12, 16, QFont::DemiBold}},
}; };
QTC_ASSERT(metrics.count(element) > 0, return metrics.at(StyleHelper::UiElementCaptionStrong)); QTC_ASSERT(metrics.count(element) > 0, return metrics.at(StyleHelper::UiElementCaptionStrong));
return metrics.at(element); return metrics.at(element);
@@ -983,8 +997,10 @@ QFont StyleHelper::uiFont(UiElement element)
case UiElementH3: case UiElementH3:
case UiElementH6Capital: case UiElementH6Capital:
font.setCapitalization(QFont::AllUppercase); font.setCapitalization(QFont::AllUppercase);
break; [[fallthrough]];
default: default:
if (!applicationFontFamilies().isEmpty())
font.setFamilies(applicationFontFamilies());
break; 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"; constexpr char C_QT_SCALE_FACTOR_ROUNDING_POLICY[] = "QT_SCALE_FACTOR_ROUNDING_POLICY";
namespace SpacingTokens { namespace SpacingTokens {
constexpr int VPaddingXXS = 4; // Top and bottom padding 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 HPaddingXxs = 4; // Left and right padding within the component
constexpr int VGapXXS = 4; // Vertical Space between TEXT LINE 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 HGapXxs = 4; // Horizontal Space between elements within the Component
constexpr int VPaddingXS = 8; constexpr int VPaddingXs = 8;
constexpr int HPaddingXS = 8; constexpr int HPaddingXs = 8;
constexpr int VGapXS = 4; constexpr int VGapXs = 4;
constexpr int HGapXS = 8; constexpr int HGapXs = 8;
constexpr int VPaddingS = 8; constexpr int VPaddingS = 8;
constexpr int HPaddingS = 16; constexpr int HPaddingS = 16;
@@ -62,10 +62,15 @@ namespace SpacingTokens {
constexpr int VGapM = 4; constexpr int VGapM = 4;
constexpr int HGapM = 16; constexpr int HGapM = 16;
constexpr int VPaddingL = 12; constexpr int VPaddingL = 16;
constexpr int HPaddingL = 24; constexpr int HPaddingL = 24;
constexpr int VGapL = 8; constexpr int VGapL = 8;
constexpr int HGapL = 16; constexpr int HGapL = 16;
constexpr int ExPaddingGapS = 2;
constexpr int ExPaddingGapM = 6;
constexpr int ExPaddingGapL = 12;
constexpr int ExVPaddingGapXl = 24;
} }
enum ToolbarStyle { enum ToolbarStyle {
@@ -82,10 +87,14 @@ enum UiElement {
UiElementH5, UiElementH5,
UiElementH6, UiElementH6,
UiElementH6Capital, UiElementH6Capital,
UiElementBody1,
UiElementBody2,
UiElementButtonMedium,
UiElementButtonSmall,
UiElementCaptionStrong, UiElementCaptionStrong,
UiElementCaption, UiElementCaption,
UIElementIconStandard, UiElementIconStandard,
UIElementIconActive, UiElementIconActive,
}; };
// Height of the project explorer navigation bar // 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")); QMetaEnum e = m.enumerator(m.indexOfEnumerator("Flag"));
for (int i = 0, total = e.keyCount(); i < total; ++i) { for (int i = 0, total = e.keyCount(); i < total; ++i) {
const QString key = QLatin1String(e.key(i)); const QString key = QLatin1String(e.key(i));
QTC_ASSERT(settings.contains(key), return );
d->flags[i] = settings.value(key).toBool(); d->flags[i] = settings.value(key).toBool();
} }
settings.endGroup(); settings.endGroup();

View File

@@ -247,18 +247,6 @@ public:
Token_Notification_Neutral, Token_Notification_Neutral,
Token_Notification_Danger, 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 Library */
Timeline_TextColor, Timeline_TextColor,
Timeline_BackgroundColor1, Timeline_BackgroundColor1,

View File

@@ -819,15 +819,13 @@ void AndroidRunnerWorker::removeForwardPort(const QString &port)
void AndroidRunnerWorker::onProcessIdChanged(PidUserPair pidUser) void AndroidRunnerWorker::onProcessIdChanged(PidUserPair pidUser)
{ {
qint64 pid = pidUser.first;
qint64 user = pidUser.second;
// Don't write to m_psProc from a different thread // Don't write to m_psProc from a different thread
QTC_ASSERT(QThread::currentThread() == thread(), return); QTC_ASSERT(QThread::currentThread() == thread(), return);
qCDebug(androidRunWorkerLog) << "Process ID changed from:" << m_processPID qCDebug(androidRunWorkerLog) << "Process ID changed from:" << m_processPID
<< "to:" << pid; << "to:" << pidUser.first;
m_processPID = pid; m_processPID = pidUser.first;
m_processUser = user; m_processUser = pidUser.second;
if (pid == -1) { if (m_processPID == -1) {
emit remoteProcessFinished(QLatin1String("\n\n") + Tr::tr("\"%1\" died.") emit remoteProcessFinished(QLatin1String("\n\n") + Tr::tr("\"%1\" died.")
.arg(m_packageName)); .arg(m_packageName));
// App died/killed. Reset log, monitor, jdb & gdbserver/lldb-server processes. // 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); QTC_ASSERT(m_psIsAlive, return);
m_psIsAlive->setObjectName("IsAliveProcess"); m_psIsAlive->setObjectName("IsAliveProcess");
m_psIsAlive->setProcessChannelMode(QProcess::MergedChannels); 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 <projectexplorer/projectexplorerconstants.h>
#include <utils/async.h>
#include <utils/detailswidget.h> #include <utils/detailswidget.h>
#include <utils/hostosinfo.h> #include <utils/hostosinfo.h>
#include <utils/infolabel.h> #include <utils/infolabel.h>
#include <utils/layoutbuilder.h> #include <utils/layoutbuilder.h>
#include <utils/pathchooser.h> #include <utils/pathchooser.h>
#include <utils/progressindicator.h> #include <utils/progressindicator.h>
#include <utils/qtcprocess.h>
#include <utils/qtcassert.h> #include <utils/qtcassert.h>
#include <utils/qtcprocess.h>
#include <utils/utilsicons.h> #include <utils/utilsicons.h>
#include <QCheckBox> #include <QCheckBox>
@@ -203,6 +204,56 @@ enum OpenSslValidation {
OpenSslCmakeListsPathExists 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() AndroidSettingsWidget::AndroidSettingsWidget()
{ {
setWindowTitle(Tr::tr("Android Configuration")); setWindowTitle(Tr::tr("Android Configuration"));
@@ -307,6 +358,15 @@ AndroidSettingsWidget::AndroidSettingsWidget()
Tr::tr("OpenSSL settings have errors."), Tr::tr("OpenSSL settings have errors."),
openSslDetailsWidget); 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, connect(m_openJdkLocationPathChooser, &PathChooser::rawPathChanged,
this, &AndroidSettingsWidget::validateJdk); this, &AndroidSettingsWidget::validateJdk);
if (androidConfig().openJDKLocation().isEmpty()) if (androidConfig().openJDKLocation().isEmpty())
@@ -533,10 +593,9 @@ bool AndroidSettingsWidget::isDefaultNdkSelected() const
void AndroidSettingsWidget::validateJdk() void AndroidSettingsWidget::validateJdk()
{ {
androidConfig().setOpenJDKLocation(m_openJdkLocationPathChooser->filePath()); androidConfig().setOpenJDKLocation(m_openJdkLocationPathChooser->filePath());
bool jdkPathExists = androidConfig().openJDKLocation().exists(); expected_str<void> test = testJavaC(androidConfig().openJDKLocation());
const FilePath bin = androidConfig().openJDKLocation()
.pathAppended("bin/javac" QTC_HOST_EXE_SUFFIX); m_androidSummary->setPointValid(JavaPathExistsAndWritableRow, test.has_value());
m_androidSummary->setPointValid(JavaPathExistsAndWritableRow, jdkPathExists && bin.exists());
updateUI(); updateUI();

View File

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

View File

@@ -22,13 +22,12 @@ private:
void handleIdentifier(); void handleIdentifier();
void handleTestCase(bool isScenario); void handleTestCase(bool isScenario);
void handleParameterizedTestCase(bool isFixture); void handleParameterizedTestCase(bool isFixture);
void handleFixtureOrRegisteredTestCase(bool isFixture); void handleFixtureOrRegisteredTestCase(bool isFixture, bool isScenario);
QString getStringLiteral(CPlusPlus::Kind &stoppedAtKind); QString getStringLiteral(CPlusPlus::Kind &stoppedAtKind);
bool skipCommentsUntil(CPlusPlus::Kind nextExpectedKind); // moves currentIndex if succeeds bool skipCommentsUntil(CPlusPlus::Kind nextExpectedKind); // moves currentIndex if succeeds
CPlusPlus::Kind skipUntilCorrespondingRParen(); // moves currentIndex CPlusPlus::Kind skipUntilCorrespondingRParen(); // moves currentIndex
bool skipFixtureParameter(); bool skipParameter();
bool skipFunctionParameter();
const QByteArray &m_source; const QByteArray &m_source;
const CPlusPlus::LanguageFeatures &m_features; 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("TEMPLATE_TEST_CASE_SIG"), QStringLiteral("TEMPLATE_PRODUCT_TEST_CASE_SIG"),
QStringLiteral("TEST_CASE_METHOD"), QStringLiteral("TEMPLATE_TEST_CASE_METHOD"), QStringLiteral("TEST_CASE_METHOD"), QStringLiteral("TEMPLATE_TEST_CASE_METHOD"),
QStringLiteral("TEMPLATE_PRODUCT_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_PRODUCT_TEST_CASE_METHOD_SIG"),
QStringLiteral("TEMPLATE_TEST_CASE_METHOD"), QStringLiteral("TEMPLATE_TEST_CASE_METHOD"),
QStringLiteral("TEMPLATE_LIST_TEST_CASE_METHOD"), QStringLiteral("TEMPLATE_LIST_TEST_CASE_METHOD"),
@@ -105,7 +107,7 @@ bool CatchTestParser::processDocument(QPromise<TestParseResultPtr> &promise,
if (!hasCatchNames(doc)) { if (!hasCatchNames(doc)) {
static const QRegularExpression regex("\\b(CATCH_)?" 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_TEST_CASE(_METHOD)?_SIG|"
"TEMPLATE_PRODUCT_TEST_CASE(_METHOD)?_SIG|" "TEMPLATE_PRODUCT_TEST_CASE(_METHOD)?_SIG|"
"TEMPLATE_LIST_TEST_CASE_METHOD|METHOD_AS_TEST_CASE|" "TEMPLATE_LIST_TEST_CASE_METHOD|METHOD_AS_TEST_CASE|"

View File

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

View File

@@ -2,18 +2,18 @@
<qresource prefix="/axivion"> <qresource prefix="/axivion">
<file>images/axivion.png</file> <file>images/axivion.png</file>
<file>images/axivion@2x.png</file> <file>images/axivion@2x.png</file>
<file>images/button-av.png</file> <file>images/button-AV.png</file>
<file>images/button-av@2x.png</file> <file>images/button-AV@2x.png</file>
<file>images/button-cl.png</file> <file>images/button-CL.png</file>
<file>images/button-cl@2x.png</file> <file>images/button-CL@2x.png</file>
<file>images/button-cy.png</file> <file>images/button-CY.png</file>
<file>images/button-cy@2x.png</file> <file>images/button-CY@2x.png</file>
<file>images/button-de.png</file> <file>images/button-DE.png</file>
<file>images/button-de@2x.png</file> <file>images/button-DE@2x.png</file>
<file>images/button-mv.png</file> <file>images/button-MV.png</file>
<file>images/button-mv@2x.png</file> <file>images/button-MV@2x.png</file>
<file>images/button-sv.png</file> <file>images/button-SV.png</file>
<file>images/button-sv@2x.png</file> <file>images/button-SV@2x.png</file>
<file>images/sortAsc.png</file> <file>images/sortAsc.png</file>
<file>images/sortAsc@2x.png</file> <file>images/sortAsc@2x.png</file>
<file>images/sortDesc.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); connect(m_pathGlobFilter, &QLineEdit::textEdited, this, &IssuesWidget::onSearchParameterChanged);
m_issuesView = new BaseTreeView(this); 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); m_headerView = new IssueHeaderView(this);
connect(m_headerView, &IssueHeaderView::sortTriggered, connect(m_headerView, &IssueHeaderView::sortTriggered,
this, &IssuesWidget::onSearchParameterChanged); this, &IssuesWidget::onSearchParameterChanged);
@@ -562,7 +564,7 @@ void IssuesWidget::updateBasicProjectInfo(std::optional<Dto::ProjectInfoDto> inf
int buttonId = 0; int buttonId = 0;
for (const Dto::IssueKindInfoDto &kind : issueKinds) { for (const Dto::IssueKindInfoDto &kind : issueKinds) {
auto button = new QToolButton(this); auto button = new QToolButton(this);
button->setIcon(iconForIssue(kind.prefix)); button->setIcon(iconForIssue(kind.getOptionalPrefixEnum()));
button->setToolTip(kind.nicePluralName); button->setToolTip(kind.nicePluralName);
button->setCheckable(true); button->setCheckable(true);
connect(button, &QToolButton::clicked, this, [this, prefix = kind.prefix]{ connect(button, &QToolButton::clicked, this, [this, prefix = kind.prefix]{

View File

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

View File

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

View File

@@ -3,6 +3,7 @@
#include "dynamiclistmodel.h" #include "dynamiclistmodel.h"
#include "axivionplugin.h"
#include "axiviontr.h" #include "axiviontr.h"
#include <utils/qtcassert.h> #include <utils/qtcassert.h>
@@ -10,8 +11,6 @@
namespace Axivion::Internal { namespace Axivion::Internal {
constexpr int pageSize = 150;
DynamicListModel::DynamicListModel(QObject *parent) DynamicListModel::DynamicListModel(QObject *parent)
: QAbstractItemModel(parent) : QAbstractItemModel(parent)
{ {
@@ -161,7 +160,7 @@ QModelIndex DynamicListModel::indexForItem(const ListItem *item) const
void DynamicListModel::onNeedFetch(int row) void DynamicListModel::onNeedFetch(int row)
{ {
m_fetchStart = row; m_fetchStart = row;
m_fetchEnd = row + pageSize; m_fetchEnd = row + DefaultSearchLimit;
if (m_fetchStart < 0) if (m_fetchStart < 0)
return; return;
m_fetchMoreTimer.start(); m_fetchMoreTimer.start();
@@ -171,14 +170,14 @@ void DynamicListModel::fetchNow()
{ {
const int old = m_lastFetch; const int old = m_lastFetch;
m_lastFetch = m_fetchStart; // we need the "original" fetch request to avoid endless loop 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) { if (old != -1) {
const int diff = old - m_fetchStart; const int diff = old - m_fetchStart;
if (0 < diff && diff < pageSize) { if (0 < diff && diff < DefaultSearchLimit) {
m_fetchStart = qMax(old - pageSize, 0); m_fetchStart = qMax(old - DefaultSearchLimit, 0);
} else if (0 > diff && diff > -pageSize) { } else if (0 > diff && diff > - DefaultSearchLimit) {
m_fetchStart = old + pageSize; m_fetchStart = old + DefaultSearchLimit;
if (m_expectedRowCount && m_fetchStart > *m_expectedRowCount) if (m_expectedRowCount && m_fetchStart > *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 QTC_CHECK(m_expectedRowCount ? m_fetchStart <= *m_expectedRowCount
: m_fetchStart >= m_children.size()); : m_fetchStart >= m_children.size());
emit fetchRequested(m_fetchStart, pageSize); emit fetchRequested(m_fetchStart, DefaultSearchLimit);
m_fetchStart = -1; m_fetchStart = -1;
m_fetchEnd = -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 if (y > 1 && y < height() - 2) { // TODO improve
const int pos = position.x(); const int pos = position.x();
const int logical = logicalIndexAt(pos); const int logical = logicalIndexAt(pos);
const int end = sectionViewportPosition(logical) + sectionSize(logical); m_lastToggleLogicalPos = logical;
const int start = end - ICON_SIZE - 2; 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; m_maybeToggleSort = start < pos && end > pos;
} }
} }
@@ -79,7 +81,8 @@ void IssueHeaderView::mouseReleaseEvent(QMouseEvent *event)
const QPoint position = event->position().toPoint(); const QPoint position = event->position().toPoint();
const int y = position.y(); const int y = position.y();
const int logical = logicalIndexAt(position.x()); 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 (m_sortableColumns.at(logical)) { // ignore non-sortable
if (y < height() / 2) // TODO improve if (y < height() / 2) // TODO improve
onToggleSort(logical, SortOrder::Ascending); onToggleSort(logical, SortOrder::Ascending);
@@ -88,6 +91,7 @@ void IssueHeaderView::mouseReleaseEvent(QMouseEvent *event)
} }
} }
} }
m_lastToggleLogicalPos = -1;
QHeaderView::mouseReleaseEvent(event); QHeaderView::mouseReleaseEvent(event);
} }
@@ -118,8 +122,10 @@ QSize IssueHeaderView::sectionSizeFromContents(int logicalIndex) const
const QSize oldSize = QHeaderView::sectionSizeFromContents(logicalIndex); const QSize oldSize = QHeaderView::sectionSizeFromContents(logicalIndex);
const QSize newSize = logicalIndex < m_columnWidths.size() const QSize newSize = logicalIndex < m_columnWidths.size()
? QSize(qMax(m_columnWidths.at(logicalIndex), oldSize.width()), oldSize.height()) : oldSize; ? 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 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)) if (!m_sortableColumns.at(logicalIndex))
return; return;
const int margin = style()->pixelMetric(QStyle::PM_HeaderGripMargin, nullptr, this);
const QIcon icon = iconForSorted(logicalIndex == m_currentSortIndex ? m_currentSortOrder : SortOrder::None); const QIcon icon = iconForSorted(logicalIndex == m_currentSortIndex ? m_currentSortOrder : SortOrder::None);
const int offset = qMax((rect.height() - ICON_SIZE), 0) / 2; 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); icon.paint(painter, iconRect);
} }

View File

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

View File

@@ -59,9 +59,14 @@ clang::format::FormatStyle calculateQtcStyle()
style.AllowShortFunctionsOnASingleLine = FormatStyle::SFS_Inline; style.AllowShortFunctionsOnASingleLine = FormatStyle::SFS_Inline;
style.AllowShortIfStatementsOnASingleLine = FormatStyle::SIS_Never; style.AllowShortIfStatementsOnASingleLine = FormatStyle::SIS_Never;
style.AllowShortLoopsOnASingleLine = false; style.AllowShortLoopsOnASingleLine = false;
style.AlwaysBreakAfterReturnType = FormatStyle::RTBS_None;
style.AlwaysBreakBeforeMultilineStrings = false; 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; style.AlwaysBreakTemplateDeclarations = FormatStyle::BTDS_Yes;
#endif
style.BinPackArguments = false; style.BinPackArguments = false;
style.BinPackParameters = false; style.BinPackParameters = false;
style.BraceWrapping.AfterClass = true; style.BraceWrapping.AfterClass = true;

View File

@@ -301,8 +301,24 @@ static CMakeBuildTarget toBuildTarget(const TargetDetails &t,
continue; continue;
const FilePath buildDir = relativeLibs ? buildDirectory : currentBuildDir; 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") if (f.role == "libraries")
tmp = tmp.parentDir(); tmp = tmp.parentDir();
@@ -312,19 +328,20 @@ static CMakeBuildTarget toBuildTarget(const TargetDetails &t,
// "/usr/local/lib" since these are usually in the standard search // "/usr/local/lib" since these are usually in the standard search
// paths. There probably are more, but the naming schemes are arbitrary // paths. There probably are more, but the naming schemes are arbitrary
// so we'd need to ask the linker ("ld --verbose | grep SEARCH_DIR"). // so we'd need to ask the linker ("ld --verbose | grep SEARCH_DIR").
if (buildDir.osType() == OsTypeWindows if (buildDir.osType() != OsTypeWindows
|| !isChildOf(tmp, && !isChildOf(tmp,
{"/lib", {"/lib", "/lib64", "/usr/lib", "/usr/lib64", "/usr/local/lib"}))
"/lib64",
"/usr/lib",
"/usr/lib64",
"/usr/local/lib"})) {
librarySeachPaths.append(tmp); 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 // Libraries often have their import libs in ../lib and the
// actual dll files in ../bin on windows. Qt is one example of that. // actual dll files in ../bin on windows. Qt is one example of that.
if (tmp.fileName() == "lib" && buildDir.osType() == OsTypeWindows) { if (tmp.fileName() == "lib" && buildDir.osType() == OsTypeWindows) {
const FilePath path = tmp.parentDir().pathAppended("bin"); const FilePath path = tmp.parentDir().pathAppended("bin");
if (path.isDir()) if (path.isDir() && path.pathAppended(*dllName).exists())
librarySeachPaths.append(path); librarySeachPaths.append(path);
} }
} }

View File

@@ -1,5 +1,9 @@
<RCC> <RCC>
<qresource prefix="/core"> <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.png</file>
<file>images/settingscategory_core@2x.png</file> <file>images/settingscategory_core@2x.png</file>
<file>images/settingscategory_design.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 "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 { namespace Core {
const char WITHACCENTCOLOR_PROPERTY_NAME[] = "_withAccentColor";
static QList<IWelcomePage *> g_welcomePages; static QList<IWelcomePage *> g_welcomePages;
const QList<IWelcomePage *> IWelcomePage::allWelcomePages() const QList<IWelcomePage *> IWelcomePage::allWelcomePages()
@@ -39,171 +22,4 @@ IWelcomePage::~IWelcomePage()
g_welcomePages.removeOne(this); 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 } // namespace Core

View File

@@ -7,13 +7,10 @@
#include <utils/id.h> #include <utils/id.h>
#include <QWidget>
#include <QObject> #include <QObject>
#include <functional>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
class QPixmap; class QWidget;
QT_END_NAMESPACE QT_END_NAMESPACE
namespace Core { namespace Core {
@@ -37,43 +34,4 @@ public:
static const QList<IWelcomePage *> allWelcomePages(); 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 } // Core

View File

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

View File

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

View File

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

View File

@@ -7,6 +7,7 @@
#include <utils/algorithm.h> #include <utils/algorithm.h>
#include <utils/fancylineedit.h> #include <utils/fancylineedit.h>
#include <utils/icon.h>
#include <utils/layoutbuilder.h> #include <utils/layoutbuilder.h>
#include <utils/qtcassert.h> #include <utils/qtcassert.h>
#include <utils/stylehelper.h> #include <utils/stylehelper.h>
@@ -26,36 +27,34 @@
#include <qdrawutil.h> #include <qdrawutil.h>
QT_BEGIN_NAMESPACE
void qt_blurImage(QImage &blurImage, qreal radius, bool quality, int transposed = 0);
QT_END_NAMESPACE
using namespace Utils; using namespace Utils;
namespace Core { namespace Core {
using namespace WelcomePageHelpers; using namespace WelcomePageHelpers;
using namespace StyleHelper::SpacingTokens;
static QColor themeColor(Theme::Color role) static QColor themeColor(Theme::Color role)
{ {
return creatorTheme()->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 { namespace WelcomePageHelpers {
QWidget *panelBar(QWidget *parent) void setBackgroundColor(QWidget *widget, Theme::Color colorRole)
{ {
auto frame = new QWidget(parent); QPalette palette = creatorTheme()->palette();
frame->setAutoFillBackground(true); const QPalette::ColorRole role = QPalette::Window;
frame->setMinimumWidth(WelcomePageHelpers::HSpacing); palette.setBrush(role, {});
QPalette pal; palette.setColor(role, creatorTheme()->color(colorRole));
pal.setBrush(QPalette::Window, {}); widget->setPalette(palette);
pal.setColor(QPalette::Window, themeColor(Theme::Welcome_BackgroundPrimaryColor)); widget->setBackgroundRole(role);
frame->setPalette(pal); widget->setAutoFillBackground(true);
return frame;
} }
void drawCardBackground(QPainter *painter, const QRectF &rect, void drawCardBackground(QPainter *painter, const QRectF &rect,
@@ -77,30 +76,351 @@ void drawCardBackground(QPainter *painter, const QRectF &rect,
painter->restore(); 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 } // namespace WelcomePageHelpers
SearchBox::SearchBox(QWidget *parent) enum WidgetState {
: WelcomePageFrame(parent) 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; switch (role) {
m_lineEdit->setFiltering(true); case Button::MediumPrimary: return mediumPrimaryTF;
m_lineEdit->setFrame(false); case Button::MediumSecondary: return mediumSecondaryTF;
m_lineEdit->setMinimumHeight(33); case Button::SmallPrimary: return smallPrimaryTF;
m_lineEdit->setAttribute(Qt::WA_MacShowFocusRect, false); 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); Button::Button(const QString &text, Role role, QWidget *parent)
// for the margins : QPushButton(text, parent)
pal.setColor(QPalette::Window, m_lineEdit->palette().color(QPalette::Base)); , m_role(role)
// for macOS dark mode {
pal.setColor(QPalette::WindowText, themeColor(Theme::Welcome_ForegroundPrimaryColor)); // Prevent QMacStyle::subElementRect(SE_PushButtonLayoutItem) from changing our geometry
pal.setColor(QPalette::Text, themeColor(Theme::Welcome_TextColor)); 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); setPalette(pal);
auto box = new QHBoxLayout(this); const QSize iconSize = searchBoxIcon().deviceIndependentSize().toSize();
box->setContentsMargins(10, 0, 1, 0); setContentsMargins({HPaddingXs, ExPaddingGapM,
box->addWidget(m_lineEdit); 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) GridView::GridView(QWidget *parent)
@@ -115,7 +435,7 @@ GridView::GridView(QWidget *parent)
setUniformItemSizes(true); setUniformItemSizes(true);
QPalette pal; 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. setPalette(pal); // Makes a difference on Mac.
} }
@@ -147,10 +467,11 @@ bool SectionGridView::hasHeightForWidth() const
int SectionGridView::heightForWidth(int width) 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 rowCount = (model()->rowCount() + columnCount - 1) / columnCount;
const int maxRowCount = m_maxRows ? std::min(*m_maxRows, rowCount) : rowCount; 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) void SectionGridView::wheelEvent(QWheelEvent *e)
@@ -165,8 +486,9 @@ bool SectionGridView::event(QEvent *e)
{ {
if (e->type() == QEvent::Resize) { if (e->type() == QEvent::Resize) {
const auto itemsFit = [this](const QSize &size) { const auto itemsFit = [this](const QSize &size) {
const int maxColumns = std::max(size.width() / WelcomePageHelpers::GridItemWidth, 1); const QSize itemSize = ListItemDelegate::itemSize();
const int maxRows = std::max(size.height() / WelcomePageHelpers::GridItemHeight, 1); 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 maxItems = maxColumns * maxRows;
const int items = model()->rowCount(); const int items = model()->rowCount();
return maxItems >= items; return maxItems >= items;
@@ -426,47 +748,123 @@ bool ListModelFilter::leaveFilterAcceptsRowBeforeFiltering(const ListItem *, boo
return false; return false;
} }
ListItemDelegate::ListItemDelegate() constexpr TextFormat titleTF {Theme::Token_Text_Default, StyleHelper::UiElementIconActive};
: backgroundPrimaryColor(themeColor(Theme::Welcome_BackgroundPrimaryColor)) constexpr TextFormat descriptionTF {titleTF.themeColor, StyleHelper::UiElementCaption};
, backgroundSecondaryColor(themeColor(Theme::Welcome_BackgroundSecondaryColor)) constexpr TextFormat tagsLabelTF {Theme::Token_Text_Muted, StyleHelper::UiElementCaptionStrong};
, foregroundPrimaryColor(themeColor(Theme::Welcome_ForegroundPrimaryColor)) constexpr TextFormat tagsTF {Theme::Token_Accent_Default, tagsLabelTF.uiElement};
, foregroundSecondaryColor(themeColor(Theme::Welcome_ForegroundSecondaryColor))
, hoverColor(themeColor(Theme::Welcome_HoverColor)) constexpr qreal itemOutlineWidth = 1;
, textColor(themeColor(Theme::Welcome_TextColor)) 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, void ListItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const 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 ListItem *item = index.data(ListModel::ItemRole).value<Core::ListItem *>();
const QRect rc = option.rect; const QFont tagsLabelFont = tagsLabelTF.font();
const QRect tileRect(0, 0, rc.width() - GridItemGap, rc.height() - GridItemGap); const QFontMetrics tagsLabelFM(tagsLabelFont);
const QSize thumbnailBgSize = GridItemImageSize.grownBy(QMargins(1, 1, 1, 1)); const QFont descriptionFont = descriptionTF.font();
const QRect thumbnailBgRect((tileRect.width() - thumbnailBgSize.width()) / 2, GridItemGap, const QFontMetrics descriptionFM(descriptionFont);
thumbnailBgSize.width(), thumbnailBgSize.height());
const QRect textArea = tileRect.adjusted(GridItemGap, GridItemGap, -GridItemGap, -GridItemGap); 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; 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->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; const int shiftY = thumbnailAreaR.bottom();
wrapped.setWrapMode(QTextOption::WordWrap);
int offset = 0; 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 (hovered) {
if (index != m_previousIndex) { if (index != m_previousIndex) {
m_previousIndex = index; m_previousIndex = index;
@@ -476,12 +874,12 @@ void ListItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opti
m_currentWidget = qobject_cast<QAbstractItemView *>( m_currentWidget = qobject_cast<QAbstractItemView *>(
const_cast<QWidget *>(option.widget)); const_cast<QWidget *>(option.widget));
} }
constexpr float hoverAnimationDuration = 260; animationProgress = qreal(m_startTime.elapsed()) / hoverDuration.count();
animationProgress = m_startTime.elapsed() / hoverAnimationDuration;
if (animationProgress < 1) { if (animationProgress < 1) {
static const QEasingCurve animationCurve(QEasingCurve::OutCubic); static const QEasingCurve animationCurve(hoverEasing);
offset = animationCurve.valueForProgress(animationProgress) * shiftY; offset = animationCurve.valueForProgress(animationProgress) * shiftY;
QTimer::singleShot(10, this, &ListItemDelegate::goon); using namespace std::chrono_literals;
QTimer::singleShot(10ms, this, &ListItemDelegate::goon);
} else { } else {
offset = shiftY; offset = shiftY;
} }
@@ -489,124 +887,125 @@ void ListItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opti
m_previousIndex = QModelIndex(); m_previousIndex = QModelIndex();
} }
const QRect shiftedTextRect = textRect.adjusted(0, -offset, 0, -offset);
// The pixmap. // The pixmap.
const QPixmap pm = index.data(ListModel::ItemImageRole).value<QPixmap>(); const QPixmap pm = index.data(ListModel::ItemImageRole).value<QPixmap>();
QPoint thumbnailPos = thumbnailBgRect.center(); QPoint thumbnailPos = thumbnailAreaR.center();
if (!pm.isNull()) { if (!pm.isNull()) {
painter->fillRect(thumbnailBgRect, backgroundSecondaryColor); painter->fillRect(thumbnailAreaR, themeColor(Theme::Token_Background_Default));
thumbnailPos.rx() -= pm.width() / pm.devicePixelRatio() / 2 - 1; thumbnailPos.rx() -= pm.width() / pm.devicePixelRatio() / 2 - 1;
thumbnailPos.ry() -= pm.height() / pm.devicePixelRatio() / 2 - 1; thumbnailPos.ry() -= pm.height() / pm.devicePixelRatio() / 2 - 1;
painter->drawPixmap(thumbnailPos, pm); painter->drawPixmap(thumbnailPos, pm);
painter->setPen(foregroundPrimaryColor); painter->setPen(titleTF.color());
drawPixmapOverlay(item, painter, option, thumbnailBgRect); drawPixmapOverlay(item, painter, option, thumbnailAreaR);
} else { } else {
// The description text as fallback. // The description text as fallback.
painter->setPen(textColor); painter->setPen(descriptionTF.color());
painter->setFont(descriptionFont); painter->setFont(descriptionTF.font());
painter->drawText(textArea, item->description, wrapped); painter->drawText(thumbnailAreaR, item->description, wrapTO);
} }
// The description background // The description background
QRect backgroundPortionR = bgR;
if (offset) { if (offset) {
QRect backgroundPortionRect = tileRect; backgroundPortionR.setTop(shiftY - offset);
backgroundPortionRect.setTop(shiftY - offset);
if (!pm.isNull()) { if (!pm.isNull()) {
if (m_blurredThumbnail.isNull()) { if (m_blurredThumbnail.isNull()) {
constexpr int blurRadius = 50; constexpr int filterMargin = hoverBlurRadius;
QImage thumbnail(tileRect.size() + QSize(blurRadius, blurRadius) * 2, QImage thumbnail(bgR.size() + QSize(filterMargin, filterMargin) * 2,
QImage::Format_ARGB32_Premultiplied); QImage::Format_ARGB32_Premultiplied);
thumbnail.fill(hoverColor); thumbnail.fill(themeColor(Theme::Token_Foreground_Muted));
QPainter thumbnailPainter(&thumbnail); QPainter thumbnailPainter(&thumbnail);
thumbnailPainter.translate(blurRadius, blurRadius); thumbnailPainter.translate(filterMargin, filterMargin);
thumbnailPainter.fillRect(thumbnailBgRect, backgroundSecondaryColor); thumbnailPainter.fillRect(thumbnailAreaR,
themeColor(Theme::Token_Background_Default));
thumbnailPainter.drawPixmap(thumbnailPos, pm); thumbnailPainter.drawPixmap(thumbnailPos, pm);
thumbnailPainter.setPen(foregroundPrimaryColor); thumbnailPainter.setPen(titleTF.color());
drawPixmapOverlay(item, &thumbnailPainter, option, thumbnailBgRect); drawPixmapOverlay(item, &thumbnailPainter, option, thumbnailAreaR);
thumbnailPainter.setOpacity(1.0 - hoverBlurOpacity);
thumbnailPainter.fillRect(thumbnail.rect(),
themeColor(Theme::Token_Foreground_Muted));
thumbnailPainter.end(); thumbnailPainter.end();
qt_blurImage(thumbnail, hoverBlurRadius, false, false);
m_blurredThumbnail = QPixmap(tileRect.size()); QImage mask(thumbnail.size(), QImage::Format_Grayscale8);
QPainter blurredThumbnailPainter(&m_blurredThumbnail); mask.fill(Qt::black);
blurredThumbnailPainter.translate(-blurRadius, -blurRadius); QPainter maskPainter(&mask);
qt_blurImage(&blurredThumbnailPainter, thumbnail, blurRadius, false, false); const QRect maskR = bgR.translated(filterMargin, filterMargin)
blurredThumbnailPainter.setOpacity(0.825); .adjusted(1, 1, -1, -1);
blurredThumbnailPainter.fillRect(tileRect, hoverColor); 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); const QPixmap thumbnailPortionPM = m_blurredThumbnail.copy(backgroundPortionR);
painter->drawPixmap(backgroundPortionRect.topLeft(), thumbnailPortionPM); painter->drawPixmap(backgroundPortionR.topLeft(), thumbnailPortionPM);
} else { } else {
painter->fillRect(backgroundPortionRect, hoverColor); painter->fillRect(thumbnailAreaR, themeColor(Theme::Token_Foreground_Muted));
} }
} }
// The description Text (unhovered or hovered) // The description Text (unhovered or hovered)
painter->setPen(textColor); painter->setPen(titleTF.color());
painter->setFont(sizedFont(13, option.widget)); // Title font painter->setFont(titleTF.font());
if (offset) { if (offset) {
// The title of the example // The title of the example
const QRectF nameRect = painter->boundingRect(shiftedTextRect, item->name, wrapped); const QRect shiftedTitleR = thumbnailAreaR.translated(backgroundPortionR.topLeft());
painter->drawText(nameRect, item->name, wrapped); 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. // The separator line below the example title.
const int ll = nameRect.height() + 3; const QRect hrR(titleR.x(), titleR.bottom() + 1 + ExPaddingGapS, thumbnailAreaR.width(), 1);
const QLine line = QLine(0, ll, textArea.width(), ll).translated(shiftedTextRect.topLeft()); painter->fillRect(hrR, themeColor(Theme::Token_Stroke_Muted));
painter->setPen(foregroundSecondaryColor);
painter->setOpacity(animationProgress); // "fade in" separator line and description
painter->drawLine(line);
// The description text. // The description text.
const int dd = ll + 5; const QRect descriptionR(hrR.x(), hrR.bottom() + 1 + ExPaddingGapS,
const QRect descRect = shiftedTextRect.adjusted(0, dd, 0, dd); thumbnailAreaR.width(), shiftY);
painter->setPen(textColor); painter->setPen(descriptionTF.color());
painter->setFont(descriptionFont); painter->setFont(descriptionTF.font());
painter->drawText(descRect, item->description, wrapped); painter->drawText(descriptionR, item->description, wrapTO);
painter->setOpacity(1); painter->setOpacity(1);
} else { } else {
// The title of the example // The title of the example
const QString elidedName = painter->fontMetrics() const QString elidedName = painter->fontMetrics()
.elidedText(item->name, Qt::ElideRight, textRect.width()); .elidedText(item->name, Qt::ElideRight, titleR.width());
painter->drawText(textRect, elidedName); 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 // The 'Tags:' section
painter->setPen(foregroundPrimaryColor); painter->setPen(tagsLabelTF.color());
const QFont tagsFont = sizedFont(10, option.widget); painter->setFont(tagsLabelTF.font());
painter->setFont(tagsFont); painter->drawText(tagsLabelR, tagsLabelTF.drawTextFlags, tagsLabelText);
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(themeColor(Theme::Welcome_LinkColor)); const QFontMetrics fm = painter->fontMetrics();
int emptyTagRowsLeft = 2;
painter->setPen(tagsTF.color());
painter->setFont(tagsTF.font());
int emptyTagRowsLeft = tagsRowsCount;
int xx = 0; int xx = 0;
int yy = 0; int yy = 0;
const bool populateTagsRects = m_currentTagRects.empty(); const bool populateTagsRects = m_currentTagRects.empty();
for (const QString &tag : item->tags) { for (const QString &tag : item->tags) {
const int ww = fm.horizontalAdvance(tag) + tagsHorSpacing; const int ww = fm.horizontalAdvance(tag);
if (xx + ww > textArea.width() - tagsLabelRect.width()) { if (xx + ww > tagsR.width()) {
if (--emptyTagRowsLeft == 0) if (--emptyTagRowsLeft == 0)
break; break;
yy += fm.lineSpacing(); yy += fm.lineSpacing();
xx = 0; xx = 0;
} }
const QRect tagRect = QRect(xx, yy, ww, tagsLabelRect.height()) const QRect tagRect = QRect(xx, yy, ww, tagsLabelR.height()).translated(tagsR.topLeft());
.translated(tagsLabelRect.topRight()); painter->drawText(tagRect, tagsTF.drawTextFlags, tag);
painter->drawText(tagRect, tag); if (populateTagsRects) {
if (populateTagsRects) constexpr int grow = tagsHGap / 2;
m_currentTagRects.append({ tag, tagRect }); const QRect tagMouseArea = tagRect.adjusted(-grow, -grow, grow, grow);
xx += ww; m_currentTagRects.append({ tag, tagMouseArea });
}
xx += ww + tagsHGap;
} }
painter->restore(); painter->restore();
@@ -642,7 +1041,7 @@ bool ListItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *model,
QSize ListItemDelegate::sizeHint(const QStyleOptionViewItem &, const QModelIndex &) const QSize ListItemDelegate::sizeHint(const QStyleOptionViewItem &, const QModelIndex &) const
{ {
return {GridItemWidth, GridItemHeight}; return itemSize();
} }
void ListItemDelegate::drawPixmapOverlay(const ListItem *, QPainter *, void ListItemDelegate::drawPixmapOverlay(const ListItem *, QPainter *,
@@ -682,6 +1081,7 @@ SectionedGridView::SectionedGridView(QWidget *parent)
auto sectionedView = new QWidget; auto sectionedView = new QWidget;
auto layout = new QVBoxLayout; auto layout = new QVBoxLayout;
layout->setSpacing(0);
layout->setContentsMargins(0, 0, 0, 0); layout->setContentsMargins(0, 0, 0, 0);
layout->addStretch(1); layout->addStretch(1);
sectionedView->setLayout(layout); sectionedView->setLayout(layout);
@@ -744,24 +1144,24 @@ void SectionedGridView::setSearchString(const QString &searchString)
filterModel->setSearchString(searchString); filterModel->setSearchString(searchString);
} }
static QWidget *createSeparator(QWidget *parent) static QLabel *createTitleLabel(const QString &text, QWidget *parent = nullptr)
{ {
QWidget *line = Layouting::createHr(parent); constexpr TextFormat headerTitleTF {Theme::Token_Text_Muted, StyleHelper::UiElementH4};
QSizePolicy linePolicy(QSizePolicy::Expanding, QSizePolicy::Ignored); auto link = new QLabel(text, parent);
linePolicy.setHorizontalStretch(2); link->setFont(headerTitleTF.font());
line->setSizePolicy(linePolicy); QPalette pal = link->palette();
QPalette pal = line->palette(); pal.setColor(QPalette::WindowText, headerTitleTF.color());
pal.setColor(QPalette::Dark, Qt::transparent); link->setPalette(pal);
pal.setColor(QPalette::Light, themeColor(Theme::Welcome_ForegroundSecondaryColor)); return link;
line->setPalette(pal);
return line;
} }
static QLabel *createLinkLabel(const QString &text, QWidget *parent) 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 + ";\">" auto link = new QLabel("<a href=\"link\" style=\"color: " + linkColor + ";\">"
+ text + "</a>", parent); + text + "</a>", parent);
link->setFont(headerLinkTF.font());
return link; return link;
} }
@@ -797,14 +1197,13 @@ ListModel *SectionedGridView::addSection(const Section &section, const QList<Lis
connect(seeAllLink, &QLabel::linkActivated, this, [this, section] { zoomInSection(section); }); connect(seeAllLink, &QLabel::linkActivated, this, [this, section] { zoomInSection(section); });
using namespace Layouting; using namespace Layouting;
QWidget *sectionLabel = Row { QWidget *sectionLabel = Row {
section.name, createTitleLabel(section.name),
createSeparator(this), st,
seeAllLink, seeAllLink,
Space(HSpacing), Space(ExVPaddingGapXl),
noMargin customMargin({0, ExPaddingGapL, 0, VPaddingL}),
}.emerge(); }.emerge();
m_sectionLabels.append(sectionLabel); m_sectionLabels.append(sectionLabel);
sectionLabel->setContentsMargins(0, ItemGap, 0, 0);
auto scrollArea = qobject_cast<QScrollArea *>(widget(0)); auto scrollArea = qobject_cast<QScrollArea *>(widget(0));
auto vbox = qobject_cast<QVBoxLayout *>(scrollArea->widget()->layout()); auto vbox = qobject_cast<QVBoxLayout *>(scrollArea->widget()->layout());
@@ -858,6 +1257,7 @@ void SectionedGridView::zoomInSection(const Section &section)
auto zoomedInWidget = new QWidget(this); auto zoomedInWidget = new QWidget(this);
auto layout = new QVBoxLayout; auto layout = new QVBoxLayout;
layout->setContentsMargins(0, 0, 0, 0); layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(0);
zoomedInWidget->setLayout(layout); zoomedInWidget->setLayout(layout);
QLabel *backLink = createLinkLabel("&lt; " + Tr::tr("Back"), this); QLabel *backLink = createLinkLabel("&lt; " + Tr::tr("Back"), this);
@@ -868,13 +1268,12 @@ void SectionedGridView::zoomInSection(const Section &section)
}); });
using namespace Layouting; using namespace Layouting;
QWidget *sectionLabel = Row { QWidget *sectionLabel = Row {
section.name, createTitleLabel(section.name),
createSeparator(this), st,
backLink, backLink,
Space(HSpacing), Space(ExVPaddingGapXl),
noMargin customMargin({0, ExPaddingGapL, 0, VPaddingL}),
}.emerge(); }.emerge();
sectionLabel->setContentsMargins(0, ItemGap, 0, 0);
auto gridView = new GridView(zoomedInWidget); auto gridView = new GridView(zoomedInWidget);
gridView->setItemDelegate(m_itemDelegate); gridView->setItemDelegate(m_itemDelegate);

View File

@@ -6,10 +6,17 @@
#include "core_global.h" #include "core_global.h"
#include "iwelcomepage.h" #include "iwelcomepage.h"
#include <utils/fancylineedit.h>
#include <utils/stylehelper.h>
#include <utils/theme/theme.h>
#include <QComboBox>
#include <QElapsedTimer> #include <QElapsedTimer>
#include <QLabel>
#include <QListView> #include <QListView>
#include <QPen> #include <QPen>
#include <QPointer> #include <QPointer>
#include <QPushButton>
#include <QSortFilterProxyModel> #include <QSortFilterProxyModel>
#include <QStackedWidget> #include <QStackedWidget>
#include <QStyledItemDelegate> #include <QStyledItemDelegate>
@@ -24,31 +31,111 @@ namespace Core {
namespace WelcomePageHelpers { namespace WelcomePageHelpers {
constexpr int HSpacing = 20; constexpr QSize WelcomeThumbnailSize(214, 160);
constexpr int ItemGap = 4;
constexpr int GridItemGap = 3 * ItemGap; class CORE_EXPORT TextFormat {
constexpr int GridItemWidth = 240 + GridItemGap; // Extra GridItemGap as "spacing" public:
constexpr int GridItemHeight = GridItemWidth; QColor color() const
constexpr QSize GridItemImageSize(GridItemWidth - GridItemGap {
- 2 * (GridItemGap + 1), // Horizontal margins + 1 pixel return Utils::creatorTheme()->color(themeColor);
GridItemHeight - GridItemGap }
- GridItemGap - 1 // Upper margin + 1 pixel
- 67); // Bottom margin (for title + tags)
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, CORE_EXPORT void drawCardBackground(QPainter *painter, const QRectF &rect,
const QBrush &fill, const QPen &pen = QPen(Qt::NoPen), 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 } // 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: public:
explicit SearchBox(QWidget *parent = nullptr); 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 class CORE_EXPORT GridView : public QListView
@@ -146,7 +233,9 @@ class CORE_EXPORT ListItemDelegate : public QStyledItemDelegate
{ {
Q_OBJECT Q_OBJECT
public: public:
ListItemDelegate(); ListItemDelegate() = default;
static QSize itemSize();
void paint(QPainter *painter, const QStyleOptionViewItem &option, void paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const override; const QModelIndex &index) const override;
@@ -165,13 +254,6 @@ protected:
void goon(); void goon();
const QColor backgroundPrimaryColor;
const QColor backgroundSecondaryColor;
const QColor foregroundPrimaryColor;
const QColor foregroundSecondaryColor;
const QColor hoverColor;
const QColor textColor;
private: private:
mutable QPersistentModelIndex m_previousIndex; mutable QPersistentModelIndex m_previousIndex;
mutable QElapsedTimer m_startTime; mutable QElapsedTimer m_startTime;

View File

@@ -242,27 +242,6 @@ void CppHighlighter::highlightBlock(const QString &text)
TextDocumentLayout::setFoldingIndent(currentBlock(), foldingIndent); 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()); setCurrentBlockState((braceDepth << 8) | tokenize.state());
TextDocumentLayout::setExpectedRawStringSuffix(currentBlock(), TextDocumentLayout::setExpectedRawStringSuffix(currentBlock(),
tokenize.expectedRawStringSuffix()); tokenize.expectedRawStringSuffix());

View File

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

View File

@@ -396,6 +396,12 @@ F2TestCase::F2TestCase(CppEditorAction action,
BaseTextEditor *currentTextEditor = dynamic_cast<BaseTextEditor*>(currentEditor); BaseTextEditor *currentTextEditor = dynamic_cast<BaseTextEditor*>(currentEditor);
QVERIFY(currentTextEditor); 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()); QCOMPARE(currentTextEditor->document()->filePath(), targetTestFile->filePath());
int expectedLine, expectedColumn; int expectedLine, expectedColumn;
if (useClangd && expectedVirtualFunctionProposal.size() == 1) { if (useClangd && expectedVirtualFunctionProposal.size() == 1) {
@@ -494,8 +500,11 @@ void FollowSymbolTest::initTestCase()
return; return;
// Find suitable kit. // 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) { 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) if (!F2TestCase::m_testKit)
QSKIP("This test requires at least one kit to be present"); QSKIP("This test requires at least one kit to be present");

View File

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

View File

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

View File

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

View File

@@ -189,7 +189,6 @@ void GlslHighlighter::highlightBlock(const QString &text)
TextDocumentLayout::setParentheses(currentBlock(), parentheses); TextDocumentLayout::setParentheses(currentBlock(), parentheses);
// if the block is ifdefed out, we only store the parentheses, but // if the block is ifdefed out, we only store the parentheses, but
// do not adjust the brace depth. // do not adjust the brace depth.
if (TextDocumentLayout::ifdefedOut(currentBlock())) { if (TextDocumentLayout::ifdefedOut(currentBlock())) {
braceDepth = initialBraceDepth; braceDepth = initialBraceDepth;
@@ -198,23 +197,6 @@ void GlslHighlighter::highlightBlock(const QString &text)
TextDocumentLayout::setFoldingIndent(currentBlock(), foldingIndent); 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()); setCurrentBlockState((braceDepth << 8) | lex.state());
} }

View File

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

View File

@@ -9,9 +9,10 @@
#include <coreplugin/welcomepagehelper.h> #include <coreplugin/welcomepagehelper.h>
#include <utils/fancylineedit.h> #include <utils/fancylineedit.h>
#include <utils/layoutbuilder.h>
#include <utils/progressindicator.h> #include <utils/progressindicator.h>
#include <utils/theme/theme.h>
#include <utils/qtcassert.h> #include <utils/qtcassert.h>
#include <utils/theme/theme.h>
#include <QDesktopServices> #include <QDesktopServices>
#include <QLabel> #include <QLabel>
@@ -44,38 +45,34 @@ class QtMarketplacePageWidget : public QWidget
public: public:
QtMarketplacePageWidget() QtMarketplacePageWidget()
{ {
auto searchBox = new Core::SearchBox(this); m_searcher = new Core::SearchBox(this);
m_searcher = searchBox->m_lineEdit;
m_searcher->setPlaceholderText(Tr::tr("Search in Marketplace...")); 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 = new QLabel(this);
m_errorLabel->setVisible(false); 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); m_sectionedProducts = new SectionedProducts(this);
auto progressIndicator = new Utils::ProgressIndicator(ProgressIndicatorSize::Large, this); auto progressIndicator = new Utils::ProgressIndicator(ProgressIndicatorSize::Large, this);
progressIndicator->attachToWidget(m_sectionedProducts); progressIndicator->attachToWidget(m_sectionedProducts);
progressIndicator->hide(); 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, connect(m_sectionedProducts, &SectionedProducts::toggleProgressIndicator,
progressIndicator, &Utils::ProgressIndicator::setVisible); progressIndicator, &Utils::ProgressIndicator::setVisible);
connect(m_sectionedProducts, &SectionedProducts::errorOccurred, this, connect(m_sectionedProducts, &SectionedProducts::errorOccurred, this,
[this, progressIndicator, searchBox](int, const QString &message) { [this, progressIndicator](int, const QString &message) {
progressIndicator->hide(); progressIndicator->hide();
progressIndicator->deleteLater(); progressIndicator->deleteLater();
m_errorLabel->setAlignment(Qt::AlignHCenter); m_errorLabel->setAlignment(Qt::AlignHCenter);
@@ -89,7 +86,7 @@ public:
"</p><br/><p><small><i>Error: %1</i></small></p>").arg(message); "</p><br/><p><small><i>Error: %1</i></small></p>").arg(message);
m_errorLabel->setText(txt); m_errorLabel->setText(txt);
m_errorLabel->setVisible(true); m_errorLabel->setVisible(true);
searchBox->setVisible(false); m_searcher->setVisible(false);
connect(m_errorLabel, &QLabel::linkActivated, connect(m_errorLabel, &QLabel::linkActivated,
this, []() { QDesktopServices::openUrl(QUrl("https://marketplace.qt.io")); }); this, []() { QDesktopServices::openUrl(QUrl("https://marketplace.qt.io")); });
}); });

View File

@@ -21,6 +21,7 @@
#include <utils/algorithm.h> #include <utils/algorithm.h>
#include <utils/fileutils.h> #include <utils/fileutils.h>
#include <utils/icon.h> #include <utils/icon.h>
#include <utils/layoutbuilder.h>
#include <utils/qtcassert.h> #include <utils/qtcassert.h>
#include <utils/stringutils.h> #include <utils/stringutils.h>
#include <utils/stylehelper.h> #include <utils/stylehelper.h>
@@ -28,9 +29,6 @@
#include <QAbstractItemDelegate> #include <QAbstractItemDelegate>
#include <QAction> #include <QAction>
#include <QBoxLayout>
#include <QDir>
#include <QFileInfo>
#include <QHeaderView> #include <QHeaderView>
#include <QHelpEvent> #include <QHelpEvent>
#include <QLabel> #include <QLabel>
@@ -42,16 +40,52 @@
using namespace Core; using namespace Core;
using namespace Core::WelcomePageHelpers; using namespace Core::WelcomePageHelpers;
using namespace Utils; 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"; const char PROJECT_BASE_ID[] = "Welcome.OpenRecentProject";
namespace ProjectExplorer { namespace ProjectExplorer {
namespace Internal { 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) ProjectModel::ProjectModel(QObject *parent)
: QAbstractListModel(parent) : QAbstractListModel(parent)
{ {
@@ -61,7 +95,7 @@ ProjectModel::ProjectModel(QObject *parent)
int ProjectModel::rowCount(const QModelIndex &) const int ProjectModel::rowCount(const QModelIndex &) const
{ {
return m_projects.count(); return int(m_projects.count());
} }
QVariant ProjectModel::data(const QModelIndex &index, int role) const QVariant ProjectModel::data(const QModelIndex &index, int role) const
@@ -190,21 +224,24 @@ static QColor themeColor(Theme::Color role)
return Utils::creatorTheme()->color(role); return Utils::creatorTheme()->color(role);
} }
static QFont sizedFont(int size, const QWidget *widget, static QPixmap pixmap(const QString &id, const Theme::Color color)
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)
{ {
const QString fileName = QString(":/welcome/images/%1.png").arg(id); const QString fileName = QString(":/welcome/images/%1.png").arg(id);
return Icon({{FilePath::fromString(fileName), color}}, Icon::Tint).pixmap(); 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 class BaseDelegate : public QAbstractItemDelegate
{ {
protected: protected:
@@ -244,6 +281,11 @@ protected:
class SessionDelegate : public BaseDelegate class SessionDelegate : public BaseDelegate
{ {
protected: protected:
bool expanded(const QModelIndex &idx) const
{
return m_expandedSessions.contains(idx.data(Qt::DisplayRole).toString());
}
QString entryType() override QString entryType() override
{ {
return Tr::tr("session", "Appears in \"Open session <name>\""); 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 // in expanded state bottom contains 'Clone', 'Rename', etc links, where the tool tip
// would be confusing // would be confusing
const bool expanded = m_expandedSessions.contains(idx.data(Qt::DisplayRole).toString()); return expanded(idx) ? itemRect.adjusted(0, 0, 0, -actionButtonHeight()) : itemRect;
return expanded ? itemRect.adjusted(0, 0, 0, -LINK_HEIGHT) : 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: 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); // visible on withIcon() Gap + arrow visible on hover Extra margin right of project item
// | | |
const QRect rc = option.rect; // +----------+----------+ +--------+-------+ +----------+----------+
const QString sessionName = idx.data(Qt::DisplayRole).toString(); // | | | | | |
//
// +------------+--------+--------+------------+--------+-------------+--------+-------+------------+---------------------+ --+
// | | | |(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 QPoint mousePos = option.widget->mapFromGlobal(QCursor::pos());
//const bool hovered = option.state & QStyle::State_MouseOver;
const bool hovered = option.rect.contains(mousePos); const bool hovered = option.rect.contains(mousePos);
const bool expanded = m_expandedSessions.contains(sessionName); const bool expanded = this->expanded(idx);
painter->fillRect(rc, themeColor(Theme::Welcome_BackgroundSecondaryColor));
painter->fillRect(rc.adjusted(0, 0, 0, -ItemGap),
hovered ? hoverColor : backgroundPrimaryColor);
const int x = rc.x(); const QRect bgR = option.rect.adjusted(0, 0, -sessionScrollBarGap, -itemSpacing());
const int x1 = x + TEXT_OFFSET_HORIZONTAL; const QRect hdR(bgR.topLeft(), QSize(bgR.width(), expanded ? headerHeight()
const int y = rc.y(); : bgR.height()));
const int firstBase = y + 18;
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 QString sessionName = idx.data(Qt::DisplayRole).toString();
const QRect arrowRect = rc.adjusted(rc.width() - SESSION_ARROW_RECT_WIDTH, 0, 0, 0);
const bool arrowRectHovered = arrowRect.contains(mousePos); const int x = bgR.x();
painter->fillRect(arrowRect.adjusted(0, 0, 0, -ItemGap), const int y = bgR.y();
arrowRectHovered ? hoverColor : backgroundPrimaryColor);
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) { if (idx.row() < 9) {
painter->setPen(foregroundSecondaryColor); painter->setPen(shortcutNumberTF.color());
painter->setFont(sizedFont(10, option.widget)); painter->setFont(shortcutNumberTF.font());
painter->drawText(x + 3, firstBase, QString::number(idx.row() + 1)); 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 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, sessionNameWidth);
painter->drawText(sessionNameR, sessionNameTF.drawTextFlags,
fullSessionNameElided);
if (switchActive)
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);
} }
const bool isLastSession = idx.data(SessionModel::LastSessionRole).toBool(); int yy = hdR.bottom();
const bool isActiveSession = idx.data(SessionModel::ActiveSessionRole).toBool();
const bool isDefaultVirgin = SessionManager::isDefaultVirgin();
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 QString fullSessionNameElided = painter->fontMetrics().elidedText(
fullSessionName, Qt::ElideRight, sessionNameTextSpace);
painter->drawText(x1, firstBase, fullSessionNameElided);
if (switchActive)
m_activeSwitchToRect = switchRect;
if (expanded) { if (expanded) {
painter->setPen(textColor); const QFont projectNameFont = sessionProjetNameTF.font();
painter->setFont(sizedFont(12, option.widget)); 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;
const FilePaths projects = ProjectManager::projectsForSessionName(sessionName); const FilePaths projects = ProjectManager::projectsForSessionName(sessionName);
int yy = firstBase + SESSION_LINE_HEIGHT - 3;
QFontMetrics fm(option.widget->font());
for (const FilePath &projectPath : projects) { for (const FilePath &projectPath : projects) {
// Project name. yy += s(VPaddingXs);
QString completeBase = projectPath.completeBaseName(); {
painter->setPen(textColor); painter->setFont(projectNameFont);
painter->drawText(x1, yy, fm.elidedText(completeBase, Qt::ElideMiddle, textSpace)); painter->setPen(sessionProjetNameTF.color());
yy += 18; const QRect projectNameR(textX, yy, textWidth, projectNameLineHeight);
const QString projectNameElided =
// Project path. projectNameFm.elidedText(projectPath.completeBaseName(), Qt::ElideMiddle,
const QString displayPath = textWidth);
projectPath.osType() == OsTypeWindows ? projectPath.displayName() painter->drawText(projectNameR, sessionProjetNameTF.drawTextFlags,
: projectPath.withTildeHomePath(); projectNameElided);
painter->setPen(foregroundPrimaryColor); yy += projectNameLineHeight;
painter->drawText(x1, yy, fm.elidedText(displayPath, Qt::ElideMiddle, textSpace)); yy += s(ExPaddingGapS);
yy += 22; }
{
const QString displayPath =
projectPath.osType() == OsTypeWindows ? projectPath.displayName()
: projectPath.withTildeHomePath();
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 = { const QStringList actions = {
Tr::tr("Clone"), Tr::tr("Clone"),
Tr::tr("Rename"), 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 QString &action = actions.at(i);
const int ww = fm.horizontalAdvance(action); const int ww = textWidths.at(i);
const int spacing = 7; // Between action link and separator line const QRect actionR(xx, yy, s(ExPaddingGapM) + ww + s(ExPaddingGapM), buttonHeight);
const QRect actionRect = const bool isDisabled = i > 0 && SessionManager::isDefaultSession(sessionName);
QRect(xx, yy - 10, ww, 15).adjusted(-spacing, -spacing, spacing, spacing); const bool isActive = actionR.adjusted(-s(VPaddingXs), 0, s(VPaddingXs) + 1, 0)
const bool isForcedDisabled = (i != 0 && sessionName == "default"); .contains(mousePos) && !isDisabled;
const bool isActive = actionRect.contains(mousePos) && !isForcedDisabled; if (isActive) {
painter->setPen(isForcedDisabled ? disabledLinkColor : linkColor); WelcomePageHelpers::drawCardBackground(painter, actionR, Qt::transparent,
painter->setFont(sizedFont(12, option.widget, isActive)); themeColor(Theme::Token_Text_Muted));
painter->drawText(xx, yy, action); m_activeActionRects[i] = actionR;
if (i < 2) {
xx += ww + 2 * spacing;
int pp = xx - spacing;
painter->setPen(textColor);
painter->drawLine(pp, yy - 10, pp, yy);
} }
if (isActive) painter->setFont(actionFont);
m_activeActionRects[i] = actionRect; 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 QSize sizeHint(const QStyleOptionViewItem &, const QModelIndex &idx) const final
{ {
int h = SESSION_LINE_HEIGHT; int h = headerHeight();
QString sessionName = idx.data(Qt::DisplayRole).toString(); if (expanded(idx)) {
if (m_expandedSessions.contains(sessionName)) { const QString sessionName = idx.data(Qt::DisplayRole).toString();
const FilePaths projects = ProjectManager::projectsForSessionName(sessionName); 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, bool editorEvent(QEvent *ev, QAbstractItemModel *model,
const QStyleOptionViewItem &option, const QModelIndex &idx) final const QStyleOptionViewItem &, const QModelIndex &idx) final
{ {
if (ev->type() == QEvent::MouseButtonRelease) { if (ev->type() == QEvent::MouseButtonRelease) {
const QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(ev); const QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(ev);
const Qt::MouseButtons button = mouseEvent->button(); const Qt::MouseButtons button = mouseEvent->button();
const QPoint pos = static_cast<QMouseEvent *>(ev)->pos(); 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(); 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". // The expand/collapse "button".
if (m_expandedSessions.contains(sessionName)) if (m_expandedSessions.contains(sessionName))
m_expandedSessions.removeOne(sessionName); m_expandedSessions.removeOne(sessionName);
@@ -421,23 +600,15 @@ public:
} }
if (ev->type() == QEvent::MouseMove) { if (ev->type() == QEvent::MouseMove) {
emit model->layoutChanged({QPersistentModelIndex(idx)}); // Somewhat brutish. emit model->layoutChanged({QPersistentModelIndex(idx)}); // Somewhat brutish.
//update(option.rect);
return false; return false;
} }
return false; return false;
} }
private: 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; QStringList m_expandedSessions;
mutable QRect m_activeExpandRect;
mutable QRect m_activeSwitchToRect; mutable QRect m_activeSwitchToRect;
mutable QRect m_activeActionRects[3]; mutable QRect m_activeActionRects[3];
}; };
@@ -453,58 +624,89 @@ class ProjectDelegate : public BaseDelegate
public: 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
{ {
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 bool hovered = option.widget->isActiveWindow()
const QRect bgRect = rc.adjusted(0, 0, -ItemGap, -ItemGap); && option.state & QStyle::State_MouseOver;
painter->fillRect(rc, themeColor(Theme::Welcome_BackgroundSecondaryColor));
painter->fillRect(bgRect, themeColor(hovered ? Theme::Welcome_HoverColor
: Theme::Welcome_BackgroundPrimaryColor));
const int x = rc.x(); const QRect bgR = option.rect.adjusted(0, 0, -s(HPaddingXs), -itemSpacing());
const int y = rc.y();
const int firstBase = y + 18;
const int secondBase = firstBase + 19;
static const QPixmap projectIcon = static const QPixmap icon = pixmap("project", Theme::Token_Text_Muted);
pixmap("project", Theme::Welcome_ForegroundSecondaryColor); const QSize iconS = icon.deviceIndependentSize().toSize();
painter->drawPixmap(x + 11, y + 6, projectIcon);
QString projectName = idx.data(Qt::DisplayRole).toString(); const int x = bgR.x();
FilePath projectPath = FilePath::fromVariant(idx.data(ProjectModel::FilePathRole)); 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)); const int y = bgR.y();
painter->setFont(sizedFont(10, option.widget)); 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) QTC_CHECK(option.rect.bottom() == projectPathR.bottom() + s(VPaddingXs) + itemSpacing());
painter->drawText(x + 3, firstBase, QString::number(idx.row() + 1));
const int textSpace = rc.width() - TEXT_OFFSET_HORIZONTAL - ItemGap - 6; {
drawBackgroundRect(painter, bgR, hovered);
painter->setPen(themeColor(Theme::Welcome_LinkColor)); }
painter->setFont(sizedFont(13, option.widget, hovered)); if (idx.row() < 9) {
const QString projectNameElided = painter->setPen(shortcutNumberTF.color());
painter->fontMetrics().elidedText(projectName, Qt::ElideRight, textSpace); painter->setFont(shortcutNumberTF.font());
painter->drawText(x + TEXT_OFFSET_HORIZONTAL, firstBase, projectNameElided); const QRect numberR(numberX, y, shortcutNumberWidth, bgR.height());
const QString numberString = QString::number(idx.row() + 1);
painter->setPen(themeColor(Theme::Welcome_ForegroundPrimaryColor)); painter->drawText(numberR, shortcutNumberTF.drawTextFlags, numberString);
painter->setFont(sizedFont(13, option.widget)); }
const QString displayPath = if (withIcon()) {
projectPath.osType() == OsTypeWindows ? projectPath.displayName() painter->drawPixmap(iconX, iconY, icon);
: projectPath.withTildeHomePath(); }
const QString displayPathElided = {
painter->fontMetrics().elidedText(displayPath, Qt::ElideMiddle, textSpace); painter->setPen(projectNameTF.color());
painter->drawText(x + TEXT_OFFSET_HORIZONTAL, secondBase, displayPathElided); painter->setFont(projectNameTF.font(hovered));
const QString projectName = idx.data(Qt::DisplayRole).toString();
const QString projectNameElided =
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, 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(); return QSize(-1, itemHeight() + itemSpacing());
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);
} }
bool editorEvent(QEvent *ev, QAbstractItemModel *model, bool editorEvent(QEvent *ev, QAbstractItemModel *model,
@@ -541,6 +743,18 @@ public:
} }
return false; 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 class TreeView : public QTreeView
@@ -559,10 +773,7 @@ public:
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
setFocusPolicy(Qt::NoFocus); setFocusPolicy(Qt::NoFocus);
setBackgroundColor(viewport(), Theme::Token_Background_Default);
QPalette pal;
pal.setColor(QPalette::Base, themeColor(Theme::Welcome_BackgroundSecondaryColor));
viewport()->setPalette(pal);
} }
}; };
@@ -577,54 +788,63 @@ public:
if (!projectWelcomePage->m_projectModel) if (!projectWelcomePage->m_projectModel)
projectWelcomePage->m_projectModel = new ProjectModel(this); projectWelcomePage->m_projectModel = new ProjectModel(this);
auto manageSessionsButton = new WelcomePageButton(this); using namespace Layouting;
manageSessionsButton->setText(Tr::tr("Manage..."));
manageSessionsButton->setWithAccentColor(true);
manageSessionsButton->setOnClicked([] { SessionManager::showSessionManager(); });
auto sessionsLabel = new QLabel(this); auto sessions = new QWidget;
sessionsLabel->setText(Tr::tr("Sessions")); {
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 recentProjectsLabel = new QLabel(this); auto projects = new QWidget;
recentProjectsLabel->setText(Tr::tr("Projects")); {
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 sessionsList = new TreeView(this, "Sessions"); Row {
sessionsList->setModel(projectWelcomePage->m_sessionModel); sessions,
sessionsList->header()->setSectionHidden(1, true); // The "last modified" column. projects,
sessionsList->setItemDelegate(&m_sessionDelegate); spacing(0),
sessionsList->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); noMargin(),
}.attachTo(this);
auto projectsList = new TreeView(this, "Recent Projects");
projectsList->setUniformRowHeights(true);
projectsList->setModel(projectWelcomePage->m_projectModel);
projectsList->setItemDelegate(&m_projectDelegate);
projectsList->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
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);
} }
SessionDelegate m_sessionDelegate; SessionDelegate m_sessionDelegate;

View File

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

View File

@@ -35,7 +35,7 @@ Tasks PythonProject::projectIssues(const Kit *k) const
return {}; return {};
return { return {
BuildSystemTask{Task::Error, 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) 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*(\\+=|=|-=|~=)(.*)$")); static const QRegularExpression regExp(QLatin1String("^([^\\s\\+-]*)\\s*(\\+=|=|-=|~=)(.*)$"));
bool after = false; bool after = false;
bool ignoreNext = false; bool ignoreNext = false;
bool nextIsQtConfArg = false;
m_unparsedArguments = args; m_unparsedArguments = args;
ProcessArgs::ArgIterator ait(&m_unparsedArguments); ProcessArgs::ArgIterator ait(&m_unparsedArguments);
while (ait.next()) { while (ait.next()) {
@@ -63,11 +64,18 @@ void MakeFileParse::parseArgs(const QString &args, const QString &project,
// Ignoring // Ignoring
ignoreNext = false; ignoreNext = false;
ait.deleteArg(); ait.deleteArg();
} else if (nextIsQtConfArg) {
nextIsQtConfArg = false;
m_qtConfFile = FilePath::fromUserInput(ait.value());
ait.deleteArg();
} else if (ait.value() == project) { } else if (ait.value() == project) {
ait.deleteArg(); ait.deleteArg();
} else if (ait.value() == QLatin1String("-after")) { } else if (ait.value() == QLatin1String("-after")) {
after = true; after = true;
ait.deleteArg(); ait.deleteArg();
} else if (ait.value() == "-qtconf") {
nextIsQtConfArg = true;
ait.deleteArg();
} else if (ait.value().contains(QLatin1Char('='))) { } else if (ait.value().contains(QLatin1Char('='))) {
const QRegularExpressionMatch match = regExp.match(ait.value()); const QRegularExpressionMatch match = regExp.match(ait.value());
if (match.hasMatch()) { if (match.hasMatch()) {

View File

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

View File

@@ -490,7 +490,9 @@ QmakeBuildConfiguration::MakefileState QmakeBuildConfiguration::compareToImportF
return MakefileIncompatible; 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() qCDebug(logs) << "**Different Qt versions, buildconfiguration:" << version->qmakeFilePath()
<< " Makefile:" << parse.qmakePath(); << " Makefile:" << parse.qmakePath();
return MakefileForWrongProject; return MakefileForWrongProject;

View File

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

View File

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

View File

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

View File

@@ -98,7 +98,8 @@ GenericLinuxDeviceConfigurationWidget::GenericLinuxDeviceConfigurationWidget(
const int dmCount = dm->deviceCount(); const int dmCount = dm->deviceCount();
for (int i = 0; i < dmCount; ++i) { for (int i = 0; i < dmCount; ++i) {
IDevice::ConstPtr dev = dm->deviceAt(i); IDevice::ConstPtr dev = dm->deviceAt(i);
m_linkDeviceComboBox->addItem(dev->displayName(), dev->id().toSetting()); if (dev->id() != device->id())
m_linkDeviceComboBox->addItem(dev->displayName(), dev->id().toSetting());
} }
auto sshPortLabel = new QLabel(Tr::tr("&SSH port:")); auto sshPortLabel = new QLabel(Tr::tr("&SSH port:"));
@@ -307,13 +308,18 @@ void GenericLinuxDeviceConfigurationWidget::initGui()
Id linkDeviceId = Id::fromSetting(device()->extraData(Constants::LinkDevice)); Id linkDeviceId = Id::fromSetting(device()->extraData(Constants::LinkDevice));
auto dm = DeviceManager::instance(); auto dm = DeviceManager::instance();
int found = -1; int found = -1;
int minus = 0;
for (int i = 0, n = dm->deviceCount(); i < n; ++i) { 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; found = i;
break; 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_hostLineEdit->setText(sshParams.host());
m_sshPortSpinBox->setValue(sshParams.port()); m_sshPortSpinBox->setValue(sshParams.port());

View File

@@ -356,13 +356,14 @@ QFont FontSettings::font() const
{ {
QFont f(family(), fontSize()); QFont f(family(), fontSize());
f.setStyleStrategy(m_antialias ? QFont::PreferAntialias : QFont::NoAntialias); f.setStyleStrategy(m_antialias ? QFont::PreferAntialias : QFont::NoAntialias);
f.setWeight(fontNormalWeight());
return f; 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 // 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) if (Utils::HostOsInfo::isMacHost() && m_family == g_sourceCodePro)
weight = QFont::Medium; weight = QFont::Medium;
return weight; return weight;

View File

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

View File

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

View File

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

View File

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

View File

@@ -3492,13 +3492,36 @@ QByteArray TextEditorWidget::saveState() const
return state; 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) void TextEditorWidget::restoreState(const QByteArray &state)
{ {
const auto callFoldLicenseHeader = [this] {
auto callFold = [this] {
if (d->m_displaySettings.m_autoFoldFirstComment)
d->foldLicenseHeader();
};
if (!singleShotAfterHighlightingDone(callFold))
callFold();
};
if (state.isEmpty()) { if (state.isEmpty()) {
if (d->m_displaySettings.m_autoFoldFirstComment) callFoldLicenseHeader();
d->foldLicenseHeader();
return; return;
} }
int version; int version;
int vval; int vval;
int hval; int hval;
@@ -3514,24 +3537,27 @@ void TextEditorWidget::restoreState(const QByteArray &state)
if (version >= 1) { if (version >= 1) {
QList<int> collapsedBlocks; QList<int> collapsedBlocks;
stream >> collapsedBlocks; stream >> collapsedBlocks;
QTextDocument *doc = document(); auto foldingRestore = [this, collapsedBlocks] {
bool layoutChanged = false; QTextDocument *doc = document();
for (const int blockNumber : std::as_const(collapsedBlocks)) { bool layoutChanged = false;
QTextBlock block = doc->findBlockByNumber(qMax(0, blockNumber)); for (const int blockNumber : std::as_const(collapsedBlocks)) {
if (block.isValid()) { QTextBlock block = doc->findBlockByNumber(qMax(0, blockNumber));
TextDocumentLayout::doFoldOrUnfold(block, false); if (block.isValid()) {
layoutChanged = true; TextDocumentLayout::doFoldOrUnfold(block, false);
layoutChanged = true;
}
} }
} if (layoutChanged) {
if (layoutChanged) { auto documentLayout = qobject_cast<TextDocumentLayout *>(doc->documentLayout());
auto documentLayout = qobject_cast<TextDocumentLayout*>(doc->documentLayout()); QTC_ASSERT(documentLayout, return);
QTC_ASSERT(documentLayout, return ); documentLayout->requestUpdate();
documentLayout->requestUpdate(); documentLayout->emitDocumentSizeChanged();
documentLayout->emitDocumentSizeChanged(); }
} };
if (!singleShotAfterHighlightingDone(foldingRestore))
foldingRestore();
} else { } else {
if (d->m_displaySettings.m_autoFoldFirstComment) callFoldLicenseHeader();
d->foldLicenseHeader();
} }
d->m_lastCursorChangeWasInteresting = false; // avoid adding last position to history d->m_lastCursorChangeWasInteresting = false; // avoid adding last position to history
@@ -6642,6 +6668,9 @@ void TextEditorWidget::ensureCursorVisible()
void TextEditorWidget::ensureBlockIsUnfolded(QTextBlock block) void TextEditorWidget::ensureBlockIsUnfolded(QTextBlock block)
{ {
if (singleShotAfterHighlightingDone([this, block] { ensureBlockIsUnfolded(block); }))
return;
if (!block.isVisible()) { if (!block.isVisible()) {
auto documentLayout = qobject_cast<TextDocumentLayout*>(document()->documentLayout()); auto documentLayout = qobject_cast<TextDocumentLayout*>(document()->documentLayout());
QTC_ASSERT(documentLayout, return); QTC_ASSERT(documentLayout, return);
@@ -8215,6 +8244,9 @@ void TextEditorWidget::foldCurrentBlock()
void TextEditorWidget::fold(const QTextBlock &block) void TextEditorWidget::fold(const QTextBlock &block)
{ {
if (singleShotAfterHighlightingDone([this, block] { fold(block); }))
return;
QTextDocument *doc = document(); QTextDocument *doc = document();
auto documentLayout = qobject_cast<TextDocumentLayout*>(doc->documentLayout()); auto documentLayout = qobject_cast<TextDocumentLayout*>(doc->documentLayout());
QTC_ASSERT(documentLayout, return); QTC_ASSERT(documentLayout, return);
@@ -8235,6 +8267,9 @@ void TextEditorWidget::fold(const QTextBlock &block)
void TextEditorWidget::unfold(const QTextBlock &block) void TextEditorWidget::unfold(const QTextBlock &block)
{ {
if (singleShotAfterHighlightingDone([this, block] { unfold(block); }))
return;
QTextDocument *doc = document(); QTextDocument *doc = document();
auto documentLayout = qobject_cast<TextDocumentLayout*>(doc->documentLayout()); auto documentLayout = qobject_cast<TextDocumentLayout*>(doc->documentLayout());
QTC_ASSERT(documentLayout, return); QTC_ASSERT(documentLayout, return);
@@ -8254,6 +8289,9 @@ void TextEditorWidget::unfoldCurrentBlock()
void TextEditorWidget::unfoldAll() void TextEditorWidget::unfoldAll()
{ {
if (singleShotAfterHighlightingDone([this] { unfoldAll(); }))
return;
QTextDocument *doc = document(); QTextDocument *doc = document();
auto documentLayout = qobject_cast<TextDocumentLayout*>(doc->documentLayout()); auto documentLayout = qobject_cast<TextDocumentLayout*>(doc->documentLayout());
QTC_ASSERT(documentLayout, return); QTC_ASSERT(documentLayout, return);

View File

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