Merge remote-tracking branch 'origin/13.0'
Conflicts: src/plugins/android/androidsettingswidget.cpp Change-Id: Ifcb16aa16c7bc2792de25d0ee7a22cf0e39a05f8
11
dist/changelog/changes-13.0.0.md
vendored
@@ -39,6 +39,8 @@ General
|
||||
for searching in `Files in File System`
|
||||
* Added `Copy to Clipboard` to the `About Qt Creator` dialog
|
||||
([QTCREATORBUG-29886](https://bugreports.qt.io/browse/QTCREATORBUG-29886))
|
||||
* Fixed issues with the window actions
|
||||
([QTCREATORBUG-30381](https://bugreports.qt.io/browse/QTCREATORBUG-30381))
|
||||
|
||||
Editing
|
||||
-------
|
||||
@@ -78,6 +80,8 @@ Editing
|
||||
* Clangd
|
||||
* Fixed that `Follow Symbol Under Cursor` only worked for exact matches
|
||||
([QTCREATORBUG-29814](https://bugreports.qt.io/browse/QTCREATORBUG-29814))
|
||||
* Fixed the version check for remote `clangd` executables
|
||||
([QTCREATORBUG-30374](https://bugreports.qt.io/browse/QTCREATORBUG-30374))
|
||||
|
||||
### QML
|
||||
|
||||
@@ -133,6 +137,8 @@ Projects
|
||||
([QTCREATORBUG-29530](https://bugreports.qt.io/browse/QTCREATORBUG-29530))
|
||||
* Added a file wizard for Qt translation (`.ts`) files
|
||||
([QTCREATORBUG-29775](https://bugreports.qt.io/browse/QTCREATORBUG-29775))
|
||||
* Added an optional warning for special characters in build directories
|
||||
([QTCREATORBUG-20834](https://bugreports.qt.io/browse/QTCREATORBUG-20834))
|
||||
* Improved the environment settings by making the changes explicit in a
|
||||
separate, text-based editor
|
||||
* Increased the maximum width of the target selector
|
||||
@@ -184,6 +190,9 @@ Debugging
|
||||
### C++
|
||||
|
||||
* Added a pretty printer for `std::tuple`
|
||||
* Improved the display of size information for the pretty printer of
|
||||
`QByteArray`
|
||||
([QTCREATORBUG-30065](https://bugreports.qt.io/browse/QTCREATORBUG-30065))
|
||||
* Fixed that breakpoints were not hit while the message dialog about missing
|
||||
debug information was shown
|
||||
([QTCREATORBUG-30168](https://bugreports.qt.io/browse/QTCREATORBUG-30168))
|
||||
@@ -270,6 +279,7 @@ Andre Hartmann
|
||||
André Pönitz
|
||||
Andreas Loth
|
||||
Artem Sokolovskii
|
||||
Assam Boudjelthia
|
||||
Brook Cronin
|
||||
Burak Hancerli
|
||||
Christian Kandeler
|
||||
@@ -306,6 +316,7 @@ Robert Löhning
|
||||
Sami Shalayel
|
||||
Samuel Jose Raposo Vieira Mira
|
||||
Samuel Mira
|
||||
Semih Yavuz
|
||||
Serg Kryvonos
|
||||
Shrief Gabr
|
||||
Sivert Krøvel
|
||||
|
BIN
doc/qtcreator/images/qtcreator-git-add-branch.webp
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
doc/qtcreator/images/qtcreator-git-branches.webp
Normal file
After Width: | Height: | Size: 5.4 KiB |
Before Width: | Height: | Size: 6.7 KiB |
@@ -99,7 +99,7 @@
|
||||
\section2 Supported GDB Versions
|
||||
|
||||
Use GDB 7.5, or later, with the Python scripting extension and Python version
|
||||
3.3, or later.
|
||||
3.5, or later.
|
||||
|
||||
For remote debugging using GDB and GDB server, the minimum supported version
|
||||
of GDB server on the target \l{glossary-device}{device} is 7.0.
|
||||
@@ -121,35 +121,26 @@
|
||||
|
||||
On Linux, the minimum supported version is LLDB 3.8.
|
||||
|
||||
\omit
|
||||
|
||||
\section2 GDB Adapter Modes
|
||||
|
||||
[Advanced Topic]
|
||||
\section2 GDB Run Modes
|
||||
|
||||
The GDB native debugger used internally by the debugger plugin runs in
|
||||
different adapter modes to cope with the variety of supported platforms and
|
||||
environments. All GDB adapters inherit from AbstractGdbAdapter:
|
||||
different modes to cope with the variety of supported platforms and
|
||||
environments:
|
||||
|
||||
\list
|
||||
|
||||
\li PlainGdbAdapter debugs locally started GUI processes. It is
|
||||
physically split into parts that are relevant only when Python is
|
||||
available, parts relevant only when Python is not available, and
|
||||
mixed code.
|
||||
\li Plain mode debugs locally started processes that do not need console input.
|
||||
|
||||
\li TermGdbAdapter debugs locally started processes that need a console.
|
||||
\li Terminal mode debugs locally started processes that need a console.
|
||||
|
||||
\li AttachGdbAdapter debugs local processes started outside \QC.
|
||||
\li Attach mode debugs local processes started outside \QC.
|
||||
|
||||
\li CoreGdbAdapter debugs core files generated from crashes.
|
||||
\li Core mode debugs core files generated from crashes.
|
||||
|
||||
\li RemoteGdbAdapter interacts with the GDB server running on Linux.
|
||||
\li Remote mode interacts with the GDB server running on Linux.
|
||||
|
||||
\endlist
|
||||
|
||||
\endomit
|
||||
|
||||
\section1 Installing Native Debuggers
|
||||
|
||||
The following sections describe installing native debuggers.
|
||||
|
@@ -238,6 +238,10 @@
|
||||
|
||||
\image qtcreator-qml-js-editing.webp {QML/JS Editing preferences}
|
||||
|
||||
When using \c qmlls from Qt 6.7 or later, it is recommended
|
||||
to set \l{QT_QML_GENERATE_QMLLS_INI} to \c{ON} in
|
||||
\uicontrol {Initial Configuration}.
|
||||
|
||||
\sa {Manage Language Servers}{How To: Manage Language Servers},
|
||||
{Enabling and Disabling Messages}, {Language Servers}
|
||||
*/
|
||||
|
@@ -15,9 +15,8 @@
|
||||
application or the \uicontrol {Open Terminal} button to open a terminal,
|
||||
it opens as an output view.
|
||||
|
||||
To open the terminal in a separate window, select \preferences >
|
||||
\uicontrol Terminal, and deselet the \uicontrol {Use internal terminal}
|
||||
check box.
|
||||
To open the terminal in a separate window, go to \preferences >
|
||||
\uicontrol Terminal, and clear \uicontrol {Use internal terminal}.
|
||||
|
||||
On Linux and \macos, you can set the terminal to open by selecting
|
||||
\preferences > \uicontrol Environment > \uicontrol System.
|
||||
@@ -39,7 +38,8 @@
|
||||
.
|
||||
|
||||
\li To select a word in a terminal, double-click it. To select the whole line,
|
||||
triple-click it.
|
||||
triple-click it. To select all text, select \uicontrol {Select All}
|
||||
in the context menu or press \key {Ctrl+A}.
|
||||
|
||||
\li To open links in a browser, files in the editor, or folders in the
|
||||
\l Projects view, hover the mouse over them, and press \key Ctrl.
|
||||
|
@@ -309,10 +309,11 @@
|
||||
\section2 Working with Branches
|
||||
|
||||
To work with Git branches, select \uicontrol {Branches}. The
|
||||
\uicontrol {Git Branches} sidebar view shows the checked out
|
||||
branch in bold and underlined in the list of branches.
|
||||
\uicontrol {Git Branches} view shows a list of branches, as well
|
||||
as the differences between your local branches and their origin.
|
||||
The branch you checked out is shown in bold and underlined.
|
||||
|
||||
\image qtcreator-vcs-gitbranch.png {Git Branches sidebar view}
|
||||
\image qtcreator-git-branches.webp {Git Branches view}
|
||||
|
||||
Old entries and tags are filtered out of the list of branches
|
||||
by default. To include them, select \inlineimage icons/filtericon.png
|
||||
@@ -332,6 +333,20 @@
|
||||
To refresh the list of branches, select \inlineimage icons/reload_gray.png
|
||||
(\uicontrol Refresh).
|
||||
|
||||
\section3 Adding Branches
|
||||
|
||||
To create a new tracking or non-tracking branch, select
|
||||
\inlineimage icons/plus.png.
|
||||
|
||||
\image qtcreator-git-add-branch.webp {Add Branch dialog}
|
||||
|
||||
To check out the branch when creating it, select
|
||||
\uicontrol {Checkout new branch}.
|
||||
|
||||
To track the selected branch, select \uicontrol {Track local branch}.
|
||||
|
||||
\section3 Managing Branches
|
||||
|
||||
The context menu for a branch has the following functions:
|
||||
|
||||
\table
|
||||
|
@@ -11,7 +11,7 @@ from utils import TypeCode
|
||||
|
||||
sys.path.insert(1, os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))))
|
||||
|
||||
from dumper import DumperBase, SubItem
|
||||
from dumper import DumperBase, SubItem, Children, DisplayFormat, UnnamedSubItem
|
||||
|
||||
|
||||
class FakeVoidType(cdbext.Type):
|
||||
@@ -84,10 +84,9 @@ class Dumper(DumperBase):
|
||||
self.check(isinstance(nativeValue, cdbext.Value))
|
||||
val = self.Value(self)
|
||||
val.name = nativeValue.name()
|
||||
val._type = self.fromNativeType(nativeValue.type())
|
||||
# There is no cdb api for the size of bitfields.
|
||||
# Workaround this issue by parsing the native debugger text for integral types.
|
||||
if val._type.code == TypeCode.Integral:
|
||||
if nativeValue.type().code() == TypeCode.Integral:
|
||||
try:
|
||||
integerString = nativeValue.nativeDebuggerValue()
|
||||
except UnicodeDecodeError:
|
||||
@@ -106,16 +105,18 @@ class Dumper(DumperBase):
|
||||
base = 16
|
||||
else:
|
||||
base = 10
|
||||
signed = not val._type.name.startswith('unsigned')
|
||||
signed = not nativeValue.type().name().startswith('unsigned')
|
||||
try:
|
||||
val.ldata = int(integerString, base).to_bytes(val._type.size(),
|
||||
val.ldata = int(integerString, base).to_bytes((nativeValue.type().bitsize() +7) // 8,
|
||||
byteorder='little', signed=signed)
|
||||
except:
|
||||
# read raw memory in case the integerString can not be interpreted
|
||||
pass
|
||||
if val._type.code == TypeCode.Enum:
|
||||
if nativeValue.type().code() == TypeCode.Enum:
|
||||
val.ldisplay = self.enumValue(nativeValue)
|
||||
val.isBaseClass = val.name == val._type.name
|
||||
elif not nativeValue.type().resolved and nativeValue.type().code() == TypeCode.Struct and not nativeValue.hasChildren():
|
||||
val.ldisplay = self.enumValue(nativeValue)
|
||||
val.isBaseClass = val.name == nativeValue.type().name()
|
||||
val.nativeValue = nativeValue
|
||||
val.laddress = nativeValue.address()
|
||||
val.lbitsize = nativeValue.bitsize()
|
||||
@@ -136,6 +137,9 @@ class Dumper(DumperBase):
|
||||
for f in nativeType.fields()])
|
||||
return typeId
|
||||
|
||||
def nativeValueType(self, nativeValue):
|
||||
return self.fromNativeType(nativeValue.type())
|
||||
|
||||
def fromNativeType(self, nativeType):
|
||||
self.check(isinstance(nativeType, cdbext.Type))
|
||||
typeId = self.nativeTypeId(nativeType)
|
||||
@@ -150,51 +154,66 @@ class Dumper(DumperBase):
|
||||
if nativeType.name().startswith('<function>'):
|
||||
code = TypeCode.Function
|
||||
elif nativeType.targetName() != nativeType.name():
|
||||
targetType = self.lookupType(nativeType.targetName(), nativeType.moduleId())
|
||||
if targetType is not None and targetType is not nativeType:
|
||||
return self.createPointerType(targetType)
|
||||
return self.createPointerType(nativeType.targetName())
|
||||
|
||||
if code == TypeCode.Array:
|
||||
# cdb reports virtual function tables as arrays those ar handled separetly by
|
||||
# the DumperBase. Declare those types as structs prevents a lookup to a
|
||||
# none existing type
|
||||
if not nativeType.name().startswith('__fptr()') and not nativeType.name().startswith('<gentype '):
|
||||
targetType = self.lookupType(nativeType.targetName(), nativeType.moduleId())
|
||||
if targetType is not None:
|
||||
return self.createArrayType(targetType, nativeType.arrayElements())
|
||||
targetName = nativeType.targetName()
|
||||
count = nativeType.arrayElements()
|
||||
if targetName.endswith(']'):
|
||||
(prefix, suffix, inner_count) = self.splitArrayType(targetName)
|
||||
type_name = '%s[%d][%d]%s' % (prefix, count, inner_count, suffix)
|
||||
else:
|
||||
type_name = '%s[%d]' % (targetName, count)
|
||||
tdata = self.TypeData(self, typeId)
|
||||
tdata.name = type_name
|
||||
tdata.code = TypeCode.Array
|
||||
tdata.ltarget = targetName
|
||||
tdata.lbitsize = lambda: nativeType.bitsize()
|
||||
return self.Type(self, typeId)
|
||||
|
||||
code = TypeCode.Struct
|
||||
|
||||
tdata = self.TypeData(self, typeId)
|
||||
tdata.name = nativeType.name()
|
||||
tdata.lbitsize = nativeType.bitsize()
|
||||
tdata.lbitsize = lambda: nativeType.bitsize()
|
||||
tdata.code = code
|
||||
tdata.moduleName = nativeType.module()
|
||||
tdata.moduleName = lambda: nativeType.module()
|
||||
if code == TypeCode.Struct:
|
||||
tdata.lfields = lambda value: \
|
||||
self.listFields(nativeType, value)
|
||||
tdata.lalignment = lambda: \
|
||||
self.nativeStructAlignment(nativeType)
|
||||
if code == TypeCode.Enum:
|
||||
tdata.enumDisplay = lambda intval, addr, form: \
|
||||
self.nativeTypeEnumDisplay(nativeType, intval, form)
|
||||
tdata.templateArguments = lambda: \
|
||||
self.listTemplateParameters(nativeType.name())
|
||||
return self.Type(self, typeId)
|
||||
|
||||
def listFields(self, nativeType, value):
|
||||
if value.address() is None or value.address() == 0:
|
||||
raise Exception("")
|
||||
nativeValue = value.nativeValue
|
||||
if nativeValue is None:
|
||||
nativeValue = cdbext.createValue(value.address(), nativeType)
|
||||
def listNativeValueChildren(self, nativeValue):
|
||||
index = 0
|
||||
nativeMember = nativeValue.childFromIndex(index)
|
||||
while nativeMember is not None:
|
||||
while nativeMember:
|
||||
if nativeMember.address() != 0:
|
||||
yield self.fromNativeValue(nativeMember)
|
||||
index += 1
|
||||
nativeMember = nativeValue.childFromIndex(index)
|
||||
|
||||
def listValueChildren(self, value):
|
||||
nativeValue = value.nativeValue
|
||||
if nativeValue is None:
|
||||
nativeValue = cdbext.createValue(value.address(), self.lookupNativeType(value.type.name, 0))
|
||||
return self.listNativeValueChildren(nativeValue)
|
||||
|
||||
def listFields(self, nativeType, value):
|
||||
nativeValue = value.nativeValue
|
||||
if nativeValue is None:
|
||||
nativeValue = cdbext.createValue(value.address(), nativeType)
|
||||
return self.listNativeValueChildren(nativeValue)
|
||||
|
||||
def nativeStructAlignment(self, nativeType):
|
||||
#DumperBase.warn("NATIVE ALIGN FOR %s" % nativeType.name)
|
||||
def handleItem(nativeFieldType, align):
|
||||
@@ -307,6 +326,7 @@ class Dumper(DumperBase):
|
||||
namespaceIndex = name.find('::')
|
||||
if namespaceIndex > 0:
|
||||
namespace = name[:namespaceIndex + 2]
|
||||
self.qtNamespace = lambda: namespace
|
||||
self.qtCustomEventFunc = self.parseAndEvaluate(
|
||||
'%s!%sQObject::customEvent' %
|
||||
(self.qtCoreModuleName(), namespace)).address()
|
||||
@@ -498,7 +518,7 @@ class Dumper(DumperBase):
|
||||
else:
|
||||
val = self.Value(self)
|
||||
val.laddress = value.pointer()
|
||||
val._type = value.type.dereference()
|
||||
val._type = DumperBase.Type(self, value.type.targetName)
|
||||
val.nativeValue = value.nativeValue
|
||||
|
||||
return val
|
||||
@@ -519,3 +539,424 @@ class Dumper(DumperBase):
|
||||
def symbolAddress(self, symbolName):
|
||||
res = self.nativeParseAndEvaluate(symbolName)
|
||||
return None if res is None else res.address()
|
||||
|
||||
def putItemX(self, value):
|
||||
#DumperBase.warn('PUT ITEM: %s' % value.stringify())
|
||||
|
||||
typeobj = value.type # unqualified()
|
||||
typeName = typeobj.name
|
||||
|
||||
self.addToCache(typeobj) # Fill type cache
|
||||
|
||||
if not value.lIsInScope:
|
||||
self.putSpecialValue('optimizedout')
|
||||
#self.putType(typeobj)
|
||||
#self.putSpecialValue('outofscope')
|
||||
self.putNumChild(0)
|
||||
return
|
||||
|
||||
if not isinstance(value, self.Value):
|
||||
raise RuntimeError('WRONG TYPE IN putItem: %s' % type(self.Value))
|
||||
|
||||
# Try on possibly typedefed type first.
|
||||
if self.tryPutPrettyItem(typeName, value):
|
||||
if typeobj.code == TypeCode.Pointer:
|
||||
self.putOriginalAddress(value.address())
|
||||
else:
|
||||
self.putAddress(value.address())
|
||||
return
|
||||
|
||||
if typeobj.code == TypeCode.Pointer:
|
||||
self.putFormattedPointer(value)
|
||||
return
|
||||
|
||||
self.putAddress(value.address())
|
||||
if value.lbitsize is not None:
|
||||
self.putField('size', value.lbitsize // 8)
|
||||
|
||||
if typeobj.code == TypeCode.Function:
|
||||
#DumperBase.warn('FUNCTION VALUE: %s' % value)
|
||||
self.putType(typeobj)
|
||||
self.putSymbolValue(value.pointer())
|
||||
self.putNumChild(0)
|
||||
return
|
||||
|
||||
if typeobj.code == TypeCode.Enum:
|
||||
#DumperBase.warn('ENUM VALUE: %s' % value.stringify())
|
||||
self.putType(typeobj.name)
|
||||
self.putValue(value.display())
|
||||
self.putNumChild(0)
|
||||
return
|
||||
|
||||
if typeobj.code == TypeCode.Array:
|
||||
#DumperBase.warn('ARRAY VALUE: %s' % value)
|
||||
self.putCStyleArray(value)
|
||||
return
|
||||
|
||||
if typeobj.code == TypeCode.Integral:
|
||||
#DumperBase.warn('INTEGER: %s %s' % (value.name, value))
|
||||
val = value.value()
|
||||
self.putNumChild(0)
|
||||
self.putValue(val)
|
||||
self.putType(typeName)
|
||||
return
|
||||
|
||||
if typeobj.code == TypeCode.Float:
|
||||
#DumperBase.warn('FLOAT VALUE: %s' % value)
|
||||
self.putValue(value.value())
|
||||
self.putNumChild(0)
|
||||
self.putType(typeobj.name)
|
||||
return
|
||||
|
||||
if typeobj.code in (TypeCode.Reference, TypeCode.RValueReference):
|
||||
#DumperBase.warn('REFERENCE VALUE: %s' % value)
|
||||
val = value.dereference()
|
||||
if val.laddress != 0:
|
||||
self.putItem(val)
|
||||
else:
|
||||
self.putSpecialValue('nullreference')
|
||||
self.putBetterType(typeName)
|
||||
return
|
||||
|
||||
if typeobj.code == TypeCode.Complex:
|
||||
self.putType(typeobj)
|
||||
self.putValue(value.display())
|
||||
self.putNumChild(0)
|
||||
return
|
||||
|
||||
self.putType(typeName)
|
||||
|
||||
if value.summary is not None and self.useFancy:
|
||||
self.putValue(self.hexencode(value.summary), 'utf8:1:0')
|
||||
self.putNumChild(0)
|
||||
return
|
||||
|
||||
self.putExpandable()
|
||||
self.putEmptyValue()
|
||||
#DumperBase.warn('STRUCT GUTS: %s ADDRESS: 0x%x ' % (value.name, value.address()))
|
||||
if self.showQObjectNames:
|
||||
#with self.timer(self.currentIName):
|
||||
self.putQObjectNameValue(value)
|
||||
if self.isExpanded():
|
||||
self.putField('sortable', 1)
|
||||
with Children(self):
|
||||
baseIndex = 0
|
||||
for item in self.listValueChildren(value):
|
||||
if item.name.startswith('__vfptr'):
|
||||
with SubItem(self, '[vptr]'):
|
||||
# int (**)(void)
|
||||
self.putType(' ')
|
||||
self.putSortGroup(20)
|
||||
self.putValue(item.name)
|
||||
n = 100
|
||||
if self.isExpanded():
|
||||
with Children(self):
|
||||
n = self.putVTableChildren(item, n)
|
||||
self.putNumChild(n)
|
||||
continue
|
||||
|
||||
if item.isBaseClass:
|
||||
baseIndex += 1
|
||||
# We cannot use nativeField.name as part of the iname as
|
||||
# it might contain spaces and other strange characters.
|
||||
with UnnamedSubItem(self, "@%d" % baseIndex):
|
||||
self.putField('iname', self.currentIName)
|
||||
self.putField('name', '[%s]' % item.name)
|
||||
self.putSortGroup(1000 - baseIndex)
|
||||
self.putAddress(item.address())
|
||||
self.putItem(item)
|
||||
continue
|
||||
|
||||
|
||||
with SubItem(self, item.name):
|
||||
self.putItem(item)
|
||||
if self.showQObjectNames:
|
||||
self.tryPutQObjectGuts(value)
|
||||
|
||||
|
||||
def putFormattedPointerX(self, value: DumperBase.Value):
|
||||
self.putOriginalAddress(value.address())
|
||||
pointer = value.pointer()
|
||||
self.putAddress(pointer)
|
||||
if pointer == 0:
|
||||
self.putType(value.type)
|
||||
self.putValue('0x0')
|
||||
return
|
||||
|
||||
typeName = value.type.name
|
||||
|
||||
try:
|
||||
self.readRawMemory(pointer, 1)
|
||||
except:
|
||||
# Failure to dereference a pointer should at least
|
||||
# show the value of a pointer.
|
||||
#DumperBase.warn('BAD POINTER: %s' % value)
|
||||
self.putValue('0x%x' % pointer)
|
||||
self.putType(typeName)
|
||||
return
|
||||
|
||||
if self.currentIName.endswith('.this'):
|
||||
self.putDerefedPointer(value)
|
||||
return
|
||||
|
||||
displayFormat = self.currentItemFormat(value.type.name)
|
||||
|
||||
if value.type.targetName == 'void':
|
||||
#DumperBase.warn('VOID POINTER: %s' % displayFormat)
|
||||
self.putType(typeName)
|
||||
self.putSymbolValue(pointer)
|
||||
return
|
||||
|
||||
if displayFormat == DisplayFormat.Raw:
|
||||
# Explicitly requested bald pointer.
|
||||
#DumperBase.warn('RAW')
|
||||
self.putType(typeName)
|
||||
self.putValue('0x%x' % pointer)
|
||||
self.putExpandable()
|
||||
if self.currentIName in self.expandedINames:
|
||||
with Children(self):
|
||||
with SubItem(self, '*'):
|
||||
self.putItem(value.dereference())
|
||||
return
|
||||
|
||||
limit = self.displayStringLimit
|
||||
if displayFormat in (DisplayFormat.SeparateLatin1String, DisplayFormat.SeparateUtf8String):
|
||||
limit = 1000000
|
||||
if self.tryPutSimpleFormattedPointer(pointer, typeName,
|
||||
value.type.targetName, displayFormat, limit):
|
||||
self.putExpandable()
|
||||
return
|
||||
|
||||
if DisplayFormat.Array10 <= displayFormat and displayFormat <= DisplayFormat.Array10000:
|
||||
n = (10, 100, 1000, 10000)[displayFormat - DisplayFormat.Array10]
|
||||
self.putType(typeName)
|
||||
self.putItemCount(n)
|
||||
self.putArrayData(value.pointer(), n, value.type.targetName)
|
||||
return
|
||||
|
||||
#DumperBase.warn('AUTODEREF: %s' % self.autoDerefPointers)
|
||||
#DumperBase.warn('INAME: %s' % self.currentIName)
|
||||
if self.autoDerefPointers:
|
||||
# Generic pointer type with AutomaticFormat, but never dereference char types:
|
||||
if value.type.targetName not in (
|
||||
'char',
|
||||
'signed char',
|
||||
'int8_t',
|
||||
'qint8',
|
||||
'unsigned char',
|
||||
'uint8_t',
|
||||
'quint8',
|
||||
'wchar_t',
|
||||
'CHAR',
|
||||
'WCHAR',
|
||||
'char8_t',
|
||||
'char16_t',
|
||||
'char32_t'
|
||||
):
|
||||
self.putDerefedPointer(value)
|
||||
return
|
||||
|
||||
#DumperBase.warn('GENERIC PLAIN POINTER: %s' % value.type)
|
||||
#DumperBase.warn('ADDR PLAIN POINTER: 0x%x' % value.laddress)
|
||||
self.putType(typeName)
|
||||
self.putSymbolValue(pointer)
|
||||
self.putExpandable()
|
||||
if self.currentIName in self.expandedINames:
|
||||
with Children(self):
|
||||
with SubItem(self, '*'):
|
||||
self.putItem(value.dereference())
|
||||
|
||||
|
||||
def putCStyleArray(self, value):
|
||||
arrayType = value.type
|
||||
innerType = arrayType.ltarget
|
||||
address = value.address()
|
||||
if address:
|
||||
self.putValue('@0x%x' % address, priority=-1)
|
||||
else:
|
||||
self.putEmptyValue()
|
||||
self.putType(arrayType)
|
||||
|
||||
displayFormat = self.currentItemFormat()
|
||||
arrayByteSize = arrayType.size()
|
||||
n = self.arrayItemCountFromTypeName(value.type.name, 100)
|
||||
|
||||
p = value.address()
|
||||
if displayFormat != DisplayFormat.Raw and p:
|
||||
if innerType.name in (
|
||||
'char',
|
||||
'int8_t',
|
||||
'qint8',
|
||||
'wchar_t',
|
||||
'unsigned char',
|
||||
'uint8_t',
|
||||
'quint8',
|
||||
'signed char',
|
||||
'CHAR',
|
||||
'WCHAR',
|
||||
'char8_t',
|
||||
'char16_t',
|
||||
'char32_t'
|
||||
):
|
||||
self.putCharArrayHelper(p, n, innerType, self.currentItemFormat(),
|
||||
makeExpandable=False)
|
||||
else:
|
||||
self.tryPutSimpleFormattedPointer(p, arrayType, innerType,
|
||||
displayFormat, arrayByteSize)
|
||||
self.putNumChild(n)
|
||||
|
||||
if self.isExpanded():
|
||||
if n > 100:
|
||||
addrStep = innerType.size()
|
||||
with Children(self, n, innerType, addrBase=address, addrStep=addrStep):
|
||||
for i in self.childRange():
|
||||
self.putSubItem(i, self.createValue(address + i * addrStep, innerType))
|
||||
else:
|
||||
with Children(self):
|
||||
n = 0
|
||||
for item in self.listValueChildren(value):
|
||||
with SubItem(self, n):
|
||||
n += 1
|
||||
self.putItem(item)
|
||||
|
||||
|
||||
def putArrayData(self, base, n, innerType, childNumChild=None):
|
||||
self.checkIntType(base)
|
||||
self.checkIntType(n)
|
||||
addrBase = base
|
||||
innerType = self.createType(innerType)
|
||||
innerSize = innerType.size()
|
||||
self.putNumChild(n)
|
||||
#DumperBase.warn('ADDRESS: 0x%x INNERSIZE: %s INNERTYPE: %s' % (addrBase, innerSize, innerType))
|
||||
enc = innerType.simpleEncoding()
|
||||
maxNumChild = self.maxArrayCount()
|
||||
if enc:
|
||||
self.put('childtype="%s",' % innerType.name)
|
||||
self.put('addrbase="0x%x",' % addrBase)
|
||||
self.put('addrstep="0x%x",' % innerSize)
|
||||
self.put('arrayencoding="%s",' % enc)
|
||||
self.put('endian="%s",' % self.packCode)
|
||||
if n > maxNumChild:
|
||||
self.put('childrenelided="%s",' % n)
|
||||
n = maxNumChild
|
||||
self.put('arraydata="')
|
||||
self.put(self.readMemory(addrBase, n * innerSize))
|
||||
self.put('",')
|
||||
else:
|
||||
with Children(self, n, innerType, childNumChild, maxNumChild,
|
||||
addrBase=addrBase, addrStep=innerSize):
|
||||
for i in self.childRange():
|
||||
self.putSubItem(i, self.createValue(addrBase + i * innerSize, innerType))
|
||||
|
||||
def tryPutSimpleFormattedPointer(self, ptr, typeName, innerType, displayFormat, limit):
|
||||
if isinstance(innerType, self.Type):
|
||||
innerType = innerType.name
|
||||
if displayFormat == DisplayFormat.Automatic:
|
||||
if innerType in ('char', 'signed char', 'unsigned char', 'uint8_t', 'CHAR'):
|
||||
# Use UTF-8 as default for char *.
|
||||
self.putType(typeName)
|
||||
(length, shown, data) = self.readToFirstZero(ptr, 1, limit)
|
||||
self.putValue(data, 'utf8', length=length)
|
||||
if self.isExpanded():
|
||||
self.putArrayData(ptr, shown, innerType)
|
||||
return True
|
||||
|
||||
if innerType in ('wchar_t', 'WCHAR'):
|
||||
self.putType(typeName)
|
||||
charSize = self.lookupType('wchar_t').size()
|
||||
(length, data) = self.encodeCArray(ptr, charSize, limit)
|
||||
if charSize == 2:
|
||||
self.putValue(data, 'utf16', length=length)
|
||||
else:
|
||||
self.putValue(data, 'ucs4', length=length)
|
||||
return True
|
||||
|
||||
if displayFormat == DisplayFormat.Latin1String:
|
||||
self.putType(typeName)
|
||||
(length, data) = self.encodeCArray(ptr, 1, limit)
|
||||
self.putValue(data, 'latin1', length=length)
|
||||
return True
|
||||
|
||||
if displayFormat == DisplayFormat.SeparateLatin1String:
|
||||
self.putType(typeName)
|
||||
(length, data) = self.encodeCArray(ptr, 1, limit)
|
||||
self.putValue(data, 'latin1', length=length)
|
||||
self.putDisplay('latin1:separate', data)
|
||||
return True
|
||||
|
||||
if displayFormat == DisplayFormat.Utf8String:
|
||||
self.putType(typeName)
|
||||
(length, data) = self.encodeCArray(ptr, 1, limit)
|
||||
self.putValue(data, 'utf8', length=length)
|
||||
return True
|
||||
|
||||
if displayFormat == DisplayFormat.SeparateUtf8String:
|
||||
self.putType(typeName)
|
||||
(length, data) = self.encodeCArray(ptr, 1, limit)
|
||||
self.putValue(data, 'utf8', length=length)
|
||||
self.putDisplay('utf8:separate', data)
|
||||
return True
|
||||
|
||||
if displayFormat == DisplayFormat.Local8BitString:
|
||||
self.putType(typeName)
|
||||
(length, data) = self.encodeCArray(ptr, 1, limit)
|
||||
self.putValue(data, 'local8bit', length=length)
|
||||
return True
|
||||
|
||||
if displayFormat == DisplayFormat.Utf16String:
|
||||
self.putType(typeName)
|
||||
(length, data) = self.encodeCArray(ptr, 2, limit)
|
||||
self.putValue(data, 'utf16', length=length)
|
||||
return True
|
||||
|
||||
if displayFormat == DisplayFormat.Ucs4String:
|
||||
self.putType(typeName)
|
||||
(length, data) = self.encodeCArray(ptr, 4, limit)
|
||||
self.putValue(data, 'ucs4', length=length)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def putDerefedPointer(self, value):
|
||||
derefValue = value.dereference()
|
||||
savedCurrentChildType = self.currentChildType
|
||||
self.currentChildType = value.type.targetName
|
||||
self.putType(value.type.targetName)
|
||||
derefValue.name = '*'
|
||||
derefValue.autoDerefCount = value.autoDerefCount + 1
|
||||
|
||||
if derefValue.type.code == TypeCode.Pointer:
|
||||
self.putField('autoderefcount', '{}'.format(derefValue.autoDerefCount))
|
||||
|
||||
self.putItem(derefValue)
|
||||
self.currentChildType = savedCurrentChildType
|
||||
|
||||
def extractPointer(self, value):
|
||||
code = 'I' if self.ptrSize() == 4 else 'Q'
|
||||
return self.extractSomething(value, code, 8 * self.ptrSize())
|
||||
|
||||
def createValue(self, datish, typish):
|
||||
if self.isInt(datish): # Used as address.
|
||||
return self.createValueFromAddressAndType(datish, typish)
|
||||
if isinstance(datish, bytes):
|
||||
val = self.Value(self)
|
||||
if isinstance(typish, self.Type):
|
||||
val._type = typish
|
||||
else:
|
||||
val._type = self.Type(self, typish)
|
||||
#DumperBase.warn('CREATING %s WITH DATA %s' % (val.type.name, self.hexencode(datish)))
|
||||
val.ldata = datish
|
||||
val.check()
|
||||
return val
|
||||
raise RuntimeError('EXPECTING ADDRESS OR BYTES, GOT %s' % type(datish))
|
||||
|
||||
def createValueFromAddressAndType(self, address, typish):
|
||||
val = self.Value(self)
|
||||
if isinstance(typish, self.Type):
|
||||
val._type = typish
|
||||
else:
|
||||
val._type = self.Type(self, typish)
|
||||
val.laddress = address
|
||||
if self.useDynamicType:
|
||||
val._type = val.type.dynamicType(address)
|
||||
return val
|
||||
|
@@ -3041,7 +3041,7 @@ class DumperBase():
|
||||
or self.type.name.startswith('unsigned ') \
|
||||
or self.type.name.find(' unsigned ') != -1
|
||||
if bitsize is None:
|
||||
bitsize = self.type.bitsize()
|
||||
bitsize = self.type.lbitsize
|
||||
return self.extractInteger(bitsize, unsigned)
|
||||
|
||||
def floatingPoint(self):
|
||||
@@ -3512,26 +3512,40 @@ class DumperBase():
|
||||
tdata.moduleName = self.moduleName
|
||||
return tdata
|
||||
|
||||
@property
|
||||
def bitsize(self):
|
||||
if callable(self.lbitsize):
|
||||
self.lbitsize = self.lbitsize()
|
||||
return self.lbitsize
|
||||
|
||||
class Type():
|
||||
def __init__(self, dumper, typeId):
|
||||
self.typeId = typeId
|
||||
self.typeId = typeId.replace('@', dumper.qtNamespace())
|
||||
self.dumper = dumper
|
||||
self.tdata = dumper.typeData.get(typeId, None)
|
||||
if self.tdata is None:
|
||||
#DumperBase.warn('USING : %s' % self.typeId)
|
||||
self.dumper.lookupType(self.typeId)
|
||||
self.tdata = self.dumper.typeData.get(self.typeId)
|
||||
self.initialized = False
|
||||
|
||||
def __str__(self):
|
||||
#return self.typeId
|
||||
return self.stringify()
|
||||
|
||||
@property
|
||||
def tdata(self):
|
||||
if not self.initialized:
|
||||
self.initialized = True
|
||||
self.data = self.dumper.typeData.get(self.typeId, None)
|
||||
if self.data is None:
|
||||
#DumperBase.warn('USING : %s' % self.typeId)
|
||||
self.dumper.lookupType(self.typeId)
|
||||
self.data = self.dumper.typeData.get(self.typeId)
|
||||
return self.data
|
||||
|
||||
def setTdata(self, tdata):
|
||||
self.initialized = True
|
||||
self.data = tdata
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
tdata = self.dumper.typeData.get(self.typeId)
|
||||
if tdata is None:
|
||||
return self.typeId
|
||||
return tdata.name
|
||||
return self.typeId if self.tdata is None else self.tdata.name
|
||||
|
||||
@property
|
||||
def code(self):
|
||||
@@ -3539,7 +3553,7 @@ class DumperBase():
|
||||
|
||||
@property
|
||||
def lbitsize(self):
|
||||
return self.tdata.lbitsize
|
||||
return self.tdata.bitsize
|
||||
|
||||
@property
|
||||
def lbitpos(self):
|
||||
@@ -3547,15 +3561,25 @@ class DumperBase():
|
||||
|
||||
@property
|
||||
def ltarget(self):
|
||||
if isinstance(self.tdata.ltarget, str):
|
||||
self.tdata.ltarget = self.dumper.createType(self.tdata.ltarget)
|
||||
return self.tdata.ltarget
|
||||
|
||||
@property
|
||||
def targetName(self):
|
||||
if self.tdata.ltarget is None:
|
||||
return ''
|
||||
return self.tdata.ltarget if isinstance(self.tdata.ltarget, str) else self.tdata.ltarget.name
|
||||
|
||||
@property
|
||||
def moduleName(self):
|
||||
if callable(self.tdata.moduleName):
|
||||
self.tdata.moduleName = self.tdata.moduleName()
|
||||
return self.tdata.moduleName
|
||||
|
||||
def stringify(self):
|
||||
return 'Type(name="%s",bsize=%s,code=%s)' \
|
||||
% (self.tdata.name, self.tdata.lbitsize, self.tdata.code)
|
||||
% (self.tdata.name, self.lbitsize, self.tdata.code)
|
||||
|
||||
def __getitem__(self, index):
|
||||
if self.dumper.isInt(index):
|
||||
@@ -3659,7 +3683,7 @@ class DumperBase():
|
||||
|
||||
def alignment(self):
|
||||
if self.tdata.code == TypeCode.Typedef:
|
||||
return self.tdata.ltarget.alignment()
|
||||
return self.ltarget.alignment()
|
||||
if self.tdata.code in (TypeCode.Integral, TypeCode.Float, TypeCode.Enum):
|
||||
if self.tdata.name in ('double', 'long long', 'unsigned long long'):
|
||||
# Crude approximation.
|
||||
@@ -3678,7 +3702,7 @@ class DumperBase():
|
||||
return self.dumper.createPointerType(self)
|
||||
|
||||
def target(self):
|
||||
return self.tdata.ltarget
|
||||
return self.ltarget
|
||||
|
||||
def stripTypedefs(self):
|
||||
if isinstance(self, self.dumper.Type) and self.code != TypeCode.Typedef:
|
||||
@@ -3687,7 +3711,7 @@ class DumperBase():
|
||||
return self.ltarget
|
||||
|
||||
def size(self):
|
||||
bs = self.bitsize()
|
||||
bs = self.lbitsize
|
||||
if bs % 8 != 0:
|
||||
DumperBase.warn('ODD SIZE: %s' % self)
|
||||
return (7 + bs) >> 3
|
||||
@@ -3797,12 +3821,12 @@ class DumperBase():
|
||||
return val
|
||||
|
||||
def createPointerType(self, targetType):
|
||||
if not isinstance(targetType, self.Type):
|
||||
raise RuntimeError('Expected type in createPointerType(), got %s'
|
||||
if not isinstance(targetType, (str, self.Type)):
|
||||
raise RuntimeError('Expected type or str in createPointerType(), got %s'
|
||||
% type(targetType))
|
||||
typeId = targetType.typeId + ' *'
|
||||
typeId = (targetType if isinstance(targetType, str) else targetType.typeId) + ' *'
|
||||
tdata = self.TypeData(self, typeId)
|
||||
tdata.name = targetType.name + '*'
|
||||
tdata.name = (targetType if isinstance(targetType, str) else targetType.name) + '*'
|
||||
tdata.lbitsize = 8 * self.ptrSize()
|
||||
tdata.code = TypeCode.Pointer
|
||||
tdata.ltarget = targetType
|
||||
@@ -3927,7 +3951,7 @@ class DumperBase():
|
||||
tdata = self.typeData.get(typish, None)
|
||||
if tdata is not None:
|
||||
if tdata.lbitsize is not None:
|
||||
if tdata.lbitsize > 0:
|
||||
if callable(tdata.lbitsize) or tdata.lbitsize > 0:
|
||||
return self.Type(self, typish)
|
||||
|
||||
knownType = self.lookupType(typish)
|
||||
@@ -3944,7 +3968,7 @@ class DumperBase():
|
||||
if typish.endswith('*'):
|
||||
tdata.code = TypeCode.Pointer
|
||||
tdata.lbitsize = 8 * self.ptrSize()
|
||||
tdata.ltarget = self.createType(typish[:-1].strip())
|
||||
tdata.ltarget = typish[:-1].strip()
|
||||
|
||||
typeobj = self.Type(self, tdata.typeId)
|
||||
#DumperBase.warn('CREATE TYPE: %s' % typeobj.stringify())
|
||||
|
@@ -419,7 +419,7 @@ class Dumper(DumperBase):
|
||||
targetTypeName = typeName[0:pos1].strip()
|
||||
#DumperBase.warn("TARGET TYPENAME: %s" % targetTypeName)
|
||||
targetType = self.fromNativeType(nativeTargetType)
|
||||
targetType.tdata = targetType.tdata.copy()
|
||||
targetType.setTdata(targetType.tdata.copy())
|
||||
targetType.tdata.name = targetTypeName
|
||||
return self.createArrayType(targetType, count)
|
||||
if hasattr(nativeType, 'GetVectorElementType'): # New in 3.8(?) / 350.x
|
||||
@@ -1583,7 +1583,8 @@ class Dumper(DumperBase):
|
||||
result += ',ignorecount="%d"' % loc.GetIgnoreCount()
|
||||
result += ',file="%s"' % toCString(lineEntry.GetFileSpec())
|
||||
result += ',line="%d"' % lineEntry.GetLine()
|
||||
result += ',addr="%s"},' % addr.GetFileAddress()
|
||||
result += ',addr="%s"' % addr.GetLoadAddress(self.target)
|
||||
result += ',faddr="%s"},' % addr.GetFileAddress()
|
||||
result += ']'
|
||||
if lineEntry is not None:
|
||||
result += ',file="%s"' % toCString(lineEntry.GetFileSpec())
|
||||
|
@@ -1,4 +1,5 @@
|
||||
[General]
|
||||
Includes=dark.figmatokens
|
||||
ThemeName=Dark
|
||||
PreferredStyles=
|
||||
DefaultTextEditorColorScheme=dark.xml
|
||||
@@ -406,39 +407,6 @@ Debugger_WatchItem_ValueChanged=ffff6666
|
||||
|
||||
Debugger_Breakpoint_TextMarkColor=ffff4040
|
||||
|
||||
; Qt Creator Color Tokens - dark mode
|
||||
Token_Basic_Black=ff131313
|
||||
Token_Basic_White=fff8f8f8
|
||||
Token_Accent_Default=ff23b26a
|
||||
Token_Accent_Muted=ff1f9b5d
|
||||
Token_Accent_Subtle=ff1a8550
|
||||
Token_Background_Default=ff1f1f1f
|
||||
Token_Background_Muted=ff262626
|
||||
Token_Background_Subtle=ff2e2e2e
|
||||
Token_Foreground_Default=ff5a5a5a
|
||||
Token_Foreground_Muted=ff3e3e3e
|
||||
Token_Foreground_Subtle=ff303030
|
||||
Token_Text_Default=fff8f8f8
|
||||
Token_Text_Muted=ffaeaeae
|
||||
Token_Text_Subtle=ff595959
|
||||
Token_Stroke_Strong=ffeeeeee
|
||||
Token_Stroke_Muted=ff727272
|
||||
Token_Stroke_Subtle=ff3a3a3a
|
||||
Token_Notification_Alert=ffc98014
|
||||
Token_Notification_Success=ff1f9b5d
|
||||
Token_Notification_Neutral=ff016876
|
||||
Token_Notification_Danger=ffb22245
|
||||
|
||||
Welcome_TextColor=text
|
||||
Welcome_ForegroundPrimaryColor=ffa3a3a3
|
||||
Welcome_ForegroundSecondaryColor=ff808080
|
||||
Welcome_BackgroundPrimaryColor=normalBackground
|
||||
Welcome_BackgroundSecondaryColor=shadowBackground
|
||||
Welcome_HoverColor=ff404040
|
||||
Welcome_AccentColor=ff57d658
|
||||
Welcome_LinkColor=ff67e668
|
||||
Welcome_DisabledLinkColor=textDisabled
|
||||
|
||||
Timeline_TextColor=text
|
||||
Timeline_BackgroundColor1=normalBackground
|
||||
Timeline_BackgroundColor2=ff444444
|
||||
|
31
share/qtcreator/themes/dark.figmatokens
Normal 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
|
@@ -1,4 +1,5 @@
|
||||
[General]
|
||||
Includes=light.figmatokens
|
||||
ThemeName=Classic
|
||||
PreferredStyles=
|
||||
|
||||
@@ -398,39 +399,6 @@ Debugger_WatchItem_ValueChanged=ffc80000
|
||||
|
||||
Debugger_Breakpoint_TextMarkColor=ffff4040
|
||||
|
||||
; Qt Creator Color Tokens - light mode
|
||||
Token_Basic_Black=ff131313
|
||||
Token_Basic_White=fff2f2f2
|
||||
Token_Accent_Default=ff23b26a
|
||||
Token_Accent_Muted=ff1f9b5d
|
||||
Token_Accent_Subtle=ff1a8550
|
||||
Token_Background_Default=ffe3e3e3
|
||||
Token_Background_Muted=ffeeeeee
|
||||
Token_Background_Subtle=fffbfbfb
|
||||
Token_Foreground_Default=ffcdcdcd
|
||||
Token_Foreground_Muted=ffd5d5d5
|
||||
Token_Foreground_Subtle=ffdddddd
|
||||
Token_Text_Default=ff393939
|
||||
Token_Text_Muted=ff7c7c7c
|
||||
Token_Text_Subtle=ffbebebe
|
||||
Token_Stroke_Strong=ff464646
|
||||
Token_Stroke_Muted=ff727272
|
||||
Token_Stroke_Subtle=ffcdcdcd
|
||||
Token_Notification_Alert=ffeb991f
|
||||
Token_Notification_Success=ff23b26a
|
||||
Token_Notification_Neutral=ff0e7887
|
||||
Token_Notification_Danger=ffdc1343
|
||||
|
||||
Welcome_TextColor=ff000000
|
||||
Welcome_ForegroundPrimaryColor=shadowBackground
|
||||
Welcome_ForegroundSecondaryColor=ff939393
|
||||
Welcome_BackgroundPrimaryColor=fffafafa
|
||||
Welcome_BackgroundSecondaryColor=ffffffff
|
||||
Welcome_HoverColor=ffefefef
|
||||
Welcome_AccentColor=ff45ce55
|
||||
Welcome_LinkColor=ff20a020
|
||||
Welcome_DisabledLinkColor=textDisabled
|
||||
|
||||
Timeline_TextColor=darkText
|
||||
Timeline_BackgroundColor1=ffffffff
|
||||
Timeline_BackgroundColor2=fff6f6f6
|
||||
|
@@ -1,4 +1,5 @@
|
||||
[General]
|
||||
Includes=light.figmatokens
|
||||
ThemeName=Design Light
|
||||
PreferredStyles=
|
||||
|
||||
@@ -410,39 +411,6 @@ Debugger_WatchItem_ValueChanged=ffbf0303
|
||||
|
||||
Debugger_Breakpoint_TextMarkColor=ffff4040
|
||||
|
||||
; Qt Creator Color Tokens - light mode
|
||||
Token_Basic_Black=ff131313
|
||||
Token_Basic_White=fff2f2f2
|
||||
Token_Accent_Default=ff23b26a
|
||||
Token_Accent_Muted=ff1f9b5d
|
||||
Token_Accent_Subtle=ff1a8550
|
||||
Token_Background_Default=ffe3e3e3
|
||||
Token_Background_Muted=ffeeeeee
|
||||
Token_Background_Subtle=fffbfbfb
|
||||
Token_Foreground_Default=ffcdcdcd
|
||||
Token_Foreground_Muted=ffd5d5d5
|
||||
Token_Foreground_Subtle=ffdddddd
|
||||
Token_Text_Default=ff393939
|
||||
Token_Text_Muted=ff7c7c7c
|
||||
Token_Text_Subtle=ffbebebe
|
||||
Token_Stroke_Strong=ff464646
|
||||
Token_Stroke_Muted=ff727272
|
||||
Token_Stroke_Subtle=ffcdcdcd
|
||||
Token_Notification_Alert=ffeb991f
|
||||
Token_Notification_Success=ff23b26a
|
||||
Token_Notification_Neutral=ff0e7887
|
||||
Token_Notification_Danger=ffdc1343
|
||||
|
||||
Welcome_TextColor=ff000000
|
||||
Welcome_ForegroundPrimaryColor=ff404040
|
||||
Welcome_ForegroundSecondaryColor=ff727272
|
||||
Welcome_BackgroundPrimaryColor=ffeaeaea
|
||||
Welcome_BackgroundSecondaryColor=ffefefef
|
||||
Welcome_HoverColor=ffe1e1e1
|
||||
Welcome_AccentColor=ff25709a
|
||||
Welcome_LinkColor=ff104090
|
||||
Welcome_DisabledLinkColor=textDisabled
|
||||
|
||||
Timeline_TextColor=text
|
||||
Timeline_BackgroundColor1=normalBackground
|
||||
Timeline_BackgroundColor2=fff6f6f6
|
||||
|
@@ -1,4 +1,5 @@
|
||||
[General]
|
||||
Includes=dark.figmatokens
|
||||
ThemeName=Design Dark
|
||||
PreferredStyles=
|
||||
DefaultTextEditorColorScheme=creator-dark.xml
|
||||
@@ -414,39 +415,6 @@ Debugger_WatchItem_ValueChanged=ffff6666
|
||||
|
||||
Debugger_Breakpoint_TextMarkColor=ffff4040
|
||||
|
||||
; Qt Creator Color Tokens - dark mode
|
||||
Token_Basic_Black=ff131313
|
||||
Token_Basic_White=fff8f8f8
|
||||
Token_Accent_Default=ff23b26a
|
||||
Token_Accent_Muted=ff1f9b5d
|
||||
Token_Accent_Subtle=ff1a8550
|
||||
Token_Background_Default=ff1f1f1f
|
||||
Token_Background_Muted=ff262626
|
||||
Token_Background_Subtle=ff2e2e2e
|
||||
Token_Foreground_Default=ff5a5a5a
|
||||
Token_Foreground_Muted=ff3e3e3e
|
||||
Token_Foreground_Subtle=ff303030
|
||||
Token_Text_Default=fff8f8f8
|
||||
Token_Text_Muted=ffaeaeae
|
||||
Token_Text_Subtle=ff595959
|
||||
Token_Stroke_Strong=ffeeeeee
|
||||
Token_Stroke_Muted=ff727272
|
||||
Token_Stroke_Subtle=ff3a3a3a
|
||||
Token_Notification_Alert=ffc98014
|
||||
Token_Notification_Success=ff1f9b5d
|
||||
Token_Notification_Neutral=ff016876
|
||||
Token_Notification_Danger=ffb22245
|
||||
|
||||
Welcome_TextColor=text
|
||||
Welcome_ForegroundPrimaryColor=ffa3a3a3
|
||||
Welcome_ForegroundSecondaryColor=ff808080
|
||||
Welcome_BackgroundPrimaryColor=ff242424
|
||||
Welcome_BackgroundSecondaryColor=ff1c1c1c
|
||||
Welcome_HoverColor=ff2b2a2a
|
||||
Welcome_AccentColor=ff3f8ccc
|
||||
Welcome_LinkColor=ff5fafef
|
||||
Welcome_DisabledLinkColor=textDisabled
|
||||
|
||||
Timeline_TextColor=text
|
||||
Timeline_BackgroundColor1=normalBackground
|
||||
Timeline_BackgroundColor2=ff444444
|
||||
|
@@ -1,4 +1,5 @@
|
||||
[General]
|
||||
Includes=dark.figmatokens
|
||||
ThemeName=Flat Dark
|
||||
PreferredStyles=
|
||||
DefaultTextEditorColorScheme=creator-dark.xml
|
||||
@@ -410,39 +411,6 @@ Debugger_WatchItem_ValueChanged=ffff6666
|
||||
|
||||
Debugger_Breakpoint_TextMarkColor=ffff4040
|
||||
|
||||
; Qt Creator Color Tokens - dark mode
|
||||
Token_Basic_Black=ff131313
|
||||
Token_Basic_White=fff8f8f8
|
||||
Token_Accent_Default=ff23b26a
|
||||
Token_Accent_Muted=ff1f9b5d
|
||||
Token_Accent_Subtle=ff1a8550
|
||||
Token_Background_Default=ff1f1f1f
|
||||
Token_Background_Muted=ff262626
|
||||
Token_Background_Subtle=ff2e2e2e
|
||||
Token_Foreground_Default=ff5a5a5a
|
||||
Token_Foreground_Muted=ff3e3e3e
|
||||
Token_Foreground_Subtle=ff303030
|
||||
Token_Text_Default=fff8f8f8
|
||||
Token_Text_Muted=ffaeaeae
|
||||
Token_Text_Subtle=ff595959
|
||||
Token_Stroke_Strong=ffeeeeee
|
||||
Token_Stroke_Muted=ff727272
|
||||
Token_Stroke_Subtle=ff3a3a3a
|
||||
Token_Notification_Alert=ffc98014
|
||||
Token_Notification_Success=ff1f9b5d
|
||||
Token_Notification_Neutral=ff016876
|
||||
Token_Notification_Danger=ffb22245
|
||||
|
||||
Welcome_TextColor=text
|
||||
Welcome_ForegroundPrimaryColor=ff999999
|
||||
Welcome_ForegroundSecondaryColor=ff808080
|
||||
Welcome_BackgroundPrimaryColor=normalBackground
|
||||
Welcome_BackgroundSecondaryColor=ff242628
|
||||
Welcome_HoverColor=ff404243
|
||||
Welcome_AccentColor=ff36c148
|
||||
Welcome_LinkColor=ff5fcf4f
|
||||
Welcome_DisabledLinkColor=textDisabled
|
||||
|
||||
Timeline_TextColor=text
|
||||
Timeline_BackgroundColor1=normalBackground
|
||||
Timeline_BackgroundColor2=ff444444
|
||||
|
@@ -1,4 +1,5 @@
|
||||
[General]
|
||||
Includes=light.figmatokens
|
||||
ThemeName=Flat Light
|
||||
PreferredStyles=
|
||||
|
||||
@@ -407,39 +408,6 @@ Debugger_WatchItem_ValueChanged=ffbf0303
|
||||
|
||||
Debugger_Breakpoint_TextMarkColor=ffff4040
|
||||
|
||||
; Qt Creator Color Tokens - light mode
|
||||
Token_Basic_Black=ff131313
|
||||
Token_Basic_White=fff2f2f2
|
||||
Token_Accent_Default=ff23b26a
|
||||
Token_Accent_Muted=ff1f9b5d
|
||||
Token_Accent_Subtle=ff1a8550
|
||||
Token_Background_Default=ffe3e3e3
|
||||
Token_Background_Muted=ffeeeeee
|
||||
Token_Background_Subtle=fffbfbfb
|
||||
Token_Foreground_Default=ffcdcdcd
|
||||
Token_Foreground_Muted=ffd5d5d5
|
||||
Token_Foreground_Subtle=ffdddddd
|
||||
Token_Text_Default=ff393939
|
||||
Token_Text_Muted=ff7c7c7c
|
||||
Token_Text_Subtle=ffbebebe
|
||||
Token_Stroke_Strong=ff464646
|
||||
Token_Stroke_Muted=ff727272
|
||||
Token_Stroke_Subtle=ffcdcdcd
|
||||
Token_Notification_Alert=ffeb991f
|
||||
Token_Notification_Success=ff23b26a
|
||||
Token_Notification_Neutral=ff0e7887
|
||||
Token_Notification_Danger=ffdc1343
|
||||
|
||||
Welcome_TextColor=ff000000
|
||||
Welcome_ForegroundPrimaryColor=ff232323
|
||||
Welcome_ForegroundSecondaryColor=ff939393
|
||||
Welcome_BackgroundPrimaryColor=fffafafa
|
||||
Welcome_BackgroundSecondaryColor=ffffffff
|
||||
Welcome_HoverColor=ffefefef
|
||||
Welcome_AccentColor=ff45ce55
|
||||
Welcome_LinkColor=ff20a020
|
||||
Welcome_DisabledLinkColor=textDisabled
|
||||
|
||||
Timeline_TextColor=text
|
||||
Timeline_BackgroundColor1=normalBackground
|
||||
Timeline_BackgroundColor2=fff6f6f6
|
||||
|
@@ -1,4 +1,5 @@
|
||||
[General]
|
||||
Includes=light.figmatokens
|
||||
ThemeName=Flat
|
||||
PreferredStyles=
|
||||
|
||||
@@ -405,39 +406,6 @@ Debugger_WatchItem_ValueChanged=ffbf0303
|
||||
|
||||
Debugger_Breakpoint_TextMarkColor=ffff4040
|
||||
|
||||
; Qt Creator Color Tokens - light mode
|
||||
Token_Basic_Black=ff131313
|
||||
Token_Basic_White=fff2f2f2
|
||||
Token_Accent_Default=ff23b26a
|
||||
Token_Accent_Muted=ff1f9b5d
|
||||
Token_Accent_Subtle=ff1a8550
|
||||
Token_Background_Default=ffe3e3e3
|
||||
Token_Background_Muted=ffeeeeee
|
||||
Token_Background_Subtle=fffbfbfb
|
||||
Token_Foreground_Default=ffcdcdcd
|
||||
Token_Foreground_Muted=ffd5d5d5
|
||||
Token_Foreground_Subtle=ffdddddd
|
||||
Token_Text_Default=ff393939
|
||||
Token_Text_Muted=ff7c7c7c
|
||||
Token_Text_Subtle=ffbebebe
|
||||
Token_Stroke_Strong=ff464646
|
||||
Token_Stroke_Muted=ff727272
|
||||
Token_Stroke_Subtle=ffcdcdcd
|
||||
Token_Notification_Alert=ffeb991f
|
||||
Token_Notification_Success=ff23b26a
|
||||
Token_Notification_Neutral=ff0e7887
|
||||
Token_Notification_Danger=ffdc1343
|
||||
|
||||
Welcome_TextColor=ff000000
|
||||
Welcome_ForegroundPrimaryColor=shadowBackground
|
||||
Welcome_ForegroundSecondaryColor=ff939393
|
||||
Welcome_BackgroundPrimaryColor=fffafafa
|
||||
Welcome_BackgroundSecondaryColor=ffffffff
|
||||
Welcome_HoverColor=ffefefef
|
||||
Welcome_AccentColor=ff45ce55
|
||||
Welcome_LinkColor=ff20a020
|
||||
Welcome_DisabledLinkColor=textDisabled
|
||||
|
||||
Timeline_TextColor=text
|
||||
Timeline_BackgroundColor1=normalBackground
|
||||
Timeline_BackgroundColor2=fff6f6f6
|
||||
|
31
share/qtcreator/themes/light.figmatokens
Normal 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
|
@@ -121,6 +121,9 @@ static std::string stripPointerType(const std::string &typeNameIn)
|
||||
std::string typeName = typeNameIn;
|
||||
if (typeName.back() == '*') {
|
||||
typeName.pop_back();
|
||||
trimBack(typeName);
|
||||
if (endsWith(typeName, "const"))
|
||||
typeName = typeName.erase(typeName.size() - 5, 5);
|
||||
} else {
|
||||
const auto arrayPosition = typeName.find_first_of('[');
|
||||
if (arrayPosition != std::string::npos
|
||||
@@ -296,23 +299,7 @@ int PyType::code() const
|
||||
return std::nullopt;
|
||||
};
|
||||
|
||||
if (!resolve())
|
||||
return parseTypeName(name()).value_or(TypeCodeUnresolvable);
|
||||
|
||||
if (m_tag < 0) {
|
||||
if (const std::optional<TypeCodes> typeCode = parseTypeName(name()))
|
||||
return *typeCode;
|
||||
|
||||
IDebugSymbolGroup2 *sg = 0;
|
||||
if (FAILED(ExtensionCommandContext::instance()->symbols()->CreateSymbolGroup2(&sg)))
|
||||
return TypeCodeStruct;
|
||||
|
||||
const std::string helperValueName = SymbolGroupValue::pointedToSymbolName(0, name(true));
|
||||
ULONG index = DEBUG_ANY_ID;
|
||||
if (SUCCEEDED(sg->AddSymbol(helperValueName.c_str(), &index)))
|
||||
m_tag = PyValue(index, sg).tag();
|
||||
sg->Release();
|
||||
}
|
||||
if (m_tag >= 0) {
|
||||
switch (m_tag) {
|
||||
case SymTagUDT: return TypeCodeStruct;
|
||||
case SymTagEnum: return TypeCodeEnum;
|
||||
@@ -323,8 +310,8 @@ int PyType::code() const
|
||||
case SymTagBaseType: return isIntegralType(name()) ? TypeCodeIntegral : TypeCodeFloat;
|
||||
default: break;
|
||||
}
|
||||
|
||||
return TypeCodeStruct;
|
||||
}
|
||||
return parseTypeName(name()).value_or(TypeCodeStruct);
|
||||
}
|
||||
|
||||
PyType PyType::target() const
|
||||
@@ -533,6 +520,7 @@ PY_FUNC_RET_OBJECT_LIST(fields, PY_OBJ_NAME)
|
||||
PY_FUNC_RET_STD_STRING(module, PY_OBJ_NAME)
|
||||
PY_FUNC(moduleId, PY_OBJ_NAME, "K")
|
||||
PY_FUNC(arrayElements, PY_OBJ_NAME, "k")
|
||||
PY_FUNC_RET_BOOL(resolved, PY_OBJ_NAME)
|
||||
PY_FUNC_DECL(templateArguments, PY_OBJ_NAME)
|
||||
{
|
||||
PY_IMPL_GUARD;
|
||||
@@ -568,6 +556,8 @@ static PyMethodDef typeMethods[] = {
|
||||
"Returns the number of elements in an array or 0 for non array types"},
|
||||
{"templateArguments", PyCFunction(templateArguments), METH_NOARGS,
|
||||
"Returns all template arguments."},
|
||||
{"resolved", PyCFunction(resolved), METH_NOARGS,
|
||||
"Returns whether the type is resolved"},
|
||||
|
||||
{NULL} /* Sentinel */
|
||||
};
|
||||
|
@@ -29,6 +29,7 @@ public:
|
||||
std::string module() const;
|
||||
ULONG64 moduleId() const;
|
||||
int arrayElements() const;
|
||||
bool resolved() const { return m_resolved.value_or(false); }
|
||||
|
||||
struct TemplateArgument
|
||||
{
|
||||
|
@@ -933,7 +933,17 @@ QColor StyleHelper::ensureReadableOn(const QColor &background, const QColor &des
|
||||
return foreground;
|
||||
}
|
||||
|
||||
static QStringList brandFontFamilies()
|
||||
static const QStringList &applicationFontFamilies()
|
||||
{
|
||||
const static QStringList families = [] {
|
||||
const QLatin1String familyName("Inter");
|
||||
// Font is either installed in the system, or was loaded from share/qtcreator/fonts/
|
||||
return QFontDatabase::hasFamily(familyName) ? QStringList(familyName) : QStringList();
|
||||
}();
|
||||
return families;
|
||||
}
|
||||
|
||||
static const QStringList &brandFontFamilies()
|
||||
{
|
||||
const static QStringList families = []{
|
||||
const int id = QFontDatabase::addApplicationFont(":/studiofonts/TitilliumWeb-Regular.ttf");
|
||||
@@ -959,10 +969,14 @@ static const UiFontMetrics& uiFontMetrics(StyleHelper::UiElement element)
|
||||
{StyleHelper::UiElementH5, {14, 16, QFont::DemiBold}},
|
||||
{StyleHelper::UiElementH6, {12, 14, QFont::DemiBold}},
|
||||
{StyleHelper::UiElementH6Capital, {12, 14, QFont::DemiBold}},
|
||||
{StyleHelper::UiElementBody1, {14, 20, QFont::Light}},
|
||||
{StyleHelper::UiElementBody2, {12, 20, QFont::Light}},
|
||||
{StyleHelper::UiElementButtonMedium, {12, 16, QFont::Bold}},
|
||||
{StyleHelper::UiElementButtonSmall, {10, 12, QFont::Bold}},
|
||||
{StyleHelper::UiElementCaptionStrong, {10, 12, QFont::DemiBold}},
|
||||
{StyleHelper::UiElementCaption, {10, 12, QFont::Normal}},
|
||||
{StyleHelper::UIElementIconStandard, {12, 16, QFont::Normal}},
|
||||
{StyleHelper::UIElementIconActive, {12, 16, QFont::DemiBold}},
|
||||
{StyleHelper::UiElementIconStandard, {12, 16, QFont::Medium}},
|
||||
{StyleHelper::UiElementIconActive, {12, 16, QFont::DemiBold}},
|
||||
};
|
||||
QTC_ASSERT(metrics.count(element) > 0, return metrics.at(StyleHelper::UiElementCaptionStrong));
|
||||
return metrics.at(element);
|
||||
@@ -983,8 +997,10 @@ QFont StyleHelper::uiFont(UiElement element)
|
||||
case UiElementH3:
|
||||
case UiElementH6Capital:
|
||||
font.setCapitalization(QFont::AllUppercase);
|
||||
break;
|
||||
[[fallthrough]];
|
||||
default:
|
||||
if (!applicationFontFamilies().isEmpty())
|
||||
font.setFamilies(applicationFontFamilies());
|
||||
break;
|
||||
}
|
||||
|
||||
|
@@ -42,15 +42,15 @@ constexpr char C_TOOLBAR_ACTIONWIDGET[] = "toolbar_actionWidget";
|
||||
constexpr char C_QT_SCALE_FACTOR_ROUNDING_POLICY[] = "QT_SCALE_FACTOR_ROUNDING_POLICY";
|
||||
|
||||
namespace SpacingTokens {
|
||||
constexpr int VPaddingXXS = 4; // Top and bottom padding within the component
|
||||
constexpr int HPaddingXXS = 4; // Left and right padding within the component
|
||||
constexpr int VGapXXS = 4; // Vertical Space between TEXT LINE within the Component
|
||||
constexpr int HGapXXS = 4; // Horizontal Space between elements within the Component
|
||||
constexpr int VPaddingXxs = 4; // Top and bottom padding within the component
|
||||
constexpr int HPaddingXxs = 4; // Left and right padding within the component
|
||||
constexpr int VGapXxs = 4; // Vertical Space between TEXT LINE within the Component
|
||||
constexpr int HGapXxs = 4; // Horizontal Space between elements within the Component
|
||||
|
||||
constexpr int VPaddingXS = 8;
|
||||
constexpr int HPaddingXS = 8;
|
||||
constexpr int VGapXS = 4;
|
||||
constexpr int HGapXS = 8;
|
||||
constexpr int VPaddingXs = 8;
|
||||
constexpr int HPaddingXs = 8;
|
||||
constexpr int VGapXs = 4;
|
||||
constexpr int HGapXs = 8;
|
||||
|
||||
constexpr int VPaddingS = 8;
|
||||
constexpr int HPaddingS = 16;
|
||||
@@ -62,10 +62,15 @@ namespace SpacingTokens {
|
||||
constexpr int VGapM = 4;
|
||||
constexpr int HGapM = 16;
|
||||
|
||||
constexpr int VPaddingL = 12;
|
||||
constexpr int VPaddingL = 16;
|
||||
constexpr int HPaddingL = 24;
|
||||
constexpr int VGapL = 8;
|
||||
constexpr int HGapL = 16;
|
||||
|
||||
constexpr int ExPaddingGapS = 2;
|
||||
constexpr int ExPaddingGapM = 6;
|
||||
constexpr int ExPaddingGapL = 12;
|
||||
constexpr int ExVPaddingGapXl = 24;
|
||||
}
|
||||
|
||||
enum ToolbarStyle {
|
||||
@@ -82,10 +87,14 @@ enum UiElement {
|
||||
UiElementH5,
|
||||
UiElementH6,
|
||||
UiElementH6Capital,
|
||||
UiElementBody1,
|
||||
UiElementBody2,
|
||||
UiElementButtonMedium,
|
||||
UiElementButtonSmall,
|
||||
UiElementCaptionStrong,
|
||||
UiElementCaption,
|
||||
UIElementIconStandard,
|
||||
UIElementIconActive,
|
||||
UiElementIconStandard,
|
||||
UiElementIconActive,
|
||||
};
|
||||
|
||||
// Height of the project explorer navigation bar
|
||||
|
@@ -245,7 +245,6 @@ void Theme::readSettingsInternal(QSettings &settings)
|
||||
QMetaEnum e = m.enumerator(m.indexOfEnumerator("Flag"));
|
||||
for (int i = 0, total = e.keyCount(); i < total; ++i) {
|
||||
const QString key = QLatin1String(e.key(i));
|
||||
QTC_ASSERT(settings.contains(key), return );
|
||||
d->flags[i] = settings.value(key).toBool();
|
||||
}
|
||||
settings.endGroup();
|
||||
|
@@ -247,18 +247,6 @@ public:
|
||||
Token_Notification_Neutral,
|
||||
Token_Notification_Danger,
|
||||
|
||||
/* Welcome Plugin */
|
||||
|
||||
Welcome_TextColor,
|
||||
Welcome_ForegroundPrimaryColor,
|
||||
Welcome_ForegroundSecondaryColor,
|
||||
Welcome_BackgroundPrimaryColor,
|
||||
Welcome_BackgroundSecondaryColor,
|
||||
Welcome_HoverColor,
|
||||
Welcome_AccentColor,
|
||||
Welcome_LinkColor,
|
||||
Welcome_DisabledLinkColor,
|
||||
|
||||
/* Timeline Library */
|
||||
Timeline_TextColor,
|
||||
Timeline_BackgroundColor1,
|
||||
|
@@ -819,15 +819,13 @@ void AndroidRunnerWorker::removeForwardPort(const QString &port)
|
||||
|
||||
void AndroidRunnerWorker::onProcessIdChanged(PidUserPair pidUser)
|
||||
{
|
||||
qint64 pid = pidUser.first;
|
||||
qint64 user = pidUser.second;
|
||||
// Don't write to m_psProc from a different thread
|
||||
QTC_ASSERT(QThread::currentThread() == thread(), return);
|
||||
qCDebug(androidRunWorkerLog) << "Process ID changed from:" << m_processPID
|
||||
<< "to:" << pid;
|
||||
m_processPID = pid;
|
||||
m_processUser = user;
|
||||
if (pid == -1) {
|
||||
<< "to:" << pidUser.first;
|
||||
m_processPID = pidUser.first;
|
||||
m_processUser = pidUser.second;
|
||||
if (m_processPID == -1) {
|
||||
emit remoteProcessFinished(QLatin1String("\n\n") + Tr::tr("\"%1\" died.")
|
||||
.arg(m_packageName));
|
||||
// App died/killed. Reset log, monitor, jdb & gdbserver/lldb-server processes.
|
||||
@@ -852,7 +850,10 @@ void AndroidRunnerWorker::onProcessIdChanged(PidUserPair pidUser)
|
||||
QTC_ASSERT(m_psIsAlive, return);
|
||||
m_psIsAlive->setObjectName("IsAliveProcess");
|
||||
m_psIsAlive->setProcessChannelMode(QProcess::MergedChannels);
|
||||
connect(m_psIsAlive.get(), &Process::done, this, [this] { onProcessIdChanged({-1, -1}); });
|
||||
connect(m_psIsAlive.get(), &Process::done, this, [this] {
|
||||
m_psIsAlive.release()->deleteLater();
|
||||
onProcessIdChanged({-1, -1});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -13,14 +13,15 @@
|
||||
|
||||
#include <projectexplorer/projectexplorerconstants.h>
|
||||
|
||||
#include <utils/async.h>
|
||||
#include <utils/detailswidget.h>
|
||||
#include <utils/hostosinfo.h>
|
||||
#include <utils/infolabel.h>
|
||||
#include <utils/layoutbuilder.h>
|
||||
#include <utils/pathchooser.h>
|
||||
#include <utils/progressindicator.h>
|
||||
#include <utils/qtcprocess.h>
|
||||
#include <utils/qtcassert.h>
|
||||
#include <utils/qtcprocess.h>
|
||||
#include <utils/utilsicons.h>
|
||||
|
||||
#include <QCheckBox>
|
||||
@@ -203,6 +204,56 @@ enum OpenSslValidation {
|
||||
OpenSslCmakeListsPathExists
|
||||
};
|
||||
|
||||
static expected_str<void> testJavaC(const FilePath &jdkPath)
|
||||
{
|
||||
if (!jdkPath.isReadableDir())
|
||||
return make_unexpected(Tr::tr("The selected path does not exist or is not readable."));
|
||||
|
||||
const QString javacCommand("javac");
|
||||
const QString versionParameter("-version");
|
||||
constexpr int requiredMajorVersion = 17;
|
||||
const FilePath bin = jdkPath / "bin" / (javacCommand + QTC_HOST_EXE_SUFFIX);
|
||||
|
||||
if (!bin.isExecutableFile())
|
||||
return make_unexpected(
|
||||
Tr::tr("Could not find \"%1\" in the selected path.")
|
||||
.arg(bin.toUserOutput()));
|
||||
|
||||
QVersionNumber jdkVersion;
|
||||
|
||||
Process javacProcess;
|
||||
const CommandLine cmd(bin, {versionParameter});
|
||||
javacProcess.setProcessChannelMode(QProcess::ProcessChannelMode::MergedChannels);
|
||||
javacProcess.setCommand(cmd);
|
||||
javacProcess.runBlocking();
|
||||
|
||||
const QString stdOut = javacProcess.stdOut().trimmed();
|
||||
|
||||
if (javacProcess.exitCode() != 0)
|
||||
return make_unexpected(
|
||||
Tr::tr("The selected path does not contain a valid JDK. (%1 failed: %2)")
|
||||
.arg(cmd.toUserOutput())
|
||||
.arg(stdOut));
|
||||
|
||||
// We expect "javac <version>" where <version> is "major.minor.patch"
|
||||
const QString outputPrefix = javacCommand + " ";
|
||||
if (!stdOut.startsWith(outputPrefix))
|
||||
return make_unexpected(Tr::tr("Unexpected output from \"%1\": %2")
|
||||
.arg(cmd.toUserOutput())
|
||||
.arg(stdOut));
|
||||
|
||||
jdkVersion = QVersionNumber::fromString(stdOut.mid(outputPrefix.length()).split('\n').first());
|
||||
|
||||
if (jdkVersion.isNull() || jdkVersion.majorVersion() != requiredMajorVersion) {
|
||||
return make_unexpected(Tr::tr("Unsupported JDK version (needs to be %1): %2 (parsed: %3)")
|
||||
.arg(requiredMajorVersion)
|
||||
.arg(stdOut)
|
||||
.arg(jdkVersion.toString()));
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
AndroidSettingsWidget::AndroidSettingsWidget()
|
||||
{
|
||||
setWindowTitle(Tr::tr("Android Configuration"));
|
||||
@@ -307,6 +358,15 @@ AndroidSettingsWidget::AndroidSettingsWidget()
|
||||
Tr::tr("OpenSSL settings have errors."),
|
||||
openSslDetailsWidget);
|
||||
|
||||
m_openJdkLocationPathChooser->setValidationFunction([](const QString &s) {
|
||||
return Utils::asyncRun([s]() -> expected_str<QString> {
|
||||
expected_str<void> test = testJavaC(FilePath::fromUserInput(s));
|
||||
if (!test)
|
||||
return make_unexpected(test.error());
|
||||
return s;
|
||||
});
|
||||
});
|
||||
|
||||
connect(m_openJdkLocationPathChooser, &PathChooser::rawPathChanged,
|
||||
this, &AndroidSettingsWidget::validateJdk);
|
||||
if (androidConfig().openJDKLocation().isEmpty())
|
||||
@@ -533,10 +593,9 @@ bool AndroidSettingsWidget::isDefaultNdkSelected() const
|
||||
void AndroidSettingsWidget::validateJdk()
|
||||
{
|
||||
androidConfig().setOpenJDKLocation(m_openJdkLocationPathChooser->filePath());
|
||||
bool jdkPathExists = androidConfig().openJDKLocation().exists();
|
||||
const FilePath bin = androidConfig().openJDKLocation()
|
||||
.pathAppended("bin/javac" QTC_HOST_EXE_SUFFIX);
|
||||
m_androidSummary->setPointValid(JavaPathExistsAndWritableRow, jdkPathExists && bin.exists());
|
||||
expected_str<void> test = testJavaC(androidConfig().openJDKLocation());
|
||||
|
||||
m_androidSummary->setPointValid(JavaPathExistsAndWritableRow, test.has_value());
|
||||
|
||||
updateUI();
|
||||
|
||||
|
@@ -79,14 +79,16 @@ void CatchCodeParser::handleIdentifier()
|
||||
|| unprefixed == "TEMPLATE_PRODUCT_TEST_CASE_SIG") {
|
||||
handleParameterizedTestCase(false);
|
||||
} else if (unprefixed == "TEST_CASE_METHOD") {
|
||||
handleFixtureOrRegisteredTestCase(true);
|
||||
handleFixtureOrRegisteredTestCase(/*fixture=*/true, /*scenario=*/false);
|
||||
} else if (unprefixed == "SCENARIO_METHOD") {
|
||||
handleFixtureOrRegisteredTestCase(/*fixture=*/true, /*scenario=*/true);
|
||||
} else if (unprefixed == "TEMPLATE_TEST_CASE_METHOD_SIG"
|
||||
|| unprefixed == "TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG"
|
||||
|| unprefixed == "TEMPLATE_TEST_CASE_METHOD"
|
||||
|| unprefixed == "TEMPLATE_LIST_TEST_CASE_METHOD") {
|
||||
handleParameterizedTestCase(true);
|
||||
} else if (unprefixed == "METHOD_AS_TEST_CASE" || unprefixed == "REGISTER_TEST_CASE") {
|
||||
handleFixtureOrRegisteredTestCase(false);
|
||||
handleFixtureOrRegisteredTestCase(/*fixture=*/false, /*scenario=*/false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,7 +126,7 @@ void CatchCodeParser::handleParameterizedTestCase(bool isFixture)
|
||||
if (!skipCommentsUntil(T_LPAREN))
|
||||
return;
|
||||
|
||||
if (isFixture && !skipFixtureParameter())
|
||||
if (isFixture && !skipParameter())
|
||||
return;
|
||||
|
||||
CatchTestCodeLocationAndType locationAndType
|
||||
@@ -154,18 +156,13 @@ void CatchCodeParser::handleParameterizedTestCase(bool isFixture)
|
||||
m_testCases.append(locationAndType);
|
||||
}
|
||||
|
||||
void CatchCodeParser::handleFixtureOrRegisteredTestCase(bool isFixture)
|
||||
void CatchCodeParser::handleFixtureOrRegisteredTestCase(bool isFixture, bool isScenario)
|
||||
{
|
||||
if (!skipCommentsUntil(T_LPAREN))
|
||||
return;
|
||||
|
||||
if (isFixture) {
|
||||
if (!skipFixtureParameter())
|
||||
if (!skipParameter())
|
||||
return;
|
||||
} else {
|
||||
if (!skipFunctionParameter())
|
||||
return;
|
||||
}
|
||||
|
||||
CatchTestCodeLocationAndType locationAndType
|
||||
= locationAndTypeFromToken(m_tokens.at(m_currentIndex));
|
||||
@@ -183,6 +180,9 @@ void CatchCodeParser::handleFixtureOrRegisteredTestCase(bool isFixture)
|
||||
if (stoppedAt != T_RPAREN)
|
||||
return;
|
||||
|
||||
if (isScenario)
|
||||
testCaseName.prepend("Scenario: "); // use a flag?
|
||||
|
||||
locationAndType.m_name = testCaseName;
|
||||
locationAndType.tags = parseTags(tagsString);
|
||||
if (isFixture)
|
||||
@@ -245,19 +245,12 @@ Kind CatchCodeParser::skipUntilCorrespondingRParen()
|
||||
return T_ERROR;
|
||||
}
|
||||
|
||||
bool CatchCodeParser::skipFixtureParameter()
|
||||
{
|
||||
if (!skipCommentsUntil(T_IDENTIFIER))
|
||||
return false;
|
||||
return skipCommentsUntil(T_COMMA);
|
||||
}
|
||||
|
||||
bool CatchCodeParser::skipFunctionParameter()
|
||||
bool CatchCodeParser::skipParameter()
|
||||
{
|
||||
if (!skipCommentsUntil(T_IDENTIFIER))
|
||||
return false;
|
||||
if (skipCommentsUntil(T_COLON_COLON))
|
||||
return skipFunctionParameter();
|
||||
return skipParameter();
|
||||
|
||||
return skipCommentsUntil(T_COMMA);
|
||||
}
|
||||
|
@@ -22,13 +22,12 @@ private:
|
||||
void handleIdentifier();
|
||||
void handleTestCase(bool isScenario);
|
||||
void handleParameterizedTestCase(bool isFixture);
|
||||
void handleFixtureOrRegisteredTestCase(bool isFixture);
|
||||
void handleFixtureOrRegisteredTestCase(bool isFixture, bool isScenario);
|
||||
|
||||
QString getStringLiteral(CPlusPlus::Kind &stoppedAtKind);
|
||||
bool skipCommentsUntil(CPlusPlus::Kind nextExpectedKind); // moves currentIndex if succeeds
|
||||
CPlusPlus::Kind skipUntilCorrespondingRParen(); // moves currentIndex
|
||||
bool skipFixtureParameter();
|
||||
bool skipFunctionParameter();
|
||||
bool skipParameter();
|
||||
|
||||
const QByteArray &m_source;
|
||||
const CPlusPlus::LanguageFeatures &m_features;
|
||||
|
@@ -29,7 +29,9 @@ static bool isCatchTestCaseMacro(const QString ¯oName)
|
||||
QStringLiteral("TEMPLATE_TEST_CASE_SIG"), QStringLiteral("TEMPLATE_PRODUCT_TEST_CASE_SIG"),
|
||||
QStringLiteral("TEST_CASE_METHOD"), QStringLiteral("TEMPLATE_TEST_CASE_METHOD"),
|
||||
QStringLiteral("TEMPLATE_PRODUCT_TEST_CASE_METHOD"),
|
||||
QStringLiteral("TEST_CASE_METHOD"), QStringLiteral("TEMPLATE_TEST_CASE_METHOD_SIG"),
|
||||
QStringLiteral("TEST_CASE_METHOD"),
|
||||
QStringLiteral("SCENARIO_METHOD"),
|
||||
QStringLiteral("TEMPLATE_TEST_CASE_METHOD_SIG"),
|
||||
QStringLiteral("TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG"),
|
||||
QStringLiteral("TEMPLATE_TEST_CASE_METHOD"),
|
||||
QStringLiteral("TEMPLATE_LIST_TEST_CASE_METHOD"),
|
||||
@@ -105,7 +107,7 @@ bool CatchTestParser::processDocument(QPromise<TestParseResultPtr> &promise,
|
||||
|
||||
if (!hasCatchNames(doc)) {
|
||||
static const QRegularExpression regex("\\b(CATCH_)?"
|
||||
"(SCENARIO|(TEMPLATE_(PRODUCT_)?)?TEST_CASE(_METHOD)?|"
|
||||
"(SCENARIO(_METHOD)?|(TEMPLATE_(PRODUCT_)?)?TEST_CASE(_METHOD)?|"
|
||||
"TEMPLATE_TEST_CASE(_METHOD)?_SIG|"
|
||||
"TEMPLATE_PRODUCT_TEST_CASE(_METHOD)?_SIG|"
|
||||
"TEMPLATE_LIST_TEST_CASE_METHOD|METHOD_AS_TEST_CASE|"
|
||||
|
@@ -55,6 +55,7 @@ TestCodeParser::TestCodeParser()
|
||||
m_qmlEditorRev.remove(filePath);
|
||||
});
|
||||
m_reparseTimer.setSingleShot(true);
|
||||
m_reparseTimer.setInterval(1000);
|
||||
connect(&m_reparseTimer, &QTimer::timeout, this, &TestCodeParser::parsePostponedFiles);
|
||||
connect(&m_taskTreeRunner, &TaskTreeRunner::aboutToStart, this, [this](TaskTree *taskTree) {
|
||||
if (m_withTaskProgress) {
|
||||
@@ -238,27 +239,10 @@ bool TestCodeParser::postponed(const QSet<FilePath> &filePaths)
|
||||
if (filePaths.size() == 1) {
|
||||
if (m_reparseTimerTimedOut)
|
||||
return false;
|
||||
const FilePath filePath = *filePaths.begin();
|
||||
switch (m_postponedFiles.size()) {
|
||||
case 0:
|
||||
m_postponedFiles.insert(filePath);
|
||||
m_reparseTimer.setInterval(1000);
|
||||
|
||||
m_postponedFiles.insert(*filePaths.begin());
|
||||
m_reparseTimer.start();
|
||||
return true;
|
||||
case 1:
|
||||
if (m_postponedFiles.contains(filePath)) {
|
||||
m_reparseTimer.start();
|
||||
return true;
|
||||
}
|
||||
Q_FALLTHROUGH();
|
||||
default:
|
||||
m_postponedFiles.insert(filePath);
|
||||
m_reparseTimer.stop();
|
||||
m_reparseTimer.setInterval(0);
|
||||
m_reparseTimerTimedOut = false;
|
||||
m_reparseTimer.start();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
case PartialParse:
|
||||
@@ -377,7 +361,7 @@ void TestCodeParser::scanForTests(const QSet<FilePath> &filePaths,
|
||||
return true;
|
||||
return cppSnapshot.contains(fn);
|
||||
});
|
||||
m_withTaskProgress = filteredFiles.size() > 5;
|
||||
m_withTaskProgress = isFullParse || filteredFiles.size() > 20;
|
||||
|
||||
qCDebug(LOG) << "Starting scan of" << filteredFiles.size() << "(" << files.size() << ")"
|
||||
<< "files with" << codeParsers.size() << "parsers";
|
||||
|
@@ -2,18 +2,18 @@
|
||||
<qresource prefix="/axivion">
|
||||
<file>images/axivion.png</file>
|
||||
<file>images/axivion@2x.png</file>
|
||||
<file>images/button-av.png</file>
|
||||
<file>images/button-av@2x.png</file>
|
||||
<file>images/button-cl.png</file>
|
||||
<file>images/button-cl@2x.png</file>
|
||||
<file>images/button-cy.png</file>
|
||||
<file>images/button-cy@2x.png</file>
|
||||
<file>images/button-de.png</file>
|
||||
<file>images/button-de@2x.png</file>
|
||||
<file>images/button-mv.png</file>
|
||||
<file>images/button-mv@2x.png</file>
|
||||
<file>images/button-sv.png</file>
|
||||
<file>images/button-sv@2x.png</file>
|
||||
<file>images/button-AV.png</file>
|
||||
<file>images/button-AV@2x.png</file>
|
||||
<file>images/button-CL.png</file>
|
||||
<file>images/button-CL@2x.png</file>
|
||||
<file>images/button-CY.png</file>
|
||||
<file>images/button-CY@2x.png</file>
|
||||
<file>images/button-DE.png</file>
|
||||
<file>images/button-DE@2x.png</file>
|
||||
<file>images/button-MV.png</file>
|
||||
<file>images/button-MV@2x.png</file>
|
||||
<file>images/button-SV.png</file>
|
||||
<file>images/button-SV@2x.png</file>
|
||||
<file>images/sortAsc.png</file>
|
||||
<file>images/sortAsc@2x.png</file>
|
||||
<file>images/sortDesc.png</file>
|
||||
|
@@ -334,6 +334,8 @@ IssuesWidget::IssuesWidget(QWidget *parent)
|
||||
connect(m_pathGlobFilter, &QLineEdit::textEdited, this, &IssuesWidget::onSearchParameterChanged);
|
||||
|
||||
m_issuesView = new BaseTreeView(this);
|
||||
m_issuesView->setFrameShape(QFrame::StyledPanel); // Bring back Qt default
|
||||
m_issuesView->setFrameShadow(QFrame::Sunken); // Bring back Qt default
|
||||
m_headerView = new IssueHeaderView(this);
|
||||
connect(m_headerView, &IssueHeaderView::sortTriggered,
|
||||
this, &IssuesWidget::onSearchParameterChanged);
|
||||
@@ -562,7 +564,7 @@ void IssuesWidget::updateBasicProjectInfo(std::optional<Dto::ProjectInfoDto> inf
|
||||
int buttonId = 0;
|
||||
for (const Dto::IssueKindInfoDto &kind : issueKinds) {
|
||||
auto button = new QToolButton(this);
|
||||
button->setIcon(iconForIssue(kind.prefix));
|
||||
button->setIcon(iconForIssue(kind.getOptionalPrefixEnum()));
|
||||
button->setToolTip(kind.nicePluralName);
|
||||
button->setCheckable(true);
|
||||
connect(button, &QToolButton::clicked, this, [this, prefix = kind.prefix]{
|
||||
|
@@ -63,18 +63,21 @@ using namespace Utils;
|
||||
|
||||
namespace Axivion::Internal {
|
||||
|
||||
QIcon iconForIssue(const QString &prefix)
|
||||
QIcon iconForIssue(const std::optional<Dto::IssueKind> &issueKind)
|
||||
{
|
||||
static QHash<QString, QIcon> prefixToIcon;
|
||||
auto it = prefixToIcon.find(prefix);
|
||||
if (!issueKind)
|
||||
return {};
|
||||
|
||||
if (it == prefixToIcon.end()) {
|
||||
Icon icon({{FilePath::fromString(":/axivion/images/button-" + prefix.toLower() + ".png"),
|
||||
Theme::PaletteButtonText}},
|
||||
Icon::Tint);
|
||||
it = prefixToIcon.insert(prefix, icon.icon());
|
||||
}
|
||||
return it.value();
|
||||
static QHash<Dto::IssueKind, QIcon> prefixToIcon;
|
||||
|
||||
auto it = prefixToIcon.constFind(*issueKind);
|
||||
if (it != prefixToIcon.constEnd())
|
||||
return *it;
|
||||
|
||||
const QLatin1String prefix = Dto::IssueKindMeta::enumToStr(*issueKind);
|
||||
const Icon icon({{FilePath::fromString(":/axivion/images/button-" + prefix + ".png"),
|
||||
Theme::PaletteButtonText}}, Icon::Tint);
|
||||
return prefixToIcon.insert(*issueKind, icon.icon()).value();
|
||||
}
|
||||
|
||||
QString anyToSimpleString(const Dto::Any &any)
|
||||
@@ -122,11 +125,30 @@ static QString credentialKey()
|
||||
QString escaped = string;
|
||||
return escaped.replace('\\', "\\\\").replace('@', "\\@");
|
||||
};
|
||||
return escape(settings().server.dashboard) + '@' + escape(settings().server.username);
|
||||
return escape(settings().server.username) + '@' + escape(settings().server.dashboard);
|
||||
}
|
||||
|
||||
static DashboardInfo toDashboardInfo(const QUrl &source, const Dto::DashboardInfoDto &infoDto)
|
||||
template <typename DtoType>
|
||||
struct GetDtoStorage
|
||||
{
|
||||
QUrl url;
|
||||
std::optional<QByteArray> credential;
|
||||
std::optional<DtoType> dtoData;
|
||||
};
|
||||
|
||||
template <typename DtoType>
|
||||
struct PostDtoStorage
|
||||
{
|
||||
QUrl url;
|
||||
std::optional<QByteArray> credential;
|
||||
QByteArray csrfToken;
|
||||
QByteArray writeData;
|
||||
std::optional<DtoType> dtoData;
|
||||
};
|
||||
|
||||
static DashboardInfo toDashboardInfo(const GetDtoStorage<Dto::DashboardInfoDto> &dashboardStorage)
|
||||
{
|
||||
const Dto::DashboardInfoDto &infoDto = *dashboardStorage.dtoData;
|
||||
const QVersionNumber versionNumber = infoDto.dashboardVersionNumber
|
||||
? QVersionNumber::fromString(*infoDto.dashboardVersionNumber) : QVersionNumber();
|
||||
|
||||
@@ -139,7 +161,7 @@ static DashboardInfo toDashboardInfo(const QUrl &source, const Dto::DashboardInf
|
||||
projectUrls.insert(project.name, project.url);
|
||||
}
|
||||
}
|
||||
return {source, versionNumber, projects, projectUrls, infoDto.checkCredentialsUrl};
|
||||
return {dashboardStorage.url, versionNumber, projects, projectUrls, infoDto.checkCredentialsUrl};
|
||||
}
|
||||
|
||||
QString IssueListSearch::toQuery() const
|
||||
@@ -226,7 +248,7 @@ public:
|
||||
const QString markText = issue.description;
|
||||
const QString id = issue.kind + QString::number(issue.id.value_or(-1));
|
||||
setToolTip(id + '\n' + markText);
|
||||
setIcon(iconForIssue(issue.kind));
|
||||
setIcon(iconForIssue(issue.getOptionalKindEnum()));
|
||||
if (color)
|
||||
setColor(*color);
|
||||
setPriority(TextMark::NormalPriority);
|
||||
@@ -323,18 +345,26 @@ void AxivionPluginPrivate::onStartupProjectChanged(Project *project)
|
||||
|
||||
static QUrl urlForProject(const QString &projectName)
|
||||
{
|
||||
return QUrl(settings().server.dashboard).resolved(QString("api/projects/")).resolved(projectName);
|
||||
if (!dd->m_dashboardInfo)
|
||||
return {};
|
||||
return dd->m_dashboardInfo->source.resolved(QString("api/projects/")).resolved(projectName);
|
||||
}
|
||||
|
||||
static constexpr int httpStatusCodeOk = 200;
|
||||
constexpr char s_htmlContentType[] = "text/html";
|
||||
constexpr char s_jsonContentType[] = "application/json";
|
||||
|
||||
static bool isServerAccessEstablished()
|
||||
{
|
||||
return dd->m_serverAccess == ServerAccess::NoAuthorization
|
||||
|| (dd->m_serverAccess == ServerAccess::WithAuthorization && dd->m_apiToken);
|
||||
}
|
||||
|
||||
static Group fetchHtmlRecipe(const QUrl &url, const std::function<void(const QByteArray &)> &handler)
|
||||
{
|
||||
// TODO: Refactor so that it's a common code with fetchDataRecipe().
|
||||
const auto onQuerySetup = [url](NetworkQuery &query) {
|
||||
if (dd->m_serverAccess == ServerAccess::Unknown)
|
||||
if (!isServerAccessEstablished())
|
||||
return SetupResult::StopWithError; // TODO: start authorizationRecipe()?
|
||||
|
||||
QNetworkRequest request(url);
|
||||
@@ -367,24 +397,6 @@ static Group fetchHtmlRecipe(const QUrl &url, const std::function<void(const QBy
|
||||
return {NetworkQueryTask(onQuerySetup, onQueryDone)};
|
||||
}
|
||||
|
||||
template <typename DtoType>
|
||||
struct GetDtoStorage
|
||||
{
|
||||
QUrl url;
|
||||
std::optional<QByteArray> credential;
|
||||
std::optional<DtoType> dtoData;
|
||||
};
|
||||
|
||||
template <typename DtoType>
|
||||
struct PostDtoStorage
|
||||
{
|
||||
QUrl url;
|
||||
std::optional<QByteArray> credential;
|
||||
QByteArray csrfToken;
|
||||
QByteArray writeData;
|
||||
std::optional<DtoType> dtoData;
|
||||
};
|
||||
|
||||
template <typename DtoType, template <typename> typename DtoStorageType>
|
||||
static Group dtoRecipe(const Storage<DtoStorageType<DtoType>> &dtoStorage)
|
||||
{
|
||||
@@ -410,7 +422,8 @@ static Group dtoRecipe(const Storage<DtoStorageType<DtoType>> &dtoStorage)
|
||||
query.setNetworkAccessManager(&dd->m_networkAccessManager);
|
||||
};
|
||||
|
||||
const auto onNetworkQueryDone = [storage](const NetworkQuery &query, DoneWith doneWith) {
|
||||
const auto onNetworkQueryDone = [storage, dtoStorage](const NetworkQuery &query,
|
||||
DoneWith doneWith) {
|
||||
QNetworkReply *reply = query.reply();
|
||||
const QNetworkReply::NetworkError error = reply->error();
|
||||
const int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
@@ -423,6 +436,7 @@ static Group dtoRecipe(const Storage<DtoStorageType<DtoType>> &dtoStorage)
|
||||
if (doneWith == DoneWith::Success && statusCode == httpStatusCodeOk
|
||||
&& contentType == s_jsonContentType) {
|
||||
*storage = reply->readAll();
|
||||
dtoStorage->url = reply->url();
|
||||
return DoneResult::Success;
|
||||
}
|
||||
|
||||
@@ -503,7 +517,7 @@ static Group authorizationRecipe()
|
||||
{
|
||||
const Storage<GetDtoStorage<Dto::DashboardInfoDto>> unauthorizedDashboardStorage;
|
||||
const auto onUnauthorizedGroupSetup = [unauthorizedDashboardStorage] {
|
||||
if (dd->m_serverAccess != ServerAccess::NoAuthorization)
|
||||
if (isServerAccessEstablished())
|
||||
return SetupResult::StopWithSuccess;
|
||||
|
||||
unauthorizedDashboardStorage->url = QUrl(settings().server.dashboard);
|
||||
@@ -512,8 +526,7 @@ static Group authorizationRecipe()
|
||||
const auto onUnauthorizedGroupDone = [unauthorizedDashboardStorage] {
|
||||
if (unauthorizedDashboardStorage->dtoData) {
|
||||
dd->m_serverAccess = ServerAccess::NoAuthorization;
|
||||
dd->m_dashboardInfo = toDashboardInfo(settings().server.dashboard,
|
||||
*unauthorizedDashboardStorage->dtoData);
|
||||
dd->m_dashboardInfo = toDashboardInfo(*unauthorizedDashboardStorage);
|
||||
} else {
|
||||
dd->m_serverAccess = ServerAccess::WithAuthorization;
|
||||
}
|
||||
@@ -540,7 +553,7 @@ static Group authorizationRecipe()
|
||||
|
||||
const Storage<QString> passwordStorage;
|
||||
const Storage<GetDtoStorage<Dto::DashboardInfoDto>> dashboardStorage;
|
||||
const auto onDashboardGroupSetup = [passwordStorage, dashboardStorage] {
|
||||
const auto onPasswordGroupSetup = [passwordStorage, dashboardStorage] {
|
||||
if (dd->m_apiToken)
|
||||
return SetupResult::StopWithSuccess;
|
||||
|
||||
@@ -563,8 +576,7 @@ static Group authorizationRecipe()
|
||||
if (!dashboardStorage->dtoData)
|
||||
return SetupResult::StopWithSuccess;
|
||||
|
||||
dd->m_dashboardInfo = toDashboardInfo(settings().server.dashboard,
|
||||
*dashboardStorage->dtoData);
|
||||
dd->m_dashboardInfo = toDashboardInfo(*dashboardStorage);
|
||||
|
||||
const Dto::DashboardInfoDto &dashboardDto = *dashboardStorage->dtoData;
|
||||
if (!dashboardDto.userApiTokenUrl)
|
||||
@@ -572,7 +584,7 @@ static Group authorizationRecipe()
|
||||
|
||||
apiTokenStorage->credential = dashboardStorage->credential;
|
||||
apiTokenStorage->url
|
||||
= QUrl(settings().server.dashboard).resolved(*dashboardDto.userApiTokenUrl);
|
||||
= dd->m_dashboardInfo->source.resolved(*dashboardDto.userApiTokenUrl);
|
||||
apiTokenStorage->csrfToken = dashboardDto.csrfToken.toUtf8();
|
||||
const Dto::ApiTokenCreationRequestDto requestDto{*passwordStorage, "IdePlugin",
|
||||
apiTokenDescription(), 0};
|
||||
@@ -598,6 +610,29 @@ static Group authorizationRecipe()
|
||||
return DoneResult::Success;
|
||||
};
|
||||
|
||||
const auto onDashboardGroupSetup = [dashboardStorage] {
|
||||
if (dd->m_dashboardInfo || dd->m_serverAccess != ServerAccess::WithAuthorization
|
||||
|| !dd->m_apiToken) {
|
||||
return SetupResult::StopWithSuccess; // Unauthorized access should have collect dashboard before
|
||||
}
|
||||
dashboardStorage->credential = "AxToken " + *dd->m_apiToken;
|
||||
dashboardStorage->url = QUrl(settings().server.dashboard);
|
||||
return SetupResult::Continue;
|
||||
};
|
||||
const auto onDeleteCredentialSetup = [dashboardStorage](CredentialQuery &credential) {
|
||||
if (dashboardStorage->dtoData) {
|
||||
dd->m_dashboardInfo = toDashboardInfo(*dashboardStorage);
|
||||
return SetupResult::StopWithSuccess;
|
||||
}
|
||||
dd->m_apiToken = {};
|
||||
MessageManager::writeFlashing(QString("Axivion: %1")
|
||||
.arg(Tr::tr("The stored ApiToken is not valid anymore, removing it.")));
|
||||
credential.setOperation(CredentialOperation::Delete);
|
||||
credential.setService(s_axivionKeychainService);
|
||||
credential.setKey(credentialKey());
|
||||
return SetupResult::Continue;
|
||||
};
|
||||
|
||||
return {
|
||||
Group {
|
||||
unauthorizedDashboardStorage,
|
||||
@@ -611,7 +646,7 @@ static Group authorizationRecipe()
|
||||
Group {
|
||||
passwordStorage,
|
||||
dashboardStorage,
|
||||
onGroupSetup(onDashboardGroupSetup),
|
||||
onGroupSetup(onPasswordGroupSetup),
|
||||
Group { // GET DashboardInfoDto
|
||||
finishAllAndSuccess,
|
||||
dtoRecipe(dashboardStorage)
|
||||
@@ -622,6 +657,13 @@ static Group authorizationRecipe()
|
||||
dtoRecipe(apiTokenStorage),
|
||||
CredentialQueryTask(onSetCredentialSetup, onSetCredentialDone, CallDoneIf::Error)
|
||||
}
|
||||
},
|
||||
Group {
|
||||
finishAllAndSuccess,
|
||||
dashboardStorage,
|
||||
onGroupSetup(onDashboardGroupSetup),
|
||||
dtoRecipe(dashboardStorage),
|
||||
CredentialQueryTask(onDeleteCredentialSetup)
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -633,9 +675,10 @@ static Group fetchDataRecipe(const QUrl &url, const std::function<void(const Dto
|
||||
const Storage<GetDtoStorage<DtoType>> dtoStorage;
|
||||
|
||||
const auto onDtoSetup = [dtoStorage, url] {
|
||||
if (!dd->m_apiToken)
|
||||
if (!isServerAccessEstablished())
|
||||
return SetupResult::StopWithError;
|
||||
|
||||
if (dd->m_serverAccess == ServerAccess::WithAuthorization && dd->m_apiToken)
|
||||
dtoStorage->credential = "AxToken " + *dd->m_apiToken;
|
||||
dtoStorage->url = url;
|
||||
return SetupResult::Continue;
|
||||
@@ -661,27 +704,22 @@ Group dashboardInfoRecipe(const DashboardInfoHandler &handler)
|
||||
{
|
||||
const auto onSetup = [handler] {
|
||||
if (dd->m_dashboardInfo) {
|
||||
if (handler)
|
||||
handler(*dd->m_dashboardInfo);
|
||||
return SetupResult::StopWithSuccess;
|
||||
}
|
||||
return SetupResult::Continue;
|
||||
};
|
||||
const auto onDone = [handler] {
|
||||
if (handler)
|
||||
handler(make_unexpected(QString("Error"))); // TODO: Collect error message in the storage.
|
||||
};
|
||||
|
||||
const auto resultHandler = [handler](const Dto::DashboardInfoDto &data) {
|
||||
dd->m_dashboardInfo = toDashboardInfo(settings().server.dashboard, data);
|
||||
if (handler)
|
||||
const auto onDone = [handler](DoneWith result) {
|
||||
if (result == DoneWith::Success && dd->m_dashboardInfo)
|
||||
handler(*dd->m_dashboardInfo);
|
||||
else
|
||||
handler(make_unexpected(QString("Error"))); // TODO: Collect error message in the storage.
|
||||
};
|
||||
|
||||
const Group root {
|
||||
onGroupSetup(onSetup), // Stops if cache exists.
|
||||
fetchDataRecipe<Dto::DashboardInfoDto>(settings().server.dashboard, resultHandler),
|
||||
onGroupDone(onDone, CallDoneIf::Error)
|
||||
authorizationRecipe(),
|
||||
onGroupDone(onDone)
|
||||
};
|
||||
return root;
|
||||
}
|
||||
@@ -754,13 +792,13 @@ void AxivionPluginPrivate::fetchProjectInfo(const QString &projectName)
|
||||
handleOpenedDocs();
|
||||
};
|
||||
|
||||
const QUrl url(settings().server.dashboard);
|
||||
taskTree.setRecipe(fetchDataRecipe<Dto::ProjectInfoDto>(url.resolved(*it), handler));
|
||||
taskTree.setRecipe(
|
||||
fetchDataRecipe<Dto::ProjectInfoDto>(m_dashboardInfo->source.resolved(*it), handler));
|
||||
return SetupResult::Continue;
|
||||
};
|
||||
|
||||
const Group root {
|
||||
dashboardInfoRecipe(),
|
||||
authorizationRecipe(),
|
||||
TaskTreeTask(onTaskTreeSetup)
|
||||
};
|
||||
m_taskTreeRunner.start(root);
|
||||
|
@@ -23,6 +23,8 @@ namespace Utils { class FilePath; }
|
||||
|
||||
namespace Axivion::Internal {
|
||||
|
||||
constexpr int DefaultSearchLimit = 2048;
|
||||
|
||||
struct IssueListSearch
|
||||
{
|
||||
QString kind;
|
||||
@@ -33,7 +35,7 @@ struct IssueListSearch
|
||||
QString filter_path;
|
||||
QString sort;
|
||||
int offset = 0;
|
||||
int limit = 150;
|
||||
int limit = DefaultSearchLimit;
|
||||
bool computeTotalRowCount = false;
|
||||
|
||||
QString toQuery() const;
|
||||
@@ -71,7 +73,7 @@ void fetchProjectInfo(const QString &projectName);
|
||||
std::optional<Dto::ProjectInfoDto> projectInfo();
|
||||
bool handleCertificateIssue();
|
||||
|
||||
QIcon iconForIssue(const QString &prefix);
|
||||
QIcon iconForIssue(const std::optional<Dto::IssueKind> &issueKind);
|
||||
QString anyToSimpleString(const Dto::Any &any);
|
||||
void fetchIssueInfo(const QString &id);
|
||||
|
||||
|
@@ -3,6 +3,7 @@
|
||||
|
||||
#include "dynamiclistmodel.h"
|
||||
|
||||
#include "axivionplugin.h"
|
||||
#include "axiviontr.h"
|
||||
|
||||
#include <utils/qtcassert.h>
|
||||
@@ -10,8 +11,6 @@
|
||||
|
||||
namespace Axivion::Internal {
|
||||
|
||||
constexpr int pageSize = 150;
|
||||
|
||||
DynamicListModel::DynamicListModel(QObject *parent)
|
||||
: QAbstractItemModel(parent)
|
||||
{
|
||||
@@ -161,7 +160,7 @@ QModelIndex DynamicListModel::indexForItem(const ListItem *item) const
|
||||
void DynamicListModel::onNeedFetch(int row)
|
||||
{
|
||||
m_fetchStart = row;
|
||||
m_fetchEnd = row + pageSize;
|
||||
m_fetchEnd = row + DefaultSearchLimit;
|
||||
if (m_fetchStart < 0)
|
||||
return;
|
||||
m_fetchMoreTimer.start();
|
||||
@@ -171,14 +170,14 @@ void DynamicListModel::fetchNow()
|
||||
{
|
||||
const int old = m_lastFetch;
|
||||
m_lastFetch = m_fetchStart; // we need the "original" fetch request to avoid endless loop
|
||||
m_lastFetchEnd = m_fetchStart + pageSize;
|
||||
m_lastFetchEnd = m_fetchStart + DefaultSearchLimit;
|
||||
|
||||
if (old != -1) {
|
||||
const int diff = old - m_fetchStart;
|
||||
if (0 < diff && diff < pageSize) {
|
||||
m_fetchStart = qMax(old - pageSize, 0);
|
||||
} else if (0 > diff && diff > -pageSize) {
|
||||
m_fetchStart = old + pageSize;
|
||||
if (0 < diff && diff < DefaultSearchLimit) {
|
||||
m_fetchStart = qMax(old - DefaultSearchLimit, 0);
|
||||
} else if (0 > diff && diff > - DefaultSearchLimit) {
|
||||
m_fetchStart = old + DefaultSearchLimit;
|
||||
if (m_expectedRowCount && m_fetchStart > *m_expectedRowCount)
|
||||
m_fetchStart = *m_expectedRowCount;
|
||||
}
|
||||
@@ -186,7 +185,7 @@ void DynamicListModel::fetchNow()
|
||||
|
||||
QTC_CHECK(m_expectedRowCount ? m_fetchStart <= *m_expectedRowCount
|
||||
: m_fetchStart >= m_children.size());
|
||||
emit fetchRequested(m_fetchStart, pageSize);
|
||||
emit fetchRequested(m_fetchStart, DefaultSearchLimit);
|
||||
m_fetchStart = -1;
|
||||
m_fetchEnd = -1;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 179 B After Width: | Height: | Size: 179 B |
Before Width: | Height: | Size: 241 B After Width: | Height: | Size: 241 B |
Before Width: | Height: | Size: 207 B After Width: | Height: | Size: 207 B |
Before Width: | Height: | Size: 263 B After Width: | Height: | Size: 263 B |
Before Width: | Height: | Size: 205 B After Width: | Height: | Size: 205 B |
Before Width: | Height: | Size: 241 B After Width: | Height: | Size: 241 B |
Before Width: | Height: | Size: 212 B After Width: | Height: | Size: 212 B |
Before Width: | Height: | Size: 397 B After Width: | Height: | Size: 397 B |
Before Width: | Height: | Size: 173 B After Width: | Height: | Size: 173 B |
Before Width: | Height: | Size: 230 B After Width: | Height: | Size: 230 B |
Before Width: | Height: | Size: 175 B After Width: | Height: | Size: 175 B |
Before Width: | Height: | Size: 318 B After Width: | Height: | Size: 318 B |
@@ -61,8 +61,10 @@ void IssueHeaderView::mousePressEvent(QMouseEvent *event)
|
||||
if (y > 1 && y < height() - 2) { // TODO improve
|
||||
const int pos = position.x();
|
||||
const int logical = logicalIndexAt(pos);
|
||||
const int end = sectionViewportPosition(logical) + sectionSize(logical);
|
||||
const int start = end - ICON_SIZE - 2;
|
||||
m_lastToggleLogicalPos = logical;
|
||||
const int margin = style()->pixelMetric(QStyle::PM_HeaderGripMargin, nullptr, this);
|
||||
const int end = sectionViewportPosition(logical) + sectionSize(logical) - margin;
|
||||
const int start = end - ICON_SIZE;
|
||||
m_maybeToggleSort = start < pos && end > pos;
|
||||
}
|
||||
}
|
||||
@@ -79,7 +81,8 @@ void IssueHeaderView::mouseReleaseEvent(QMouseEvent *event)
|
||||
const QPoint position = event->position().toPoint();
|
||||
const int y = position.y();
|
||||
const int logical = logicalIndexAt(position.x());
|
||||
if (logical > -1 && logical < m_sortableColumns.size()) {
|
||||
if (logical == m_lastToggleLogicalPos
|
||||
&& logical > -1 && logical < m_sortableColumns.size()) {
|
||||
if (m_sortableColumns.at(logical)) { // ignore non-sortable
|
||||
if (y < height() / 2) // TODO improve
|
||||
onToggleSort(logical, SortOrder::Ascending);
|
||||
@@ -88,6 +91,7 @@ void IssueHeaderView::mouseReleaseEvent(QMouseEvent *event)
|
||||
}
|
||||
}
|
||||
}
|
||||
m_lastToggleLogicalPos = -1;
|
||||
QHeaderView::mouseReleaseEvent(event);
|
||||
}
|
||||
|
||||
@@ -118,8 +122,10 @@ QSize IssueHeaderView::sectionSizeFromContents(int logicalIndex) const
|
||||
const QSize oldSize = QHeaderView::sectionSizeFromContents(logicalIndex);
|
||||
const QSize newSize = logicalIndex < m_columnWidths.size()
|
||||
? QSize(qMax(m_columnWidths.at(logicalIndex), oldSize.width()), oldSize.height()) : oldSize;
|
||||
// add icon size and margin (2)
|
||||
return QSize{newSize.width() + ICON_SIZE + 2, qMax(newSize.height(), ICON_SIZE)};
|
||||
|
||||
const int margin = style()->pixelMetric(QStyle::PM_HeaderGripMargin, nullptr, this);
|
||||
// add icon size and margin (default resize handle margin + 1)
|
||||
return QSize{newSize.width() + ICON_SIZE + margin, qMax(newSize.height(), ICON_SIZE)};
|
||||
}
|
||||
|
||||
void IssueHeaderView::paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const
|
||||
@@ -132,9 +138,10 @@ void IssueHeaderView::paintSection(QPainter *painter, const QRect &rect, int log
|
||||
if (!m_sortableColumns.at(logicalIndex))
|
||||
return;
|
||||
|
||||
const int margin = style()->pixelMetric(QStyle::PM_HeaderGripMargin, nullptr, this);
|
||||
const QIcon icon = iconForSorted(logicalIndex == m_currentSortIndex ? m_currentSortOrder : SortOrder::None);
|
||||
const int offset = qMax((rect.height() - ICON_SIZE), 0) / 2;
|
||||
const QRect iconRect(rect.left() + rect.width() - ICON_SIZE - 2, offset, ICON_SIZE, ICON_SIZE);
|
||||
const QRect iconRect(rect.left() + rect.width() - ICON_SIZE - margin, offset, ICON_SIZE, ICON_SIZE);
|
||||
icon.paint(painter, iconRect);
|
||||
}
|
||||
|
||||
|
@@ -35,6 +35,7 @@ private:
|
||||
void onToggleSort(int index, SortOrder order);
|
||||
bool m_dragging = false;
|
||||
bool m_maybeToggleSort = false;
|
||||
int m_lastToggleLogicalPos = -1;
|
||||
int m_currentSortIndex = -1;
|
||||
SortOrder m_currentSortOrder = SortOrder::None;
|
||||
QList<bool> m_sortableColumns;
|
||||
|
@@ -59,9 +59,14 @@ clang::format::FormatStyle calculateQtcStyle()
|
||||
style.AllowShortFunctionsOnASingleLine = FormatStyle::SFS_Inline;
|
||||
style.AllowShortIfStatementsOnASingleLine = FormatStyle::SIS_Never;
|
||||
style.AllowShortLoopsOnASingleLine = false;
|
||||
style.AlwaysBreakAfterReturnType = FormatStyle::RTBS_None;
|
||||
style.AlwaysBreakBeforeMultilineStrings = false;
|
||||
#if LLVM_VERSION_MAJOR >= 19
|
||||
style.BreakAfterReturnType = FormatStyle::RTBS_None;
|
||||
style.BreakTemplateDeclarations = FormatStyle::BTDS_Yes;
|
||||
#else
|
||||
style.AlwaysBreakAfterReturnType = FormatStyle::RTBS_None;
|
||||
style.AlwaysBreakTemplateDeclarations = FormatStyle::BTDS_Yes;
|
||||
#endif
|
||||
style.BinPackArguments = false;
|
||||
style.BinPackParameters = false;
|
||||
style.BraceWrapping.AfterClass = true;
|
||||
|
@@ -301,8 +301,24 @@ static CMakeBuildTarget toBuildTarget(const TargetDetails &t,
|
||||
continue;
|
||||
|
||||
const FilePath buildDir = relativeLibs ? buildDirectory : currentBuildDir;
|
||||
FilePath tmp = buildDir.resolvePath(part);
|
||||
std::optional<QString> dllName;
|
||||
|
||||
if (buildDir.osType() == OsTypeWindows && (f.role == "libraries")) {
|
||||
// Skip object libraries on Windows. This case can happen with static qml plugins
|
||||
if (part.endsWith(".obj") || part.endsWith(".o"))
|
||||
continue;
|
||||
|
||||
// Only consider dlls, not static libraries
|
||||
for (const QString &suffix :
|
||||
{QString(".lib"), QString(".dll.a"), QString(".a")}) {
|
||||
if (part.endsWith(suffix) && !dllName)
|
||||
dllName = FilePath::fromUserInput(
|
||||
part.chopped(suffix.length()).append(".dll"))
|
||||
.fileName();
|
||||
}
|
||||
}
|
||||
|
||||
FilePath tmp = buildDir.resolvePath(part);
|
||||
if (f.role == "libraries")
|
||||
tmp = tmp.parentDir();
|
||||
|
||||
@@ -312,19 +328,20 @@ static CMakeBuildTarget toBuildTarget(const TargetDetails &t,
|
||||
// "/usr/local/lib" since these are usually in the standard search
|
||||
// paths. There probably are more, but the naming schemes are arbitrary
|
||||
// so we'd need to ask the linker ("ld --verbose | grep SEARCH_DIR").
|
||||
if (buildDir.osType() == OsTypeWindows
|
||||
|| !isChildOf(tmp,
|
||||
{"/lib",
|
||||
"/lib64",
|
||||
"/usr/lib",
|
||||
"/usr/lib64",
|
||||
"/usr/local/lib"})) {
|
||||
if (buildDir.osType() != OsTypeWindows
|
||||
&& !isChildOf(tmp,
|
||||
{"/lib", "/lib64", "/usr/lib", "/usr/lib64", "/usr/local/lib"}))
|
||||
librarySeachPaths.append(tmp);
|
||||
|
||||
if (buildDir.osType() == OsTypeWindows && dllName) {
|
||||
if (tmp.pathAppended(*dllName).exists())
|
||||
librarySeachPaths.append(tmp);
|
||||
|
||||
// Libraries often have their import libs in ../lib and the
|
||||
// actual dll files in ../bin on windows. Qt is one example of that.
|
||||
if (tmp.fileName() == "lib" && buildDir.osType() == OsTypeWindows) {
|
||||
const FilePath path = tmp.parentDir().pathAppended("bin");
|
||||
if (path.isDir())
|
||||
if (path.isDir() && path.pathAppended(*dllName).exists())
|
||||
librarySeachPaths.append(path);
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,9 @@
|
||||
<RCC>
|
||||
<qresource prefix="/core">
|
||||
<file>images/expandarrow.png</file>
|
||||
<file>images/expandarrow@2x.png</file>
|
||||
<file>images/search.png</file>
|
||||
<file>images/search@2x.png</file>
|
||||
<file>images/settingscategory_core.png</file>
|
||||
<file>images/settingscategory_core@2x.png</file>
|
||||
<file>images/settingscategory_design.png</file>
|
||||
|
BIN
src/plugins/coreplugin/images/expandarrow.png
Normal file
After Width: | Height: | Size: 169 B |
BIN
src/plugins/coreplugin/images/expandarrow@2x.png
Normal file
After Width: | Height: | Size: 209 B |
BIN
src/plugins/coreplugin/images/search.png
Normal file
After Width: | Height: | Size: 179 B |
BIN
src/plugins/coreplugin/images/search@2x.png
Normal file
After Width: | Height: | Size: 361 B |
@@ -3,25 +3,8 @@
|
||||
|
||||
#include "iwelcomepage.h"
|
||||
|
||||
#include <utils/icon.h>
|
||||
#include <utils/theme/theme.h>
|
||||
#include <utils/stylehelper.h>
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QPainter>
|
||||
#include <QPainterPath>
|
||||
#include <QPixmap>
|
||||
#include <QUrl>
|
||||
|
||||
#include <qdrawutil.h>
|
||||
|
||||
using namespace Utils;
|
||||
|
||||
namespace Core {
|
||||
|
||||
const char WITHACCENTCOLOR_PROPERTY_NAME[] = "_withAccentColor";
|
||||
|
||||
static QList<IWelcomePage *> g_welcomePages;
|
||||
|
||||
const QList<IWelcomePage *> IWelcomePage::allWelcomePages()
|
||||
@@ -39,171 +22,4 @@ IWelcomePage::~IWelcomePage()
|
||||
g_welcomePages.removeOne(this);
|
||||
}
|
||||
|
||||
QPalette WelcomePageFrame::buttonPalette(bool isActive, bool isCursorInside, bool forText)
|
||||
{
|
||||
QPalette pal;
|
||||
pal.setBrush(QPalette::Window, {});
|
||||
pal.setBrush(QPalette::WindowText, {});
|
||||
|
||||
Theme *theme = Utils::creatorTheme();
|
||||
if (isActive) {
|
||||
if (forText) {
|
||||
pal.setColor(QPalette::Window, theme->color(Theme::Welcome_ForegroundPrimaryColor));
|
||||
pal.setColor(QPalette::WindowText, theme->color(Theme::Welcome_BackgroundPrimaryColor));
|
||||
} else {
|
||||
pal.setColor(QPalette::Window, theme->color(Theme::Welcome_AccentColor));
|
||||
pal.setColor(QPalette::WindowText, theme->color(Theme::Welcome_AccentColor));
|
||||
}
|
||||
} else {
|
||||
if (isCursorInside) {
|
||||
if (forText) {
|
||||
pal.setColor(QPalette::Window, theme->color(Theme::Welcome_HoverColor));
|
||||
pal.setColor(QPalette::WindowText, theme->color(Theme::Welcome_TextColor));
|
||||
} else {
|
||||
pal.setColor(QPalette::Window, theme->color(Theme::Welcome_HoverColor));
|
||||
pal.setColor(QPalette::WindowText, theme->color(Theme::Welcome_ForegroundSecondaryColor));
|
||||
}
|
||||
} else {
|
||||
if (forText) {
|
||||
pal.setColor(QPalette::Window, theme->color(Theme::Welcome_ForegroundPrimaryColor));
|
||||
pal.setColor(QPalette::WindowText, theme->color(Theme::Welcome_TextColor));
|
||||
} else {
|
||||
pal.setColor(QPalette::Window, theme->color(Theme::Welcome_BackgroundPrimaryColor));
|
||||
pal.setColor(QPalette::WindowText, theme->color(Theme::Welcome_ForegroundSecondaryColor));
|
||||
}
|
||||
}
|
||||
}
|
||||
return pal;
|
||||
}
|
||||
|
||||
WelcomePageFrame::WelcomePageFrame(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
setContentsMargins(1, 1, 1, 1);
|
||||
}
|
||||
|
||||
void WelcomePageFrame::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
QWidget::paintEvent(event);
|
||||
QPainter p(this);
|
||||
|
||||
qDrawPlainRect(&p, rect(), palette().color(QPalette::WindowText), 1);
|
||||
|
||||
if (property(WITHACCENTCOLOR_PROPERTY_NAME).toBool()) {
|
||||
const int accentRectWidth = 10;
|
||||
const QRect accentRect = rect().adjusted(width() - accentRectWidth, 0, 0, 0);
|
||||
p.fillRect(accentRect, creatorTheme()->color(Theme::Welcome_AccentColor));
|
||||
}
|
||||
}
|
||||
|
||||
class WelcomePageButtonPrivate
|
||||
{
|
||||
public:
|
||||
explicit WelcomePageButtonPrivate(WelcomePageButton *parent)
|
||||
: q(parent) {}
|
||||
bool isActive() const;
|
||||
void doUpdate(bool cursorInside);
|
||||
|
||||
WelcomePageButton *q;
|
||||
QHBoxLayout *m_layout = nullptr;
|
||||
QLabel *m_label = nullptr;
|
||||
|
||||
std::function<void()> onClicked;
|
||||
std::function<bool()> activeChecker;
|
||||
};
|
||||
|
||||
WelcomePageButton::WelcomePageButton(QWidget *parent)
|
||||
: WelcomePageFrame(parent), d(new WelcomePageButtonPrivate(this))
|
||||
{
|
||||
setAutoFillBackground(true);
|
||||
setPalette(buttonPalette(false, false, false));
|
||||
setContentsMargins(0, 1, 0, 1);
|
||||
|
||||
d->m_label = new QLabel(this);
|
||||
d->m_label->setPalette(buttonPalette(false, false, true));
|
||||
d->m_label->setAlignment(Qt::AlignCenter);
|
||||
|
||||
d->m_layout = new QHBoxLayout;
|
||||
d->m_layout->setSpacing(0);
|
||||
d->m_layout->addWidget(d->m_label);
|
||||
setSize(SizeLarge);
|
||||
setLayout(d->m_layout);
|
||||
}
|
||||
|
||||
WelcomePageButton::~WelcomePageButton()
|
||||
{
|
||||
delete d;
|
||||
}
|
||||
|
||||
void WelcomePageButton::mousePressEvent(QMouseEvent *)
|
||||
{
|
||||
if (d->onClicked)
|
||||
d->onClicked();
|
||||
}
|
||||
|
||||
void WelcomePageButton::enterEvent(QEnterEvent *)
|
||||
{
|
||||
d->doUpdate(true);
|
||||
}
|
||||
|
||||
void WelcomePageButton::leaveEvent(QEvent *)
|
||||
{
|
||||
d->doUpdate(false);
|
||||
}
|
||||
|
||||
bool WelcomePageButtonPrivate::isActive() const
|
||||
{
|
||||
return activeChecker && activeChecker();
|
||||
}
|
||||
|
||||
void WelcomePageButtonPrivate::doUpdate(bool cursorInside)
|
||||
{
|
||||
const bool active = isActive();
|
||||
q->setPalette(WelcomePageFrame::buttonPalette(active, cursorInside, false));
|
||||
const QPalette lpal = WelcomePageFrame::buttonPalette(active, cursorInside, true);
|
||||
m_label->setPalette(lpal);
|
||||
q->update();
|
||||
}
|
||||
|
||||
void WelcomePageButton::setText(const QString &text)
|
||||
{
|
||||
d->m_label->setText(text);
|
||||
}
|
||||
|
||||
void WelcomePageButton::setSize(Size size)
|
||||
{
|
||||
const int hMargin = size == SizeSmall ? 12 : 26;
|
||||
const int vMargin = size == SizeSmall ? 2 : 4;
|
||||
d->m_layout->setContentsMargins(hMargin, vMargin, hMargin, vMargin);
|
||||
}
|
||||
|
||||
void WelcomePageButton::setWithAccentColor(bool withAccent)
|
||||
{
|
||||
setProperty(WITHACCENTCOLOR_PROPERTY_NAME, withAccent);
|
||||
}
|
||||
|
||||
void WelcomePageButton::setActiveChecker(const std::function<bool ()> &value)
|
||||
{
|
||||
d->activeChecker = value;
|
||||
}
|
||||
|
||||
void WelcomePageButton::recheckActive()
|
||||
{
|
||||
bool isActive = d->isActive();
|
||||
d->doUpdate(isActive);
|
||||
}
|
||||
|
||||
void WelcomePageButton::click()
|
||||
{
|
||||
if (d->onClicked)
|
||||
d->onClicked();
|
||||
}
|
||||
|
||||
void WelcomePageButton::setOnClicked(const std::function<void ()> &value)
|
||||
{
|
||||
d->onClicked = value;
|
||||
if (d->isActive())
|
||||
click();
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
|
@@ -7,13 +7,10 @@
|
||||
|
||||
#include <utils/id.h>
|
||||
|
||||
#include <QWidget>
|
||||
#include <QObject>
|
||||
|
||||
#include <functional>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QPixmap;
|
||||
class QWidget;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
namespace Core {
|
||||
@@ -37,43 +34,4 @@ public:
|
||||
static const QList<IWelcomePage *> allWelcomePages();
|
||||
};
|
||||
|
||||
class WelcomePageButtonPrivate;
|
||||
|
||||
class CORE_EXPORT WelcomePageFrame : public QWidget
|
||||
{
|
||||
public:
|
||||
WelcomePageFrame(QWidget *parent);
|
||||
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
static QPalette buttonPalette(bool isActive, bool isCursorInside, bool forText);
|
||||
};
|
||||
|
||||
class CORE_EXPORT WelcomePageButton : public WelcomePageFrame
|
||||
{
|
||||
public:
|
||||
enum Size {
|
||||
SizeSmall,
|
||||
SizeLarge,
|
||||
};
|
||||
|
||||
explicit WelcomePageButton(QWidget *parent = nullptr);
|
||||
~WelcomePageButton() override;
|
||||
|
||||
void mousePressEvent(QMouseEvent *) override;
|
||||
void enterEvent(QEnterEvent *) override;
|
||||
void leaveEvent(QEvent *) override;
|
||||
|
||||
void setText(const QString &text);
|
||||
void setSize(enum Size);
|
||||
void setWithAccentColor(bool withAccent);
|
||||
void setOnClicked(const std::function<void ()> &value);
|
||||
void setActiveChecker(const std::function<bool ()> &value);
|
||||
void recheckActive();
|
||||
void click();
|
||||
|
||||
private:
|
||||
WelcomePageButtonPrivate *d;
|
||||
};
|
||||
|
||||
} // Core
|
||||
|
@@ -559,10 +559,16 @@ private:
|
||||
|
||||
const QString timestamp = QDateTime::currentDateTime().toString("HH:mm:ss.zzz");
|
||||
|
||||
auto append = [this, timestamp, type, category, msg] {
|
||||
if (rowCount() >= 1000000) // limit log to 1000000 items
|
||||
destroyItem(itemForIndex(index(0, 0)));
|
||||
|
||||
appendItem(LogEntry{timestamp, messageTypeToString(type), category, msg});
|
||||
};
|
||||
|
||||
if (QThread::currentThread() != thread())
|
||||
QMetaObject::invokeMethod(this, append, Qt::QueuedConnection);
|
||||
else
|
||||
append();
|
||||
}
|
||||
|
||||
private:
|
||||
|
@@ -267,6 +267,11 @@ QDateTime SessionManager::lastActiveTime(const QString &session)
|
||||
return d->m_lastActiveTimes.value(session);
|
||||
}
|
||||
|
||||
int SessionManager::sessionsCount()
|
||||
{
|
||||
return d->m_sessions.count();
|
||||
}
|
||||
|
||||
FilePath SessionManager::sessionNameToFileName(const QString &session)
|
||||
{
|
||||
return ICore::userResourcePath(session + ".qws");
|
||||
|
@@ -34,6 +34,7 @@ public:
|
||||
static QStringList sessions();
|
||||
static QDateTime sessionDateTime(const QString &session);
|
||||
static QDateTime lastActiveTime(const QString &session);
|
||||
static int sessionsCount();
|
||||
|
||||
static bool createSession(const QString &session);
|
||||
|
||||
|
@@ -7,6 +7,7 @@
|
||||
|
||||
#include <utils/algorithm.h>
|
||||
#include <utils/fancylineedit.h>
|
||||
#include <utils/icon.h>
|
||||
#include <utils/layoutbuilder.h>
|
||||
#include <utils/qtcassert.h>
|
||||
#include <utils/stylehelper.h>
|
||||
@@ -26,36 +27,34 @@
|
||||
|
||||
#include <qdrawutil.h>
|
||||
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
void qt_blurImage(QImage &blurImage, qreal radius, bool quality, int transposed = 0);
|
||||
QT_END_NAMESPACE
|
||||
|
||||
using namespace Utils;
|
||||
|
||||
namespace Core {
|
||||
|
||||
using namespace WelcomePageHelpers;
|
||||
using namespace StyleHelper::SpacingTokens;
|
||||
|
||||
static QColor themeColor(Theme::Color role)
|
||||
{
|
||||
return creatorTheme()->color(role);
|
||||
}
|
||||
|
||||
static QFont sizedFont(int size, const QWidget *widget)
|
||||
{
|
||||
QFont f = widget->font();
|
||||
f.setPixelSize(size);
|
||||
return f;
|
||||
}
|
||||
|
||||
namespace WelcomePageHelpers {
|
||||
|
||||
QWidget *panelBar(QWidget *parent)
|
||||
void setBackgroundColor(QWidget *widget, Theme::Color colorRole)
|
||||
{
|
||||
auto frame = new QWidget(parent);
|
||||
frame->setAutoFillBackground(true);
|
||||
frame->setMinimumWidth(WelcomePageHelpers::HSpacing);
|
||||
QPalette pal;
|
||||
pal.setBrush(QPalette::Window, {});
|
||||
pal.setColor(QPalette::Window, themeColor(Theme::Welcome_BackgroundPrimaryColor));
|
||||
frame->setPalette(pal);
|
||||
return frame;
|
||||
QPalette palette = creatorTheme()->palette();
|
||||
const QPalette::ColorRole role = QPalette::Window;
|
||||
palette.setBrush(role, {});
|
||||
palette.setColor(role, creatorTheme()->color(colorRole));
|
||||
widget->setPalette(palette);
|
||||
widget->setBackgroundRole(role);
|
||||
widget->setAutoFillBackground(true);
|
||||
}
|
||||
|
||||
void drawCardBackground(QPainter *painter, const QRectF &rect,
|
||||
@@ -77,30 +76,351 @@ void drawCardBackground(QPainter *painter, const QRectF &rect,
|
||||
painter->restore();
|
||||
}
|
||||
|
||||
QWidget *createRule(Qt::Orientation orientation, QWidget *parent)
|
||||
{
|
||||
auto rule = new QWidget(parent);
|
||||
if (orientation == Qt::Horizontal)
|
||||
rule->setFixedHeight(1);
|
||||
else
|
||||
rule->setFixedWidth(1);
|
||||
setBackgroundColor(rule, Theme::Token_Stroke_Subtle);
|
||||
return rule;
|
||||
}
|
||||
|
||||
} // namespace WelcomePageHelpers
|
||||
|
||||
SearchBox::SearchBox(QWidget *parent)
|
||||
: WelcomePageFrame(parent)
|
||||
enum WidgetState {
|
||||
WidgetStateDefault,
|
||||
WidgetStateChecked,
|
||||
WidgetStateHovered,
|
||||
};
|
||||
|
||||
static const TextFormat &buttonTF(Button::Role role, WidgetState state)
|
||||
{
|
||||
setAutoFillBackground(true);
|
||||
using namespace WelcomePageHelpers;
|
||||
static const TextFormat mediumPrimaryTF
|
||||
{Theme::Token_Basic_White, StyleHelper::UiElement::UiElementButtonMedium,
|
||||
Qt::AlignCenter | Qt::TextDontClip};
|
||||
static const TextFormat mediumSecondaryTF
|
||||
{Theme::Token_Text_Default, mediumPrimaryTF.uiElement, mediumPrimaryTF.drawTextFlags};
|
||||
static const TextFormat smallPrimaryTF
|
||||
{mediumPrimaryTF.themeColor, StyleHelper::UiElement::UiElementButtonSmall,
|
||||
mediumPrimaryTF.drawTextFlags};
|
||||
static const TextFormat smallSecondaryTF
|
||||
{mediumSecondaryTF.themeColor, smallPrimaryTF.uiElement, smallPrimaryTF.drawTextFlags};
|
||||
static const TextFormat smallListDefaultTF
|
||||
{Theme::Token_Text_Default, StyleHelper::UiElement::UiElementIconStandard,
|
||||
Qt::AlignLeft | Qt::AlignVCenter | Qt::TextDontClip};
|
||||
static const TextFormat smallListCheckedTF
|
||||
{smallListDefaultTF.themeColor, StyleHelper::UiElement::UiElementIconActive,
|
||||
smallListDefaultTF.drawTextFlags};
|
||||
static const TextFormat smallLinkDefaultTF
|
||||
{Theme::Token_Text_Default, smallListDefaultTF.uiElement, smallListDefaultTF.drawTextFlags};
|
||||
static const TextFormat smallLinkHoveredTF
|
||||
{Theme::Token_Accent_Default, smallListCheckedTF.uiElement,
|
||||
smallLinkDefaultTF.drawTextFlags};
|
||||
|
||||
m_lineEdit = new FancyLineEdit;
|
||||
m_lineEdit->setFiltering(true);
|
||||
m_lineEdit->setFrame(false);
|
||||
m_lineEdit->setMinimumHeight(33);
|
||||
m_lineEdit->setAttribute(Qt::WA_MacShowFocusRect, false);
|
||||
switch (role) {
|
||||
case Button::MediumPrimary: return mediumPrimaryTF;
|
||||
case Button::MediumSecondary: return mediumSecondaryTF;
|
||||
case Button::SmallPrimary: return smallPrimaryTF;
|
||||
case Button::SmallSecondary: return smallSecondaryTF;
|
||||
case Button::SmallList: return (state == WidgetStateDefault) ? smallListDefaultTF
|
||||
: smallListCheckedTF;
|
||||
case Button::SmallLink: return (state == WidgetStateDefault) ? smallLinkDefaultTF
|
||||
: smallLinkHoveredTF;
|
||||
}
|
||||
return mediumPrimaryTF;
|
||||
}
|
||||
|
||||
QPalette pal = buttonPalette(false, false, true);
|
||||
// for the margins
|
||||
pal.setColor(QPalette::Window, m_lineEdit->palette().color(QPalette::Base));
|
||||
// for macOS dark mode
|
||||
pal.setColor(QPalette::WindowText, themeColor(Theme::Welcome_ForegroundPrimaryColor));
|
||||
pal.setColor(QPalette::Text, themeColor(Theme::Welcome_TextColor));
|
||||
Button::Button(const QString &text, Role role, QWidget *parent)
|
||||
: QPushButton(text, parent)
|
||||
, m_role(role)
|
||||
{
|
||||
// Prevent QMacStyle::subElementRect(SE_PushButtonLayoutItem) from changing our geometry
|
||||
setFlat(true);
|
||||
|
||||
updateMargins();
|
||||
if (m_role == SmallList)
|
||||
setCheckable(true);
|
||||
else if (m_role == SmallLink)
|
||||
setCursor(Qt::PointingHandCursor);
|
||||
}
|
||||
|
||||
QSize Button::minimumSizeHint() const
|
||||
{
|
||||
const TextFormat &tf = buttonTF(m_role, WidgetStateHovered);
|
||||
const QFontMetrics fm(tf.font());
|
||||
const QSize textS = fm.size(Qt::TextShowMnemonic, text());
|
||||
const QMargins margins = contentsMargins();
|
||||
return {margins.left() + textS.width() + margins.right(),
|
||||
margins.top() + tf.lineHeight() + margins.bottom()};
|
||||
}
|
||||
|
||||
void Button::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
// Without pixmap
|
||||
// +----------------+----------------+----------------+
|
||||
// | |(VPadding[S|XS])| |
|
||||
// | +----------------+----------------+
|
||||
// |(HPadding[S|XS])| <label> |(HPadding[S|XS])|
|
||||
// | +----------------+----------------+
|
||||
// | |(VPadding[S|XS])| |
|
||||
// +----------------+---------------------------------+
|
||||
//
|
||||
// With pixmap
|
||||
// +--------+------------+---------------------------------+
|
||||
// | | |(VPadding[S|XS])| |
|
||||
// | | +----------------+ |
|
||||
// |<pixmap>|(HGap[S|XS])| <label> |(HPadding[S|XS])|
|
||||
// | | +----------------+ |
|
||||
// | | |(VPadding[S|XS])| |
|
||||
// +--------+------------+----------------+----------------+
|
||||
|
||||
using namespace WelcomePageHelpers;
|
||||
const bool hovered = underMouse();
|
||||
const WidgetState state = isChecked() ? WidgetStateChecked : hovered ? WidgetStateHovered
|
||||
: WidgetStateDefault;
|
||||
const TextFormat &tf = buttonTF(m_role, state);
|
||||
const QMargins margins = contentsMargins();
|
||||
const QRect bgR = rect();
|
||||
|
||||
QPainter p(this);
|
||||
|
||||
const qreal brRectRounding = 3.75;
|
||||
switch (m_role) {
|
||||
case MediumPrimary:
|
||||
case SmallPrimary: {
|
||||
const QBrush fill(creatorTheme()->color(isDown()
|
||||
? Theme::Token_Accent_Subtle
|
||||
: hovered ? Theme::Token_Accent_Muted
|
||||
: Theme::Token_Accent_Default));
|
||||
drawCardBackground(&p, bgR, fill, QPen(Qt::NoPen), brRectRounding);
|
||||
break;
|
||||
}
|
||||
case MediumSecondary:
|
||||
case SmallSecondary: {
|
||||
const QPen outline(creatorTheme()->color(Theme::Token_Text_Default), hovered ? 2 : 1);
|
||||
drawCardBackground(&p, bgR, QBrush(Qt::NoBrush), outline, brRectRounding);
|
||||
break;
|
||||
}
|
||||
case SmallList: {
|
||||
if (isChecked() || hovered) {
|
||||
const QBrush fill(creatorTheme()->color(isChecked() ? Theme::Token_Foreground_Muted
|
||||
: Theme::Token_Foreground_Subtle));
|
||||
drawCardBackground(&p, bgR, fill, QPen(Qt::NoPen), brRectRounding);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SmallLink:
|
||||
break;
|
||||
}
|
||||
|
||||
if (!m_pixmap.isNull()) {
|
||||
const int pixmapHeight = int(m_pixmap.deviceIndependentSize().height());
|
||||
const int pixmapY = (bgR.height() - pixmapHeight) / 2;
|
||||
p.drawPixmap(0, pixmapY, m_pixmap);
|
||||
}
|
||||
|
||||
const int availableLabelWidth = event->rect().width() - margins.left() - margins.right();
|
||||
const QFont font = tf.font();
|
||||
const QFontMetrics fm(font);
|
||||
const QString elidedLabelText = fm.elidedText(text(), Qt::ElideRight, availableLabelWidth);
|
||||
const QRect labelR(margins.left(), margins.top(), availableLabelWidth, tf.lineHeight());
|
||||
p.setFont(font);
|
||||
p.setPen(tf.color());
|
||||
p.drawText(labelR, tf.drawTextFlags, elidedLabelText);
|
||||
}
|
||||
|
||||
void Button::setPixmap(const QPixmap &pixmap)
|
||||
{
|
||||
m_pixmap = pixmap;
|
||||
updateMargins();
|
||||
}
|
||||
|
||||
void Button::updateMargins()
|
||||
{
|
||||
const bool tokenSizeS = m_role == MediumPrimary || m_role == MediumSecondary
|
||||
|| m_role == SmallList || m_role == SmallLink;
|
||||
const int gap = tokenSizeS ? HGapS : HGapXs;
|
||||
const int hPaddingR = tokenSizeS ? HPaddingS : HPaddingXs;
|
||||
const int hPaddingL = m_pixmap.isNull() ? hPaddingR
|
||||
: int(m_pixmap.deviceIndependentSize().width()) + gap;
|
||||
const int vPadding = tokenSizeS ? VPaddingS : VPaddingXs;
|
||||
setContentsMargins(hPaddingL, vPadding, hPaddingR, vPadding);
|
||||
}
|
||||
|
||||
Label::Label(const QString &text, Role role, QWidget *parent)
|
||||
: QLabel(text, parent)
|
||||
, m_role(role)
|
||||
{
|
||||
using namespace WelcomePageHelpers;
|
||||
static const TextFormat primaryTF
|
||||
{Theme::Token_Text_Muted, StyleHelper::UiElement::UiElementH3};
|
||||
static const TextFormat secondaryTF
|
||||
{primaryTF.themeColor, StyleHelper::UiElement::UiElementH6Capital};
|
||||
|
||||
const TextFormat &tF = m_role == Primary ? primaryTF : secondaryTF;
|
||||
const int vPadding = m_role == Primary ? ExPaddingGapM : VPaddingS;
|
||||
|
||||
setFixedHeight(vPadding + tF.lineHeight() + vPadding);
|
||||
setFont(tF.font());
|
||||
QPalette pal = palette();
|
||||
pal.setColor(QPalette::WindowText, tF.color());
|
||||
setPalette(pal);
|
||||
}
|
||||
|
||||
constexpr TextFormat searchBoxTextTF
|
||||
{Theme::Token_Text_Default, StyleHelper::UiElement::UiElementBody2};
|
||||
constexpr TextFormat searchBoxPlaceholderTF
|
||||
{Theme::Token_Text_Muted, searchBoxTextTF.uiElement};
|
||||
|
||||
static const QPixmap &searchBoxIcon()
|
||||
{
|
||||
static const QPixmap icon = Icon({{FilePath::fromString(":/core/images/search"),
|
||||
Theme::Token_Text_Muted}}, Icon::Tint).pixmap();
|
||||
return icon;
|
||||
}
|
||||
|
||||
SearchBox::SearchBox(QWidget *parent)
|
||||
: QLineEdit(parent)
|
||||
{
|
||||
setAttribute(Qt::WA_MacShowFocusRect, false);
|
||||
setAutoFillBackground(false);
|
||||
setFont(searchBoxTextTF.font());
|
||||
setFrame(false);
|
||||
setMouseTracking(true);
|
||||
|
||||
QPalette pal = palette();
|
||||
pal.setColor(QPalette::Base, Qt::transparent);
|
||||
pal.setColor(QPalette::PlaceholderText, searchBoxPlaceholderTF.color());
|
||||
pal.setColor(QPalette::Text, searchBoxTextTF.color());
|
||||
setPalette(pal);
|
||||
|
||||
auto box = new QHBoxLayout(this);
|
||||
box->setContentsMargins(10, 0, 1, 0);
|
||||
box->addWidget(m_lineEdit);
|
||||
const QSize iconSize = searchBoxIcon().deviceIndependentSize().toSize();
|
||||
setContentsMargins({HPaddingXs, ExPaddingGapM,
|
||||
HPaddingXs + iconSize.width() + HPaddingXs, ExPaddingGapM});
|
||||
setFixedHeight(ExPaddingGapM + searchBoxTextTF.lineHeight() + ExPaddingGapM);
|
||||
}
|
||||
|
||||
QSize SearchBox::minimumSizeHint() const
|
||||
{
|
||||
const QFontMetrics fm(searchBoxTextTF.font());
|
||||
const QSize textS = fm.size(Qt::TextSingleLine, text());
|
||||
const QMargins margins = contentsMargins();
|
||||
return {margins.left() + textS.width() + margins.right(),
|
||||
margins.top() + searchBoxTextTF.lineHeight() + margins.bottom()};
|
||||
}
|
||||
|
||||
void SearchBox::enterEvent(QEnterEvent *event)
|
||||
{
|
||||
QLineEdit::enterEvent(event);
|
||||
update();
|
||||
}
|
||||
|
||||
void SearchBox::leaveEvent(QEvent *event)
|
||||
{
|
||||
QLineEdit::leaveEvent(event);
|
||||
update();
|
||||
}
|
||||
|
||||
static void paintCommonBackground(QPainter *p, const QRectF &rect, const QWidget *widget)
|
||||
{
|
||||
const QBrush fill(creatorTheme()->color(Theme::Token_Background_Muted));
|
||||
const Theme::Color c = widget->hasFocus() ? Theme::Token_Stroke_Strong :
|
||||
widget->underMouse() ? Theme::Token_Stroke_Muted
|
||||
: Theme::Token_Stroke_Subtle;
|
||||
const QPen pen(creatorTheme()->color(c));
|
||||
drawCardBackground(p, rect, fill, pen);
|
||||
}
|
||||
|
||||
void SearchBox::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
// +------------+---------------+------------+------+------------+
|
||||
// | |(ExPaddingGapM)| | | |
|
||||
// | +---------------+ | | |
|
||||
// |(HPaddingXs)| <lineEdit> |(HPaddingXs)|<icon>|(HPaddingXs)|
|
||||
// | +---------------+ | | |
|
||||
// | |(ExPaddingGapM)| | | |
|
||||
// +------------+---------------+------------+------+------------+
|
||||
|
||||
QPainter p(this);
|
||||
|
||||
paintCommonBackground(&p, rect(), this);
|
||||
const QPixmap icon = searchBoxIcon();
|
||||
const QSize iconS = icon.deviceIndependentSize().toSize();
|
||||
const QPoint iconPos(width() - HPaddingXs - iconS.width(), (height() - iconS.height()) / 2);
|
||||
p.drawPixmap(iconPos, icon);
|
||||
|
||||
QLineEdit::paintEvent(event);
|
||||
}
|
||||
|
||||
constexpr TextFormat ComboBoxTf
|
||||
{Theme::Token_Text_Muted, StyleHelper::UiElementIconActive,
|
||||
Qt::AlignLeft | Qt::AlignVCenter | Qt::TextDontClip};
|
||||
|
||||
static const QPixmap &comboBoxIcon()
|
||||
{
|
||||
static const QPixmap icon = Icon({{FilePath::fromString(":/core/images/expandarrow"),
|
||||
ComboBoxTf.themeColor}}, Icon::Tint).pixmap();
|
||||
return icon;
|
||||
}
|
||||
|
||||
ComboBox::ComboBox(QWidget *parent)
|
||||
: QComboBox(parent)
|
||||
{
|
||||
setFont(ComboBoxTf.font());
|
||||
setMouseTracking(true);
|
||||
|
||||
const QSize iconSize = comboBoxIcon().deviceIndependentSize().toSize();
|
||||
setContentsMargins({HPaddingXs, VPaddingXs,
|
||||
HGapXxs + iconSize.width() + HPaddingXs, VPaddingXs});
|
||||
}
|
||||
|
||||
QSize ComboBox::sizeHint() const
|
||||
{
|
||||
const QSize parentS = QComboBox::sizeHint();
|
||||
const QMargins margins = contentsMargins();
|
||||
return {margins.left() + parentS.width() + margins.right(),
|
||||
margins.top() + ComboBoxTf.lineHeight() + margins.bottom()};
|
||||
}
|
||||
|
||||
void ComboBox::enterEvent(QEnterEvent *event)
|
||||
{
|
||||
QComboBox::enterEvent(event);
|
||||
update();
|
||||
}
|
||||
|
||||
void ComboBox::leaveEvent(QEvent *event)
|
||||
{
|
||||
QComboBox::leaveEvent(event);
|
||||
update();
|
||||
}
|
||||
|
||||
void ComboBox::paintEvent(QPaintEvent *)
|
||||
{
|
||||
// +------------+-------------+---------+-------+------------+
|
||||
// | | (VPaddingXs)| | | |
|
||||
// | +-------------+ | | |
|
||||
// |(HPaddingXs)|<currentItem>|(HGapXxs)|<arrow>|(HPaddingXs)|
|
||||
// | +-------------+ | | |
|
||||
// | | (VPaddingXs)| | | |
|
||||
// +------------+-------------+---------+-------+------------+
|
||||
|
||||
QPainter p(this);
|
||||
paintCommonBackground(&p, rect(), this);
|
||||
|
||||
const QMargins margins = contentsMargins();
|
||||
const QRect textR(margins.left(), margins.top(),
|
||||
width() - margins.right(), ComboBoxTf.lineHeight());
|
||||
p.setFont(ComboBoxTf.font());
|
||||
p.setPen(ComboBoxTf.color());
|
||||
p.drawText(textR, ComboBoxTf.drawTextFlags, currentText());
|
||||
|
||||
const QPixmap icon = comboBoxIcon();
|
||||
const QSize iconS = icon.deviceIndependentSize().toSize();
|
||||
const QPoint iconPos(width() - HPaddingXs - iconS.width(), (height() - iconS.height()) / 2);
|
||||
p.drawPixmap(iconPos, icon);
|
||||
}
|
||||
|
||||
GridView::GridView(QWidget *parent)
|
||||
@@ -115,7 +435,7 @@ GridView::GridView(QWidget *parent)
|
||||
setUniformItemSizes(true);
|
||||
|
||||
QPalette pal;
|
||||
pal.setColor(QPalette::Base, themeColor(Theme::Welcome_BackgroundSecondaryColor));
|
||||
pal.setColor(QPalette::Base, themeColor(Theme::Token_Background_Default));
|
||||
setPalette(pal); // Makes a difference on Mac.
|
||||
}
|
||||
|
||||
@@ -147,10 +467,11 @@ bool SectionGridView::hasHeightForWidth() const
|
||||
|
||||
int SectionGridView::heightForWidth(int width) const
|
||||
{
|
||||
const int columnCount = qMax(1, width / Core::WelcomePageHelpers::GridItemWidth);
|
||||
const QSize itemSize = ListItemDelegate::itemSize();
|
||||
const int columnCount = qMax(1, width / itemSize.width());
|
||||
const int rowCount = (model()->rowCount() + columnCount - 1) / columnCount;
|
||||
const int maxRowCount = m_maxRows ? std::min(*m_maxRows, rowCount) : rowCount;
|
||||
return maxRowCount * Core::WelcomePageHelpers::GridItemHeight;
|
||||
return maxRowCount * itemSize.height();
|
||||
}
|
||||
|
||||
void SectionGridView::wheelEvent(QWheelEvent *e)
|
||||
@@ -165,8 +486,9 @@ bool SectionGridView::event(QEvent *e)
|
||||
{
|
||||
if (e->type() == QEvent::Resize) {
|
||||
const auto itemsFit = [this](const QSize &size) {
|
||||
const int maxColumns = std::max(size.width() / WelcomePageHelpers::GridItemWidth, 1);
|
||||
const int maxRows = std::max(size.height() / WelcomePageHelpers::GridItemHeight, 1);
|
||||
const QSize itemSize = ListItemDelegate::itemSize();
|
||||
const int maxColumns = std::max(size.width() / itemSize.width(), 1);
|
||||
const int maxRows = std::max(size.height() / itemSize.height(), 1);
|
||||
const int maxItems = maxColumns * maxRows;
|
||||
const int items = model()->rowCount();
|
||||
return maxItems >= items;
|
||||
@@ -426,47 +748,123 @@ bool ListModelFilter::leaveFilterAcceptsRowBeforeFiltering(const ListItem *, boo
|
||||
return false;
|
||||
}
|
||||
|
||||
ListItemDelegate::ListItemDelegate()
|
||||
: backgroundPrimaryColor(themeColor(Theme::Welcome_BackgroundPrimaryColor))
|
||||
, backgroundSecondaryColor(themeColor(Theme::Welcome_BackgroundSecondaryColor))
|
||||
, foregroundPrimaryColor(themeColor(Theme::Welcome_ForegroundPrimaryColor))
|
||||
, foregroundSecondaryColor(themeColor(Theme::Welcome_ForegroundSecondaryColor))
|
||||
, hoverColor(themeColor(Theme::Welcome_HoverColor))
|
||||
, textColor(themeColor(Theme::Welcome_TextColor))
|
||||
constexpr TextFormat titleTF {Theme::Token_Text_Default, StyleHelper::UiElementIconActive};
|
||||
constexpr TextFormat descriptionTF {titleTF.themeColor, StyleHelper::UiElementCaption};
|
||||
constexpr TextFormat tagsLabelTF {Theme::Token_Text_Muted, StyleHelper::UiElementCaptionStrong};
|
||||
constexpr TextFormat tagsTF {Theme::Token_Accent_Default, tagsLabelTF.uiElement};
|
||||
|
||||
constexpr qreal itemOutlineWidth = 1;
|
||||
constexpr qreal itemCornerRounding = 6;
|
||||
constexpr int thumbnailAreaBorderWidth = 1;
|
||||
constexpr QSize thumbnailAreaSize =
|
||||
WelcomeThumbnailSize.grownBy({thumbnailAreaBorderWidth, thumbnailAreaBorderWidth,
|
||||
thumbnailAreaBorderWidth, thumbnailAreaBorderWidth});
|
||||
constexpr int tagsRowsCount = 1;
|
||||
constexpr int tagsHGap = ExPaddingGapM;
|
||||
|
||||
constexpr QEasingCurve::Type hoverEasing = QEasingCurve::OutCubic;
|
||||
constexpr std::chrono::milliseconds hoverDuration(300);
|
||||
constexpr int hoverBlurRadius = 50;
|
||||
constexpr qreal hoverBlurOpacity = 0.175;
|
||||
|
||||
QSize ListItemDelegate::itemSize()
|
||||
{
|
||||
const int tagsTfLineHeight = tagsTF.lineHeight();
|
||||
const int width =
|
||||
ExPaddingGapL
|
||||
+ thumbnailAreaSize.width()
|
||||
+ ExPaddingGapL;
|
||||
const int height =
|
||||
ExPaddingGapL
|
||||
+ thumbnailAreaSize.height()
|
||||
+ VGapL
|
||||
+ titleTF.lineHeight()
|
||||
+ VGapL
|
||||
+ tagsTfLineHeight
|
||||
+ (tagsTfLineHeight + ExPaddingGapS) * (tagsRowsCount - 1) // If more than one row
|
||||
+ ExPaddingGapL;
|
||||
return {width + ExVPaddingGapXl, height + ExVPaddingGapXl};
|
||||
}
|
||||
|
||||
void ListItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
|
||||
const QModelIndex &index) const
|
||||
{
|
||||
// Unhovered tile
|
||||
// +---------------+------------------+---------------+-----------------+
|
||||
// | | (ExPaddingGapL) | | |
|
||||
// | +------------------+ | |
|
||||
// | | <thumbnail> | | |
|
||||
// | +------------------+ | |
|
||||
// | | (VGapL) | | |
|
||||
// | +------------------+ | |
|
||||
// |(ExPaddingGapL)| <title> |(ExPaddingGapL)|(ExVPaddingGapXs)|
|
||||
// | +------------------+ | |
|
||||
// | | (VGapL) | | |
|
||||
// | +-----------+------+ | |
|
||||
// | |<tagsLabel>|<tags>| | |
|
||||
// | +-----------+------+ | |
|
||||
// | | (ExPaddingGapL) | | |
|
||||
// +---------------+------------------+---------------+-----------------+
|
||||
// | (ExVPaddingGapXs) |
|
||||
// +--------------------------------------------------------------------+
|
||||
//
|
||||
// Hovered, final animation state of the are above tagsLabel
|
||||
// +---------------+------------------+---------------+
|
||||
// | | (ExPaddingGapL) | |
|
||||
// | +------------------+ |
|
||||
// | | <title> | |
|
||||
// | +------------------+ |
|
||||
// | | (ExPaddingGapS) | |
|
||||
// |(ExPaddingGapL)+------------------+(ExPaddingGapL)| ...
|
||||
// | | <hr> | |
|
||||
// | +------------------+ |
|
||||
// | | (ExPaddingGapS) | |
|
||||
// | +------------------+ |
|
||||
// | | <description> | |
|
||||
// +---------------+------------------+---------------+
|
||||
// ...
|
||||
|
||||
const ListItem *item = index.data(ListModel::ItemRole).value<Core::ListItem *>();
|
||||
|
||||
const QRect rc = option.rect;
|
||||
const QRect tileRect(0, 0, rc.width() - GridItemGap, rc.height() - GridItemGap);
|
||||
const QSize thumbnailBgSize = GridItemImageSize.grownBy(QMargins(1, 1, 1, 1));
|
||||
const QRect thumbnailBgRect((tileRect.width() - thumbnailBgSize.width()) / 2, GridItemGap,
|
||||
thumbnailBgSize.width(), thumbnailBgSize.height());
|
||||
const QRect textArea = tileRect.adjusted(GridItemGap, GridItemGap, -GridItemGap, -GridItemGap);
|
||||
const QFont tagsLabelFont = tagsLabelTF.font();
|
||||
const QFontMetrics tagsLabelFM(tagsLabelFont);
|
||||
const QFont descriptionFont = descriptionTF.font();
|
||||
const QFontMetrics descriptionFM(descriptionFont);
|
||||
|
||||
const QRect bgRGlobal = option.rect.adjusted(0, 0, -ExVPaddingGapXl, -ExVPaddingGapXl);
|
||||
const QRect bgR = bgRGlobal.translated(-option.rect.topLeft());
|
||||
const QRect thumbnailAreaR(bgR.left() + ExPaddingGapL, bgR.top() + ExPaddingGapL,
|
||||
thumbnailAreaSize.width(), thumbnailAreaSize.height());
|
||||
const QRect titleR(thumbnailAreaR.left(), thumbnailAreaR.bottom() + VGapL + 1,
|
||||
thumbnailAreaR.width(), titleTF.lineHeight());
|
||||
const QString tagsLabelText = Tr::tr("Tags:");
|
||||
const int tagsLabelTextWidth = tagsLabelFM.horizontalAdvance(tagsLabelText);
|
||||
const QRect tagsLabelR(titleR.left(), titleR.bottom() + VGapL + 1,
|
||||
tagsLabelTextWidth, tagsTF.lineHeight());
|
||||
const QRect tagsR(tagsLabelR.right() + 1 + tagsHGap, tagsLabelR.top(),
|
||||
bgR.right() - ExPaddingGapL - tagsLabelR.right() - tagsHGap,
|
||||
tagsLabelR.height()
|
||||
+ (tagsLabelR.height() + ExPaddingGapS) * (tagsRowsCount - 1));
|
||||
QTC_CHECK(option.rect.height() == tagsR.bottom() + 1 + ExPaddingGapL + ExVPaddingGapXl);
|
||||
QTC_CHECK(option.rect.width() == tagsR.right() + 1 + ExPaddingGapL + ExVPaddingGapXl);
|
||||
|
||||
QTextOption wrapTO;
|
||||
wrapTO.setWrapMode(QTextOption::WordWrap);
|
||||
|
||||
const bool hovered = option.state & QStyle::State_MouseOver;
|
||||
|
||||
constexpr int TagsSeparatorY = GridItemHeight - GridItemGap - 52;
|
||||
constexpr int tagsBase = TagsSeparatorY + 17;
|
||||
constexpr int shiftY = TagsSeparatorY - 16;
|
||||
constexpr int nameY = TagsSeparatorY - 20;
|
||||
|
||||
const QRect textRect = textArea.translated(0, nameY);
|
||||
const QFont descriptionFont = sizedFont(11, option.widget);
|
||||
|
||||
painter->save();
|
||||
painter->translate(rc.topLeft());
|
||||
painter->translate(bgRGlobal.topLeft());
|
||||
|
||||
painter->fillRect(tileRect, hovered ? hoverColor : backgroundPrimaryColor);
|
||||
const QColor fill(themeColor(hovered ? Theme::Token_Foreground_Muted
|
||||
: Theme::Token_Background_Muted));
|
||||
const QPen pen(themeColor(hovered ? Theme::Token_Foreground_Muted
|
||||
: Theme::Token_Stroke_Subtle), itemOutlineWidth);
|
||||
WelcomePageHelpers::drawCardBackground(painter, bgR, fill, pen, itemCornerRounding);
|
||||
|
||||
QTextOption wrapped;
|
||||
wrapped.setWrapMode(QTextOption::WordWrap);
|
||||
const int shiftY = thumbnailAreaR.bottom();
|
||||
int offset = 0;
|
||||
float animationProgress = 0; // Linear increase from 0.0 to 1.0 during hover animation
|
||||
qreal animationProgress = 0; // Linear increase from 0.0 to 1.0 during hover animation
|
||||
if (hovered) {
|
||||
if (index != m_previousIndex) {
|
||||
m_previousIndex = index;
|
||||
@@ -476,12 +874,12 @@ void ListItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opti
|
||||
m_currentWidget = qobject_cast<QAbstractItemView *>(
|
||||
const_cast<QWidget *>(option.widget));
|
||||
}
|
||||
constexpr float hoverAnimationDuration = 260;
|
||||
animationProgress = m_startTime.elapsed() / hoverAnimationDuration;
|
||||
animationProgress = qreal(m_startTime.elapsed()) / hoverDuration.count();
|
||||
if (animationProgress < 1) {
|
||||
static const QEasingCurve animationCurve(QEasingCurve::OutCubic);
|
||||
static const QEasingCurve animationCurve(hoverEasing);
|
||||
offset = animationCurve.valueForProgress(animationProgress) * shiftY;
|
||||
QTimer::singleShot(10, this, &ListItemDelegate::goon);
|
||||
using namespace std::chrono_literals;
|
||||
QTimer::singleShot(10ms, this, &ListItemDelegate::goon);
|
||||
} else {
|
||||
offset = shiftY;
|
||||
}
|
||||
@@ -489,124 +887,125 @@ void ListItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opti
|
||||
m_previousIndex = QModelIndex();
|
||||
}
|
||||
|
||||
const QRect shiftedTextRect = textRect.adjusted(0, -offset, 0, -offset);
|
||||
|
||||
// The pixmap.
|
||||
const QPixmap pm = index.data(ListModel::ItemImageRole).value<QPixmap>();
|
||||
QPoint thumbnailPos = thumbnailBgRect.center();
|
||||
QPoint thumbnailPos = thumbnailAreaR.center();
|
||||
if (!pm.isNull()) {
|
||||
painter->fillRect(thumbnailBgRect, backgroundSecondaryColor);
|
||||
painter->fillRect(thumbnailAreaR, themeColor(Theme::Token_Background_Default));
|
||||
|
||||
thumbnailPos.rx() -= pm.width() / pm.devicePixelRatio() / 2 - 1;
|
||||
thumbnailPos.ry() -= pm.height() / pm.devicePixelRatio() / 2 - 1;
|
||||
painter->drawPixmap(thumbnailPos, pm);
|
||||
|
||||
painter->setPen(foregroundPrimaryColor);
|
||||
drawPixmapOverlay(item, painter, option, thumbnailBgRect);
|
||||
painter->setPen(titleTF.color());
|
||||
drawPixmapOverlay(item, painter, option, thumbnailAreaR);
|
||||
} else {
|
||||
// The description text as fallback.
|
||||
painter->setPen(textColor);
|
||||
painter->setFont(descriptionFont);
|
||||
painter->drawText(textArea, item->description, wrapped);
|
||||
painter->setPen(descriptionTF.color());
|
||||
painter->setFont(descriptionTF.font());
|
||||
painter->drawText(thumbnailAreaR, item->description, wrapTO);
|
||||
}
|
||||
|
||||
// The description background
|
||||
QRect backgroundPortionR = bgR;
|
||||
if (offset) {
|
||||
QRect backgroundPortionRect = tileRect;
|
||||
backgroundPortionRect.setTop(shiftY - offset);
|
||||
backgroundPortionR.setTop(shiftY - offset);
|
||||
if (!pm.isNull()) {
|
||||
if (m_blurredThumbnail.isNull()) {
|
||||
constexpr int blurRadius = 50;
|
||||
QImage thumbnail(tileRect.size() + QSize(blurRadius, blurRadius) * 2,
|
||||
constexpr int filterMargin = hoverBlurRadius;
|
||||
QImage thumbnail(bgR.size() + QSize(filterMargin, filterMargin) * 2,
|
||||
QImage::Format_ARGB32_Premultiplied);
|
||||
thumbnail.fill(hoverColor);
|
||||
thumbnail.fill(themeColor(Theme::Token_Foreground_Muted));
|
||||
QPainter thumbnailPainter(&thumbnail);
|
||||
thumbnailPainter.translate(blurRadius, blurRadius);
|
||||
thumbnailPainter.fillRect(thumbnailBgRect, backgroundSecondaryColor);
|
||||
thumbnailPainter.translate(filterMargin, filterMargin);
|
||||
thumbnailPainter.fillRect(thumbnailAreaR,
|
||||
themeColor(Theme::Token_Background_Default));
|
||||
thumbnailPainter.drawPixmap(thumbnailPos, pm);
|
||||
thumbnailPainter.setPen(foregroundPrimaryColor);
|
||||
drawPixmapOverlay(item, &thumbnailPainter, option, thumbnailBgRect);
|
||||
thumbnailPainter.setPen(titleTF.color());
|
||||
drawPixmapOverlay(item, &thumbnailPainter, option, thumbnailAreaR);
|
||||
thumbnailPainter.setOpacity(1.0 - hoverBlurOpacity);
|
||||
thumbnailPainter.fillRect(thumbnail.rect(),
|
||||
themeColor(Theme::Token_Foreground_Muted));
|
||||
thumbnailPainter.end();
|
||||
qt_blurImage(thumbnail, hoverBlurRadius, false, false);
|
||||
|
||||
m_blurredThumbnail = QPixmap(tileRect.size());
|
||||
QPainter blurredThumbnailPainter(&m_blurredThumbnail);
|
||||
blurredThumbnailPainter.translate(-blurRadius, -blurRadius);
|
||||
qt_blurImage(&blurredThumbnailPainter, thumbnail, blurRadius, false, false);
|
||||
blurredThumbnailPainter.setOpacity(0.825);
|
||||
blurredThumbnailPainter.fillRect(tileRect, hoverColor);
|
||||
QImage mask(thumbnail.size(), QImage::Format_Grayscale8);
|
||||
mask.fill(Qt::black);
|
||||
QPainter maskPainter(&mask);
|
||||
const QRect maskR = bgR.translated(filterMargin, filterMargin)
|
||||
.adjusted(1, 1, -1, -1);
|
||||
WelcomePageHelpers::drawCardBackground(&maskPainter, maskR,
|
||||
Qt::white, Qt::NoPen, itemCornerRounding);
|
||||
thumbnail.setAlphaChannel(mask);
|
||||
|
||||
m_blurredThumbnail = QPixmap::fromImage(
|
||||
thumbnail.copy({filterMargin, filterMargin, bgR.width(), bgR.height()}));
|
||||
}
|
||||
const QPixmap thumbnailPortionPM = m_blurredThumbnail.copy(backgroundPortionRect);
|
||||
painter->drawPixmap(backgroundPortionRect.topLeft(), thumbnailPortionPM);
|
||||
const QPixmap thumbnailPortionPM = m_blurredThumbnail.copy(backgroundPortionR);
|
||||
painter->drawPixmap(backgroundPortionR.topLeft(), thumbnailPortionPM);
|
||||
} else {
|
||||
painter->fillRect(backgroundPortionRect, hoverColor);
|
||||
painter->fillRect(thumbnailAreaR, themeColor(Theme::Token_Foreground_Muted));
|
||||
}
|
||||
}
|
||||
|
||||
// The description Text (unhovered or hovered)
|
||||
painter->setPen(textColor);
|
||||
painter->setFont(sizedFont(13, option.widget)); // Title font
|
||||
painter->setPen(titleTF.color());
|
||||
painter->setFont(titleTF.font());
|
||||
if (offset) {
|
||||
// The title of the example
|
||||
const QRectF nameRect = painter->boundingRect(shiftedTextRect, item->name, wrapped);
|
||||
painter->drawText(nameRect, item->name, wrapped);
|
||||
const QRect shiftedTitleR = thumbnailAreaR.translated(backgroundPortionR.topLeft());
|
||||
const QRect titleR = painter->boundingRect(shiftedTitleR, item->name, wrapTO).toRect();
|
||||
painter->drawText(shiftedTitleR, item->name, wrapTO);
|
||||
|
||||
painter->setOpacity(animationProgress); // "fade in" separator line and description
|
||||
|
||||
// The separator line below the example title.
|
||||
const int ll = nameRect.height() + 3;
|
||||
const QLine line = QLine(0, ll, textArea.width(), ll).translated(shiftedTextRect.topLeft());
|
||||
painter->setPen(foregroundSecondaryColor);
|
||||
painter->setOpacity(animationProgress); // "fade in" separator line and description
|
||||
painter->drawLine(line);
|
||||
const QRect hrR(titleR.x(), titleR.bottom() + 1 + ExPaddingGapS, thumbnailAreaR.width(), 1);
|
||||
painter->fillRect(hrR, themeColor(Theme::Token_Stroke_Muted));
|
||||
|
||||
// The description text.
|
||||
const int dd = ll + 5;
|
||||
const QRect descRect = shiftedTextRect.adjusted(0, dd, 0, dd);
|
||||
painter->setPen(textColor);
|
||||
painter->setFont(descriptionFont);
|
||||
painter->drawText(descRect, item->description, wrapped);
|
||||
const QRect descriptionR(hrR.x(), hrR.bottom() + 1 + ExPaddingGapS,
|
||||
thumbnailAreaR.width(), shiftY);
|
||||
painter->setPen(descriptionTF.color());
|
||||
painter->setFont(descriptionTF.font());
|
||||
painter->drawText(descriptionR, item->description, wrapTO);
|
||||
painter->setOpacity(1);
|
||||
} else {
|
||||
// The title of the example
|
||||
const QString elidedName = painter->fontMetrics()
|
||||
.elidedText(item->name, Qt::ElideRight, textRect.width());
|
||||
painter->drawText(textRect, elidedName);
|
||||
.elidedText(item->name, Qt::ElideRight, titleR.width());
|
||||
painter->drawText(titleR, titleTF.drawTextFlags, elidedName);
|
||||
}
|
||||
|
||||
// Separator line between text and 'Tags:' section
|
||||
painter->setPen(foregroundSecondaryColor);
|
||||
painter->drawLine(QLineF(textArea.topLeft(), textArea.topRight())
|
||||
.translated(0, TagsSeparatorY));
|
||||
|
||||
// The 'Tags:' section
|
||||
painter->setPen(foregroundPrimaryColor);
|
||||
const QFont tagsFont = sizedFont(10, option.widget);
|
||||
painter->setFont(tagsFont);
|
||||
const QFontMetrics fm = painter->fontMetrics();
|
||||
const QString tagsLabelText = Tr::tr("Tags:");
|
||||
constexpr int tagsHorSpacing = 5;
|
||||
const QRect tagsLabelRect =
|
||||
QRect(0, 0, fm.horizontalAdvance(tagsLabelText) + tagsHorSpacing, fm.height())
|
||||
.translated(textArea.x(), tagsBase);
|
||||
painter->drawText(tagsLabelRect, tagsLabelText);
|
||||
painter->setPen(tagsLabelTF.color());
|
||||
painter->setFont(tagsLabelTF.font());
|
||||
painter->drawText(tagsLabelR, tagsLabelTF.drawTextFlags, tagsLabelText);
|
||||
|
||||
painter->setPen(themeColor(Theme::Welcome_LinkColor));
|
||||
int emptyTagRowsLeft = 2;
|
||||
const QFontMetrics fm = painter->fontMetrics();
|
||||
|
||||
painter->setPen(tagsTF.color());
|
||||
painter->setFont(tagsTF.font());
|
||||
int emptyTagRowsLeft = tagsRowsCount;
|
||||
int xx = 0;
|
||||
int yy = 0;
|
||||
const bool populateTagsRects = m_currentTagRects.empty();
|
||||
for (const QString &tag : item->tags) {
|
||||
const int ww = fm.horizontalAdvance(tag) + tagsHorSpacing;
|
||||
if (xx + ww > textArea.width() - tagsLabelRect.width()) {
|
||||
const int ww = fm.horizontalAdvance(tag);
|
||||
if (xx + ww > tagsR.width()) {
|
||||
if (--emptyTagRowsLeft == 0)
|
||||
break;
|
||||
yy += fm.lineSpacing();
|
||||
xx = 0;
|
||||
}
|
||||
const QRect tagRect = QRect(xx, yy, ww, tagsLabelRect.height())
|
||||
.translated(tagsLabelRect.topRight());
|
||||
painter->drawText(tagRect, tag);
|
||||
if (populateTagsRects)
|
||||
m_currentTagRects.append({ tag, tagRect });
|
||||
xx += ww;
|
||||
const QRect tagRect = QRect(xx, yy, ww, tagsLabelR.height()).translated(tagsR.topLeft());
|
||||
painter->drawText(tagRect, tagsTF.drawTextFlags, tag);
|
||||
if (populateTagsRects) {
|
||||
constexpr int grow = tagsHGap / 2;
|
||||
const QRect tagMouseArea = tagRect.adjusted(-grow, -grow, grow, grow);
|
||||
m_currentTagRects.append({ tag, tagMouseArea });
|
||||
}
|
||||
xx += ww + tagsHGap;
|
||||
}
|
||||
|
||||
painter->restore();
|
||||
@@ -642,7 +1041,7 @@ bool ListItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *model,
|
||||
|
||||
QSize ListItemDelegate::sizeHint(const QStyleOptionViewItem &, const QModelIndex &) const
|
||||
{
|
||||
return {GridItemWidth, GridItemHeight};
|
||||
return itemSize();
|
||||
}
|
||||
|
||||
void ListItemDelegate::drawPixmapOverlay(const ListItem *, QPainter *,
|
||||
@@ -682,6 +1081,7 @@ SectionedGridView::SectionedGridView(QWidget *parent)
|
||||
|
||||
auto sectionedView = new QWidget;
|
||||
auto layout = new QVBoxLayout;
|
||||
layout->setSpacing(0);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->addStretch(1);
|
||||
sectionedView->setLayout(layout);
|
||||
@@ -744,24 +1144,24 @@ void SectionedGridView::setSearchString(const QString &searchString)
|
||||
filterModel->setSearchString(searchString);
|
||||
}
|
||||
|
||||
static QWidget *createSeparator(QWidget *parent)
|
||||
static QLabel *createTitleLabel(const QString &text, QWidget *parent = nullptr)
|
||||
{
|
||||
QWidget *line = Layouting::createHr(parent);
|
||||
QSizePolicy linePolicy(QSizePolicy::Expanding, QSizePolicy::Ignored);
|
||||
linePolicy.setHorizontalStretch(2);
|
||||
line->setSizePolicy(linePolicy);
|
||||
QPalette pal = line->palette();
|
||||
pal.setColor(QPalette::Dark, Qt::transparent);
|
||||
pal.setColor(QPalette::Light, themeColor(Theme::Welcome_ForegroundSecondaryColor));
|
||||
line->setPalette(pal);
|
||||
return line;
|
||||
constexpr TextFormat headerTitleTF {Theme::Token_Text_Muted, StyleHelper::UiElementH4};
|
||||
auto link = new QLabel(text, parent);
|
||||
link->setFont(headerTitleTF.font());
|
||||
QPalette pal = link->palette();
|
||||
pal.setColor(QPalette::WindowText, headerTitleTF.color());
|
||||
link->setPalette(pal);
|
||||
return link;
|
||||
}
|
||||
|
||||
static QLabel *createLinkLabel(const QString &text, QWidget *parent)
|
||||
{
|
||||
const QString linkColor = themeColor(Theme::Welcome_LinkColor).name();
|
||||
constexpr TextFormat headerLinkTF {Theme::Token_Accent_Default, StyleHelper::UiElementH6};
|
||||
const QString linkColor = themeColor(headerLinkTF.themeColor).name();
|
||||
auto link = new QLabel("<a href=\"link\" style=\"color: " + linkColor + ";\">"
|
||||
+ text + "</a>", parent);
|
||||
link->setFont(headerLinkTF.font());
|
||||
return link;
|
||||
}
|
||||
|
||||
@@ -797,14 +1197,13 @@ ListModel *SectionedGridView::addSection(const Section §ion, const QList<Lis
|
||||
connect(seeAllLink, &QLabel::linkActivated, this, [this, section] { zoomInSection(section); });
|
||||
using namespace Layouting;
|
||||
QWidget *sectionLabel = Row {
|
||||
section.name,
|
||||
createSeparator(this),
|
||||
createTitleLabel(section.name),
|
||||
st,
|
||||
seeAllLink,
|
||||
Space(HSpacing),
|
||||
noMargin
|
||||
Space(ExVPaddingGapXl),
|
||||
customMargin({0, ExPaddingGapL, 0, VPaddingL}),
|
||||
}.emerge();
|
||||
m_sectionLabels.append(sectionLabel);
|
||||
sectionLabel->setContentsMargins(0, ItemGap, 0, 0);
|
||||
auto scrollArea = qobject_cast<QScrollArea *>(widget(0));
|
||||
auto vbox = qobject_cast<QVBoxLayout *>(scrollArea->widget()->layout());
|
||||
|
||||
@@ -858,6 +1257,7 @@ void SectionedGridView::zoomInSection(const Section §ion)
|
||||
auto zoomedInWidget = new QWidget(this);
|
||||
auto layout = new QVBoxLayout;
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->setSpacing(0);
|
||||
zoomedInWidget->setLayout(layout);
|
||||
|
||||
QLabel *backLink = createLinkLabel("< " + Tr::tr("Back"), this);
|
||||
@@ -868,13 +1268,12 @@ void SectionedGridView::zoomInSection(const Section §ion)
|
||||
});
|
||||
using namespace Layouting;
|
||||
QWidget *sectionLabel = Row {
|
||||
section.name,
|
||||
createSeparator(this),
|
||||
createTitleLabel(section.name),
|
||||
st,
|
||||
backLink,
|
||||
Space(HSpacing),
|
||||
noMargin
|
||||
Space(ExVPaddingGapXl),
|
||||
customMargin({0, ExPaddingGapL, 0, VPaddingL}),
|
||||
}.emerge();
|
||||
sectionLabel->setContentsMargins(0, ItemGap, 0, 0);
|
||||
|
||||
auto gridView = new GridView(zoomedInWidget);
|
||||
gridView->setItemDelegate(m_itemDelegate);
|
||||
|
@@ -6,10 +6,17 @@
|
||||
#include "core_global.h"
|
||||
#include "iwelcomepage.h"
|
||||
|
||||
#include <utils/fancylineedit.h>
|
||||
#include <utils/stylehelper.h>
|
||||
#include <utils/theme/theme.h>
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QElapsedTimer>
|
||||
#include <QLabel>
|
||||
#include <QListView>
|
||||
#include <QPen>
|
||||
#include <QPointer>
|
||||
#include <QPushButton>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QStackedWidget>
|
||||
#include <QStyledItemDelegate>
|
||||
@@ -24,31 +31,111 @@ namespace Core {
|
||||
|
||||
namespace WelcomePageHelpers {
|
||||
|
||||
constexpr int HSpacing = 20;
|
||||
constexpr int ItemGap = 4;
|
||||
constexpr QSize WelcomeThumbnailSize(214, 160);
|
||||
|
||||
constexpr int GridItemGap = 3 * ItemGap;
|
||||
constexpr int GridItemWidth = 240 + GridItemGap; // Extra GridItemGap as "spacing"
|
||||
constexpr int GridItemHeight = GridItemWidth;
|
||||
constexpr QSize GridItemImageSize(GridItemWidth - GridItemGap
|
||||
- 2 * (GridItemGap + 1), // Horizontal margins + 1 pixel
|
||||
GridItemHeight - GridItemGap
|
||||
- GridItemGap - 1 // Upper margin + 1 pixel
|
||||
- 67); // Bottom margin (for title + tags)
|
||||
class CORE_EXPORT TextFormat {
|
||||
public:
|
||||
QColor color() const
|
||||
{
|
||||
return Utils::creatorTheme()->color(themeColor);
|
||||
}
|
||||
|
||||
CORE_EXPORT QWidget *panelBar(QWidget *parent = nullptr);
|
||||
QFont font(bool underlined = false) const
|
||||
{
|
||||
QFont result = Utils::StyleHelper::uiFont(uiElement);
|
||||
result.setUnderline(underlined);
|
||||
return result;
|
||||
}
|
||||
|
||||
int lineHeight() const
|
||||
{
|
||||
return Utils::StyleHelper::uiFontLineHeight(uiElement);
|
||||
}
|
||||
|
||||
const Utils::Theme::Color themeColor;
|
||||
const Utils::StyleHelper::UiElement uiElement;
|
||||
const int drawTextFlags = Qt::AlignLeft | Qt::AlignBottom | Qt::TextDontClip;
|
||||
};
|
||||
|
||||
CORE_EXPORT void setBackgroundColor(QWidget *widget, Utils::Theme::Color colorRole);
|
||||
constexpr qreal defaultCardBackgroundRounding = 3.75;
|
||||
CORE_EXPORT void drawCardBackground(QPainter *painter, const QRectF &rect,
|
||||
const QBrush &fill, const QPen &pen = QPen(Qt::NoPen),
|
||||
qreal rounding = 5.0);
|
||||
qreal rounding = defaultCardBackgroundRounding);
|
||||
CORE_EXPORT QWidget *createRule(Qt::Orientation orientation, QWidget *parent = nullptr);
|
||||
|
||||
} // namespace WelcomePageHelpers
|
||||
|
||||
class CORE_EXPORT SearchBox : public WelcomePageFrame
|
||||
class CORE_EXPORT Button : public QPushButton
|
||||
{
|
||||
public:
|
||||
enum Role {
|
||||
MediumPrimary,
|
||||
MediumSecondary,
|
||||
SmallPrimary,
|
||||
SmallSecondary,
|
||||
SmallList,
|
||||
SmallLink,
|
||||
};
|
||||
|
||||
explicit Button(const QString &text, Role role, QWidget *parent = nullptr);
|
||||
|
||||
QSize minimumSizeHint() const override;
|
||||
|
||||
void setPixmap(const QPixmap &newPixmap);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
private:
|
||||
void updateMargins();
|
||||
|
||||
const Role m_role = MediumPrimary;
|
||||
QPixmap m_pixmap;
|
||||
};
|
||||
|
||||
class CORE_EXPORT Label : public QLabel
|
||||
{
|
||||
public:
|
||||
enum Role {
|
||||
Primary,
|
||||
Secondary,
|
||||
};
|
||||
|
||||
explicit Label(const QString &text, Role role, QWidget *parent = nullptr);
|
||||
|
||||
private:
|
||||
const Role m_role = Primary;
|
||||
};
|
||||
|
||||
class CORE_EXPORT SearchBox : public QLineEdit
|
||||
{
|
||||
public:
|
||||
explicit SearchBox(QWidget *parent = nullptr);
|
||||
|
||||
Utils::FancyLineEdit *m_lineEdit = nullptr;
|
||||
QSize minimumSizeHint() const override;
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
protected:
|
||||
void enterEvent(QEnterEvent *event) override;
|
||||
void leaveEvent(QEvent *event) override;
|
||||
};
|
||||
|
||||
class CORE_EXPORT ComboBox : public QComboBox
|
||||
{
|
||||
public:
|
||||
explicit ComboBox(QWidget *parent = nullptr);
|
||||
|
||||
QSize sizeHint() const override;
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
|
||||
protected:
|
||||
void enterEvent(QEnterEvent *event) override;
|
||||
void leaveEvent(QEvent *event) override;
|
||||
};
|
||||
|
||||
class CORE_EXPORT GridView : public QListView
|
||||
@@ -146,7 +233,9 @@ class CORE_EXPORT ListItemDelegate : public QStyledItemDelegate
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ListItemDelegate();
|
||||
ListItemDelegate() = default;
|
||||
|
||||
static QSize itemSize();
|
||||
void paint(QPainter *painter, const QStyleOptionViewItem &option,
|
||||
const QModelIndex &index) const override;
|
||||
|
||||
@@ -165,13 +254,6 @@ protected:
|
||||
|
||||
void goon();
|
||||
|
||||
const QColor backgroundPrimaryColor;
|
||||
const QColor backgroundSecondaryColor;
|
||||
const QColor foregroundPrimaryColor;
|
||||
const QColor foregroundSecondaryColor;
|
||||
const QColor hoverColor;
|
||||
const QColor textColor;
|
||||
|
||||
private:
|
||||
mutable QPersistentModelIndex m_previousIndex;
|
||||
mutable QElapsedTimer m_startTime;
|
||||
|
@@ -242,27 +242,6 @@ void CppHighlighter::highlightBlock(const QString &text)
|
||||
|
||||
TextDocumentLayout::setFoldingIndent(currentBlock(), foldingIndent);
|
||||
|
||||
// optimization: if only the brace depth changes, we adjust subsequent blocks
|
||||
// to have QSyntaxHighlighter stop the rehighlighting
|
||||
int currentState = currentBlockState();
|
||||
if (currentState != -1) {
|
||||
int oldState = currentState & 0xff;
|
||||
int oldBraceDepth = currentState >> 8;
|
||||
if (oldState == tokenize.state() && oldBraceDepth != braceDepth) {
|
||||
TextDocumentLayout::FoldValidator foldValidor;
|
||||
foldValidor.setup(qobject_cast<TextDocumentLayout *>(document()->documentLayout()));
|
||||
int delta = braceDepth - oldBraceDepth;
|
||||
QTextBlock block = currentBlock().next();
|
||||
while (block.isValid() && block.userState() != -1) {
|
||||
TextDocumentLayout::changeBraceDepth(block, delta);
|
||||
TextDocumentLayout::changeFoldingIndent(block, delta);
|
||||
foldValidor.process(block);
|
||||
block = block.next();
|
||||
}
|
||||
foldValidor.finalize();
|
||||
}
|
||||
}
|
||||
|
||||
setCurrentBlockState((braceDepth << 8) | tokenize.state());
|
||||
TextDocumentLayout::setExpectedRawStringSuffix(currentBlock(),
|
||||
tokenize.expectedRawStringSuffix());
|
||||
|
@@ -20,10 +20,11 @@
|
||||
#include <projectexplorer/projectexplorer.h>
|
||||
#include <projectexplorer/projectmanager.h>
|
||||
|
||||
#include <texteditor/texteditor.h>
|
||||
#include <texteditor/codeassist/iassistproposal.h>
|
||||
#include <texteditor/codeassist/iassistproposalmodel.h>
|
||||
#include <texteditor/storagesettings.h>
|
||||
#include <texteditor/syntaxhighlighterrunner.h>
|
||||
#include <texteditor/texteditor.h>
|
||||
|
||||
#include <utils/environment.h>
|
||||
#include <utils/fileutils.h>
|
||||
@@ -221,6 +222,17 @@ bool TestCase::openCppEditor(const FilePath &filePath, TextEditor::BaseTextEdito
|
||||
s.m_addFinalNewLine = false;
|
||||
e->textDocument()->setStorageSettings(s);
|
||||
}
|
||||
|
||||
if (!QTest::qWaitFor(
|
||||
[e] {
|
||||
return e->editorWidget()
|
||||
->textDocument()
|
||||
->syntaxHighlighterRunner()
|
||||
->syntaxInfoUpdated();
|
||||
},
|
||||
5000))
|
||||
return false;
|
||||
|
||||
if (editorWidget) {
|
||||
if (CppEditorWidget *w = dynamic_cast<CppEditorWidget *>(e->editorWidget())) {
|
||||
*editorWidget = w;
|
||||
|
@@ -396,6 +396,12 @@ F2TestCase::F2TestCase(CppEditorAction action,
|
||||
BaseTextEditor *currentTextEditor = dynamic_cast<BaseTextEditor*>(currentEditor);
|
||||
QVERIFY(currentTextEditor);
|
||||
|
||||
if (useClangd) {
|
||||
QEXPECT_FAIL("matchFunctionSignatureFuzzy1Forward", "clangd returns decl loc", Abort);
|
||||
QEXPECT_FAIL("matchFunctionSignatureFuzzy2Forward", "clangd returns decl loc", Abort);
|
||||
QEXPECT_FAIL("matchFunctionSignatureFuzzy1Backward", "clangd returns def loc", Abort);
|
||||
QEXPECT_FAIL("matchFunctionSignatureFuzzy2Backward", "clangd returns def loc", Abort);
|
||||
}
|
||||
QCOMPARE(currentTextEditor->document()->filePath(), targetTestFile->filePath());
|
||||
int expectedLine, expectedColumn;
|
||||
if (useClangd && expectedVirtualFunctionProposal.size() == 1) {
|
||||
@@ -494,8 +500,11 @@ void FollowSymbolTest::initTestCase()
|
||||
return;
|
||||
|
||||
// Find suitable kit.
|
||||
// Qt is not actually required for the tests, but we need it for consistency with
|
||||
// configureAsExampleProject().
|
||||
// FIXME: Make configureAsExampleProject() work with non-Qt kits.
|
||||
F2TestCase::m_testKit = Utils::findOr(KitManager::kits(), nullptr, [](const Kit *k) {
|
||||
return k->isValid();
|
||||
return k->isValid() && !k->hasWarning() && k->value("QtSupport.QtInformation").isValid();
|
||||
});
|
||||
if (!F2TestCase::m_testKit)
|
||||
QSKIP("This test requires at least one kit to be present");
|
||||
|
@@ -29,14 +29,6 @@ using namespace Utils;
|
||||
|
||||
namespace ExtensionManager::Internal {
|
||||
|
||||
static QWidget *createVr(QWidget *parent = nullptr)
|
||||
{
|
||||
auto vr = new QWidget(parent);
|
||||
vr->setFixedWidth(1);
|
||||
setBackgroundColor(vr, Theme::Token_Stroke_Subtle);
|
||||
return vr;
|
||||
}
|
||||
|
||||
class CollapsingWidget : public QWidget
|
||||
{
|
||||
public:
|
||||
@@ -79,13 +71,13 @@ ExtensionManagerWidget::ExtensionManagerWidget()
|
||||
|
||||
using namespace Layouting;
|
||||
Row {
|
||||
createVr(),
|
||||
WelcomePageHelpers::createRule(Qt::Vertical),
|
||||
m_secondaryDescription,
|
||||
noMargin(), spacing(0),
|
||||
}.attachTo(m_secondarDescriptionWidget);
|
||||
|
||||
Row {
|
||||
createVr(),
|
||||
WelcomePageHelpers::createRule(Qt::Vertical),
|
||||
Row {
|
||||
m_primaryDescription,
|
||||
noMargin(),
|
||||
@@ -95,13 +87,13 @@ ExtensionManagerWidget::ExtensionManagerWidget()
|
||||
}.attachTo(descriptionColumns);
|
||||
|
||||
Row {
|
||||
Space(WelcomePageHelpers::HSpacing),
|
||||
Space(StyleHelper::SpacingTokens::ExVPaddingGapXl),
|
||||
m_leftColumn,
|
||||
descriptionColumns,
|
||||
noMargin(), spacing(0),
|
||||
}.attachTo(this);
|
||||
|
||||
setBackgroundColor(this, Theme::Token_Background_Default);
|
||||
WelcomePageHelpers::setBackgroundColor(this, Theme::Token_Background_Default);
|
||||
|
||||
connect(m_leftColumn, &ExtensionsBrowser::itemSelected,
|
||||
this, &ExtensionManagerWidget::updateView);
|
||||
@@ -132,7 +124,7 @@ void ExtensionManagerWidget::updateView(const QModelIndex ¤t,
|
||||
"margin-left: %3px; margin-right: %3px;")
|
||||
.arg(creatorTheme()->color(Theme::Token_Text_Default).name())
|
||||
.arg(creatorTheme()->color(Theme::Token_Background_Muted).name())
|
||||
.arg(WelcomePageHelpers::HSpacing);
|
||||
.arg(StyleHelper::SpacingTokens::ExVPaddingGapXl);
|
||||
const QString htmlStart = QString(R"(
|
||||
<html>
|
||||
<body style="%1">
|
||||
|
@@ -41,7 +41,7 @@ using PluginSpecList = QList<const PluginSpec *>;
|
||||
using Tags = QStringList;
|
||||
|
||||
constexpr QSize itemSize = {330, 86};
|
||||
constexpr int gapSize = 2 * WelcomePageHelpers::GridItemGap;
|
||||
constexpr int gapSize = StyleHelper::SpacingTokens::ExVPaddingGapXl;
|
||||
constexpr QSize cellSize = {itemSize.width() + gapSize, itemSize.height() + gapSize};
|
||||
|
||||
enum Role {
|
||||
@@ -62,16 +62,6 @@ ItemData itemData(const QModelIndex &index)
|
||||
};
|
||||
}
|
||||
|
||||
void setBackgroundColor(QWidget *widget, Theme::Color colorRole)
|
||||
{
|
||||
QPalette palette = creatorTheme()->palette();
|
||||
palette.setColor(QPalette::Window,
|
||||
creatorTheme()->color(colorRole));
|
||||
widget->setPalette(palette);
|
||||
widget->setBackgroundRole(QPalette::Window);
|
||||
widget->setAutoFillBackground(true);
|
||||
}
|
||||
|
||||
static QColor colorForExtensionName(const QString &name)
|
||||
{
|
||||
const size_t hash = qHash(name);
|
||||
@@ -390,7 +380,7 @@ public:
|
||||
}
|
||||
{
|
||||
constexpr int textX = 80;
|
||||
constexpr int rightMargin = 2 * WelcomePageHelpers::ItemGap;
|
||||
constexpr int rightMargin = StyleHelper::SpacingTokens::ExVPaddingGapXl;
|
||||
constexpr int maxTextWidth = itemSize.width() - textX - rightMargin;
|
||||
constexpr Qt::TextElideMode elideMode = Qt::ElideRight;
|
||||
|
||||
@@ -440,8 +430,7 @@ ExtensionsBrowser::ExtensionsBrowser()
|
||||
m_searchBox = new Core::SearchBox;
|
||||
m_searchBox->setFixedWidth(itemSize.width());
|
||||
|
||||
m_updateButton = new WelcomePageButton;
|
||||
m_updateButton->setText(Tr::tr("Install..."));
|
||||
m_updateButton = new Button(Tr::tr("Install..."), Button::MediumPrimary);
|
||||
|
||||
m_filterProxyModel = new QSortFilterProxyModel(this);
|
||||
m_filterProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
|
||||
@@ -469,9 +458,10 @@ ExtensionsBrowser::ExtensionsBrowser()
|
||||
noMargin(), spacing(0),
|
||||
}.attachTo(this);
|
||||
|
||||
setBackgroundColor(this, Theme::Token_Background_Default);
|
||||
setBackgroundColor(m_extensionsView, Theme::Token_Background_Default);
|
||||
setBackgroundColor(m_extensionsView->viewport(), Theme::Token_Background_Default);
|
||||
WelcomePageHelpers::setBackgroundColor(this, Theme::Token_Background_Default);
|
||||
WelcomePageHelpers::setBackgroundColor(m_extensionsView, Theme::Token_Background_Default);
|
||||
WelcomePageHelpers::setBackgroundColor(m_extensionsView->viewport(),
|
||||
Theme::Token_Background_Default);
|
||||
|
||||
auto updateModel = [this] {
|
||||
m_model.reset(extensionsModel());
|
||||
@@ -488,7 +478,7 @@ ExtensionsBrowser::ExtensionsBrowser()
|
||||
|
||||
connect(ExtensionSystem::PluginManager::instance(),
|
||||
&ExtensionSystem::PluginManager::pluginsChanged, this, updateModel);
|
||||
connect(m_searchBox->m_lineEdit, &Utils::FancyLineEdit::textChanged,
|
||||
connect(m_searchBox, &QLineEdit::textChanged,
|
||||
m_filterProxyModel, &QSortFilterProxyModel::setFilterWildcard);
|
||||
}
|
||||
|
||||
|
@@ -10,7 +10,9 @@
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QItemSelectionModel;
|
||||
class QLineEdit;
|
||||
class QListView;
|
||||
class QPushButton;
|
||||
class QSortFilterProxyModel;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
@@ -19,11 +21,6 @@ namespace ExtensionSystem
|
||||
class PluginSpec;
|
||||
}
|
||||
|
||||
namespace Core {
|
||||
class SearchBox;
|
||||
class WelcomePageButton;
|
||||
}
|
||||
|
||||
namespace ExtensionManager::Internal {
|
||||
|
||||
using PluginSpecList = QList<const ExtensionSystem::PluginSpec *>;
|
||||
@@ -42,7 +39,6 @@ struct ItemData {
|
||||
};
|
||||
|
||||
ItemData itemData(const QModelIndex &index);
|
||||
void setBackgroundColor(QWidget *widget, Utils::Theme::Color colorRole);
|
||||
|
||||
class ExtensionsBrowser final : public QWidget
|
||||
{
|
||||
@@ -61,8 +57,8 @@ private:
|
||||
int extraListViewWidth() const; // Space for scrollbar, etc.
|
||||
|
||||
QScopedPointer<QStandardItemModel> m_model;
|
||||
Core::SearchBox *m_searchBox;
|
||||
Core::WelcomePageButton *m_updateButton;
|
||||
QLineEdit *m_searchBox;
|
||||
QPushButton *m_updateButton;
|
||||
QListView *m_extensionsView;
|
||||
QItemSelectionModel *m_selectionModel = nullptr;
|
||||
QSortFilterProxyModel *m_filterProxyModel;
|
||||
|
@@ -189,7 +189,6 @@ void GlslHighlighter::highlightBlock(const QString &text)
|
||||
TextDocumentLayout::setParentheses(currentBlock(), parentheses);
|
||||
|
||||
// if the block is ifdefed out, we only store the parentheses, but
|
||||
|
||||
// do not adjust the brace depth.
|
||||
if (TextDocumentLayout::ifdefedOut(currentBlock())) {
|
||||
braceDepth = initialBraceDepth;
|
||||
@@ -198,23 +197,6 @@ void GlslHighlighter::highlightBlock(const QString &text)
|
||||
|
||||
TextDocumentLayout::setFoldingIndent(currentBlock(), foldingIndent);
|
||||
|
||||
// optimization: if only the brace depth changes, we adjust subsequent blocks
|
||||
// to have QSyntaxHighlighter stop the rehighlighting
|
||||
int currentState = currentBlockState();
|
||||
if (currentState != -1) {
|
||||
int oldState = currentState & 0xff;
|
||||
int oldBraceDepth = currentState >> 8;
|
||||
if (oldState == lex.state() && oldBraceDepth != braceDepth) {
|
||||
int delta = braceDepth - oldBraceDepth;
|
||||
QTextBlock block = currentBlock().next();
|
||||
while (block.isValid() && block.userState() != -1) {
|
||||
TextDocumentLayout::changeBraceDepth(block, delta);
|
||||
TextDocumentLayout::changeFoldingIndent(block, delta);
|
||||
block = block.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setCurrentBlockState((braceDepth << 8) | lex.state());
|
||||
}
|
||||
|
||||
|
@@ -269,7 +269,7 @@ void SectionedProducts::onImageDownloadFinished(QNetworkReply *reply)
|
||||
if (pixmap.loadFromData(data, imageFormat.toLatin1())) {
|
||||
const QString url = imageUrl.toString();
|
||||
const int dpr = qApp->devicePixelRatio();
|
||||
pixmap = pixmap.scaled(WelcomePageHelpers::GridItemImageSize * dpr,
|
||||
pixmap = pixmap.scaled(WelcomePageHelpers::WelcomeThumbnailSize * dpr,
|
||||
Qt::KeepAspectRatio,
|
||||
Qt::SmoothTransformation);
|
||||
pixmap.setDevicePixelRatio(dpr);
|
||||
|
@@ -9,9 +9,10 @@
|
||||
#include <coreplugin/welcomepagehelper.h>
|
||||
|
||||
#include <utils/fancylineedit.h>
|
||||
#include <utils/layoutbuilder.h>
|
||||
#include <utils/progressindicator.h>
|
||||
#include <utils/theme/theme.h>
|
||||
#include <utils/qtcassert.h>
|
||||
#include <utils/theme/theme.h>
|
||||
|
||||
#include <QDesktopServices>
|
||||
#include <QLabel>
|
||||
@@ -44,38 +45,34 @@ class QtMarketplacePageWidget : public QWidget
|
||||
public:
|
||||
QtMarketplacePageWidget()
|
||||
{
|
||||
auto searchBox = new Core::SearchBox(this);
|
||||
m_searcher = searchBox->m_lineEdit;
|
||||
m_searcher = new Core::SearchBox(this);
|
||||
m_searcher->setPlaceholderText(Tr::tr("Search in Marketplace..."));
|
||||
|
||||
auto vbox = new QVBoxLayout(this);
|
||||
vbox->setContentsMargins(0, 0, 0, Core::WelcomePageHelpers::ItemGap);
|
||||
vbox->setSpacing(Core::WelcomePageHelpers::ItemGap);
|
||||
|
||||
auto searchBar = Core::WelcomePageHelpers::panelBar();
|
||||
auto hbox = new QHBoxLayout(searchBar);
|
||||
hbox->setContentsMargins(Core::WelcomePageHelpers::HSpacing, 0,
|
||||
Core::WelcomePageHelpers::HSpacing, 0);
|
||||
hbox->addWidget(searchBox);
|
||||
vbox->addWidget(searchBar);
|
||||
m_errorLabel = new QLabel(this);
|
||||
m_errorLabel->setVisible(false);
|
||||
vbox->addWidget(m_errorLabel);
|
||||
|
||||
auto resultWidget = new QWidget(this);
|
||||
auto resultHBox = new QHBoxLayout(resultWidget);
|
||||
resultHBox->setContentsMargins(Core::WelcomePageHelpers::HSpacing, 0, 0, 0);
|
||||
m_sectionedProducts = new SectionedProducts(this);
|
||||
auto progressIndicator = new Utils::ProgressIndicator(ProgressIndicatorSize::Large, this);
|
||||
progressIndicator->attachToWidget(m_sectionedProducts);
|
||||
progressIndicator->hide();
|
||||
resultHBox->addWidget(m_sectionedProducts);
|
||||
vbox->addWidget(resultWidget);
|
||||
|
||||
using namespace StyleHelper::SpacingTokens;
|
||||
|
||||
using namespace Layouting;
|
||||
Column {
|
||||
Row {
|
||||
m_searcher,
|
||||
customMargin({0, 0, ExVPaddingGapXl, 0}),
|
||||
},
|
||||
m_sectionedProducts,
|
||||
spacing(VPaddingL),
|
||||
customMargin({ExVPaddingGapXl, ExVPaddingGapXl, 0, 0}),
|
||||
}.attachTo(this);
|
||||
|
||||
connect(m_sectionedProducts, &SectionedProducts::toggleProgressIndicator,
|
||||
progressIndicator, &Utils::ProgressIndicator::setVisible);
|
||||
connect(m_sectionedProducts, &SectionedProducts::errorOccurred, this,
|
||||
[this, progressIndicator, searchBox](int, const QString &message) {
|
||||
[this, progressIndicator](int, const QString &message) {
|
||||
progressIndicator->hide();
|
||||
progressIndicator->deleteLater();
|
||||
m_errorLabel->setAlignment(Qt::AlignHCenter);
|
||||
@@ -89,7 +86,7 @@ public:
|
||||
"</p><br/><p><small><i>Error: %1</i></small></p>").arg(message);
|
||||
m_errorLabel->setText(txt);
|
||||
m_errorLabel->setVisible(true);
|
||||
searchBox->setVisible(false);
|
||||
m_searcher->setVisible(false);
|
||||
connect(m_errorLabel, &QLabel::linkActivated,
|
||||
this, []() { QDesktopServices::openUrl(QUrl("https://marketplace.qt.io")); });
|
||||
});
|
||||
|
@@ -21,6 +21,7 @@
|
||||
#include <utils/algorithm.h>
|
||||
#include <utils/fileutils.h>
|
||||
#include <utils/icon.h>
|
||||
#include <utils/layoutbuilder.h>
|
||||
#include <utils/qtcassert.h>
|
||||
#include <utils/stringutils.h>
|
||||
#include <utils/stylehelper.h>
|
||||
@@ -28,9 +29,6 @@
|
||||
|
||||
#include <QAbstractItemDelegate>
|
||||
#include <QAction>
|
||||
#include <QBoxLayout>
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QHeaderView>
|
||||
#include <QHelpEvent>
|
||||
#include <QLabel>
|
||||
@@ -42,16 +40,52 @@
|
||||
using namespace Core;
|
||||
using namespace Core::WelcomePageHelpers;
|
||||
using namespace Utils;
|
||||
using namespace Utils::StyleHelper::SpacingTokens;
|
||||
|
||||
const int LINK_HEIGHT = 35;
|
||||
const int TEXT_OFFSET_HORIZONTAL = 36;
|
||||
const int SESSION_LINE_HEIGHT = 28;
|
||||
const int SESSION_ARROW_RECT_WIDTH = 24;
|
||||
const char PROJECT_BASE_ID[] = "Welcome.OpenRecentProject";
|
||||
|
||||
namespace ProjectExplorer {
|
||||
namespace Internal {
|
||||
|
||||
constexpr TextFormat projectNameTF {Theme::Token_Accent_Default, StyleHelper::UiElementH5};
|
||||
constexpr TextFormat projectPathTF {Theme::Token_Text_Muted, StyleHelper::UiElementIconActive};
|
||||
constexpr TextFormat sessionNameTF {projectNameTF.themeColor, projectNameTF.uiElement};
|
||||
constexpr TextFormat sessionProjetNameTF {Theme::Token_Text_Default, projectNameTF.uiElement};
|
||||
constexpr TextFormat shortcutNumberTF {Theme::Token_Text_Default,
|
||||
StyleHelper::UiElementCaptionStrong,
|
||||
Qt::AlignCenter | Qt::TextDontClip};
|
||||
constexpr TextFormat actionTF {Theme::Token_Text_Default, StyleHelper::UiElementIconActive,
|
||||
Qt::AlignCenter | Qt::TextDontClip};
|
||||
constexpr TextFormat actionDisabledTF {Theme::Token_Text_Subtle, actionTF.uiElement,
|
||||
actionTF.drawTextFlags};
|
||||
constexpr int shortcutNumberWidth = 16;
|
||||
constexpr int actionSepWidth = 1;
|
||||
constexpr int sessionScrollBarGap = HPaddingXs;
|
||||
|
||||
static int s(const int metric)
|
||||
{
|
||||
constexpr int shrinkWhenAbove = 150; // Above this session count, increasingly reduce scale
|
||||
constexpr qreal maxScale = 1.0; // Spacings as defined by design
|
||||
constexpr qreal minScale = 0.2; // Maximum "condensed" layout
|
||||
|
||||
const int sessionsCount = SessionManager::sessionsCount();
|
||||
const qreal scaling = sessionsCount < shrinkWhenAbove
|
||||
? maxScale
|
||||
: qMax(minScale,
|
||||
maxScale - (sessionsCount - shrinkWhenAbove) * 0.065);
|
||||
return int(qMax(1.0, scaling * metric));
|
||||
}
|
||||
|
||||
static int itemSpacing()
|
||||
{
|
||||
return qMax(int(s(VGapL)), VGapS);
|
||||
}
|
||||
|
||||
static bool withIcon()
|
||||
{
|
||||
return s(100) > 60; // Hide icons if spacings are scaled to below 60%
|
||||
}
|
||||
|
||||
ProjectModel::ProjectModel(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
{
|
||||
@@ -61,7 +95,7 @@ ProjectModel::ProjectModel(QObject *parent)
|
||||
|
||||
int ProjectModel::rowCount(const QModelIndex &) const
|
||||
{
|
||||
return m_projects.count();
|
||||
return int(m_projects.count());
|
||||
}
|
||||
|
||||
QVariant ProjectModel::data(const QModelIndex &index, int role) const
|
||||
@@ -190,21 +224,24 @@ static QColor themeColor(Theme::Color role)
|
||||
return Utils::creatorTheme()->color(role);
|
||||
}
|
||||
|
||||
static QFont sizedFont(int size, const QWidget *widget,
|
||||
bool underline = false)
|
||||
{
|
||||
QFont f = widget->font();
|
||||
f.setPixelSize(size);
|
||||
f.setUnderline(underline);
|
||||
return f;
|
||||
}
|
||||
|
||||
static QPixmap pixmap(const QString &id, const Theme::Color &color)
|
||||
static QPixmap pixmap(const QString &id, const Theme::Color color)
|
||||
{
|
||||
const QString fileName = QString(":/welcome/images/%1.png").arg(id);
|
||||
return Icon({{FilePath::fromString(fileName), color}}, Icon::Tint).pixmap();
|
||||
}
|
||||
|
||||
static void drawBackgroundRect(QPainter *painter, const QRectF &rect, bool hovered)
|
||||
{
|
||||
const QColor fill(themeColor(hovered ? Theme::Token_Foreground_Muted
|
||||
: Theme::Token_Background_Muted));
|
||||
const QPen pen(themeColor(hovered ? Theme::Token_Foreground_Muted
|
||||
: Theme::Token_Stroke_Subtle));
|
||||
|
||||
const qreal rounding = s(defaultCardBackgroundRounding * 1000) / 1000.0;
|
||||
const qreal saneRounding = rounding <= 2 ? 0 : rounding;
|
||||
WelcomePageHelpers::drawCardBackground(painter, rect, fill, pen, saneRounding);
|
||||
}
|
||||
|
||||
class BaseDelegate : public QAbstractItemDelegate
|
||||
{
|
||||
protected:
|
||||
@@ -244,6 +281,11 @@ protected:
|
||||
class SessionDelegate : public BaseDelegate
|
||||
{
|
||||
protected:
|
||||
bool expanded(const QModelIndex &idx) const
|
||||
{
|
||||
return m_expandedSessions.contains(idx.data(Qt::DisplayRole).toString());
|
||||
}
|
||||
|
||||
QString entryType() override
|
||||
{
|
||||
return Tr::tr("session", "Appears in \"Open session <name>\"");
|
||||
@@ -252,150 +294,287 @@ protected:
|
||||
{
|
||||
// in expanded state bottom contains 'Clone', 'Rename', etc links, where the tool tip
|
||||
// would be confusing
|
||||
const bool expanded = m_expandedSessions.contains(idx.data(Qt::DisplayRole).toString());
|
||||
return expanded ? itemRect.adjusted(0, 0, 0, -LINK_HEIGHT) : itemRect;
|
||||
return expanded(idx) ? itemRect.adjusted(0, 0, 0, -actionButtonHeight()) : itemRect;
|
||||
}
|
||||
|
||||
int shortcutRole() const override
|
||||
{
|
||||
return SessionModel::ShortcutRole;
|
||||
}
|
||||
|
||||
static int actionButtonHeight()
|
||||
{
|
||||
return s(VPaddingXxs) + actionTF.lineHeight() + s(VPaddingXxs);
|
||||
}
|
||||
|
||||
static const QPixmap &icon()
|
||||
{
|
||||
static const QPixmap icon = pixmap("session", Theme::Token_Text_Muted);
|
||||
return icon;
|
||||
}
|
||||
int shortcutRole() const override { return SessionModel::ShortcutRole; }
|
||||
|
||||
public:
|
||||
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &idx) const final
|
||||
void paint(QPainter *painter, const QStyleOptionViewItem &option,
|
||||
const QModelIndex &idx) const final
|
||||
{
|
||||
static const QPixmap sessionIcon = pixmap("session", Theme::Welcome_ForegroundSecondaryColor);
|
||||
|
||||
const QRect rc = option.rect;
|
||||
const QString sessionName = idx.data(Qt::DisplayRole).toString();
|
||||
// visible on withIcon() Gap + arrow visible on hover Extra margin right of project item
|
||||
// | | |
|
||||
// +----------+----------+ +--------+-------+ +----------+----------+
|
||||
// | | | | | |
|
||||
//
|
||||
// +------------+--------+--------+------------+--------+-------------+--------+-------+------------+---------------------+ --+
|
||||
// | | | |(VPaddingXs)| |(VPaddingXs) | | | | | |
|
||||
// | | | +------------+ +-------------+ | | | | |
|
||||
// |(HPaddingXs)|<number>|(HGapXs)| <icon> |(HGapXs)|<sessionName>|(HGapXs)|<arrow>| | | +-- Header
|
||||
// | |(16x16) | +------------+ +-------------+ | | | | |
|
||||
// | | | |(VPaddingXs)| |(VPaddingXs) | | | | | |
|
||||
// |------------+--------+--------+------------+--------+-------------+--------+-------+ | | --+
|
||||
// | +-- | (VPaddingXs) | | | |
|
||||
// | | +------------------------------+(HPaddingXs)| | |
|
||||
// | | | <projectName> | | | |
|
||||
// | | +------------------------------+ | | |
|
||||
// | Per project in session --+ | (EXSPaddingGapS) | |(sessionScrollBarGap)| |
|
||||
// | | +------------------------------+ | | |
|
||||
// | | | <projectPath> | | | |
|
||||
// | | +------------------------------+ | | +-- Expansion
|
||||
// | +-- | (VPaddingXs) | | | |
|
||||
// +----------------------------------------------+------------------------------------+------------+ | |
|
||||
// | (VPaddingXs) | | |
|
||||
// +----------------------------------------+--------------+----------------------------------------+ | |
|
||||
// +-- | <cloneButton>|<renameButton>|<deleteButton> | | |
|
||||
// | +----------------------------------------+--------------+----------------------------------------+ | |
|
||||
// | | (VPaddingXs) | | |
|
||||
// | +------------------------------------------------------------------------------------------------+---------------------+ --+
|
||||
// | | (VGapL) | +-- Gap between session items
|
||||
// | +----------------------------------------------------------------------------------------------------------------------+ --+
|
||||
// |
|
||||
// \ session action "buttons" and dividers
|
||||
// +-----------------------------------------------+--------+---------+--------+
|
||||
// | (VGapXs) | | | |
|
||||
// +----------------+-------------+----------------+ | | |
|
||||
// |(EXSPaddingGapM)|<buttonLabel>|(EXSPaddingGapM)|(HGapXs)|<divider>|(HGapXs)|
|
||||
// +----------------+-------------+----------------+ |(w:1) | |
|
||||
// | (VGapXs) | | | |
|
||||
// +-----------------------------------------------+--------+---------+--------+
|
||||
//
|
||||
// | |
|
||||
// +-------------+-------------+
|
||||
// |
|
||||
// omitted after last button
|
||||
|
||||
const QPoint mousePos = option.widget->mapFromGlobal(QCursor::pos());
|
||||
//const bool hovered = option.state & QStyle::State_MouseOver;
|
||||
const bool hovered = option.rect.contains(mousePos);
|
||||
const bool expanded = m_expandedSessions.contains(sessionName);
|
||||
painter->fillRect(rc, themeColor(Theme::Welcome_BackgroundSecondaryColor));
|
||||
painter->fillRect(rc.adjusted(0, 0, 0, -ItemGap),
|
||||
hovered ? hoverColor : backgroundPrimaryColor);
|
||||
const bool expanded = this->expanded(idx);
|
||||
|
||||
const int x = rc.x();
|
||||
const int x1 = x + TEXT_OFFSET_HORIZONTAL;
|
||||
const int y = rc.y();
|
||||
const int firstBase = y + 18;
|
||||
const QRect bgR = option.rect.adjusted(0, 0, -sessionScrollBarGap, -itemSpacing());
|
||||
const QRect hdR(bgR.topLeft(), QSize(bgR.width(), expanded ? headerHeight()
|
||||
: bgR.height()));
|
||||
|
||||
painter->drawPixmap(x + 11, y + 6, sessionIcon);
|
||||
const QSize iconS = icon().deviceIndependentSize().toSize();
|
||||
static const QPixmap arrow = Icon({{FilePath::fromString(":/core/images/expandarrow"),
|
||||
Theme::Token_Text_Muted}}, Icon::Tint).pixmap();
|
||||
const QSize arrowS = arrow.deviceIndependentSize().toSize();
|
||||
const bool arrowVisible = hovered || expanded;
|
||||
|
||||
if (hovered && !expanded) {
|
||||
const QRect arrowRect = rc.adjusted(rc.width() - SESSION_ARROW_RECT_WIDTH, 0, 0, 0);
|
||||
const bool arrowRectHovered = arrowRect.contains(mousePos);
|
||||
painter->fillRect(arrowRect.adjusted(0, 0, 0, -ItemGap),
|
||||
arrowRectHovered ? hoverColor : backgroundPrimaryColor);
|
||||
const QString sessionName = idx.data(Qt::DisplayRole).toString();
|
||||
|
||||
const int x = bgR.x();
|
||||
const int y = bgR.y();
|
||||
|
||||
const int numberX = x + s(HPaddingXs);
|
||||
const int iconX = numberX + shortcutNumberWidth + s(HGapXs);
|
||||
const int arrowX = bgR.right() - s(HPaddingXs) - arrowS.width();
|
||||
const QRect arrowHoverR(arrowX - s(HGapXs) + 1, y,
|
||||
s(HGapXs) + arrowS.width() + s(HPaddingXs), hdR.height());
|
||||
const int textX = withIcon() ? iconX + iconS.width() + s(HGapXs) : iconX;
|
||||
|
||||
const int iconY = y + (hdR.height() - iconS.height()) / 2;
|
||||
const int arrowY = y + (hdR.height() - arrowS.height()) / 2;
|
||||
|
||||
{
|
||||
drawBackgroundRect(painter, bgR, hovered);
|
||||
}
|
||||
|
||||
if (hovered || expanded) {
|
||||
static const QPixmap arrowUp = pixmap("expandarrow",Theme::Welcome_ForegroundSecondaryColor);
|
||||
static const QPixmap arrowDown = QPixmap::fromImage(arrowUp.toImage().mirrored(false, true));
|
||||
painter->drawPixmap(rc.right() - 19, y + 6, expanded ? arrowDown : arrowUp);
|
||||
}
|
||||
|
||||
if (idx.row() < 9) {
|
||||
painter->setPen(foregroundSecondaryColor);
|
||||
painter->setFont(sizedFont(10, option.widget));
|
||||
painter->drawText(x + 3, firstBase, QString::number(idx.row() + 1));
|
||||
painter->setPen(shortcutNumberTF.color());
|
||||
painter->setFont(shortcutNumberTF.font());
|
||||
const QRect numberR(numberX, y, shortcutNumberWidth, hdR.height());
|
||||
const QString numberString = QString::number(idx.row() + 1);
|
||||
painter->drawText(numberR, shortcutNumberTF.drawTextFlags, numberString);
|
||||
}
|
||||
|
||||
if (withIcon()) {
|
||||
painter->drawPixmap(iconX, iconY, icon());
|
||||
}
|
||||
{
|
||||
const bool isLastSession = idx.data(SessionModel::LastSessionRole).toBool();
|
||||
const bool isActiveSession = idx.data(SessionModel::ActiveSessionRole).toBool();
|
||||
const bool isDefaultVirgin = SessionManager::isDefaultVirgin();
|
||||
|
||||
const int sessionNameWidth = hdR.right()
|
||||
- (arrowVisible ? arrowHoverR.width(): s(HPaddingXs))
|
||||
- textX;
|
||||
const int sessionNameHeight = sessionNameTF.lineHeight();
|
||||
const int sessionNameY = y + (hdR.height() - sessionNameHeight) / 2;
|
||||
const QRect sessionNameR(textX, sessionNameY, sessionNameWidth, sessionNameHeight);
|
||||
|
||||
QString fullSessionName = sessionName;
|
||||
if (isLastSession && isDefaultVirgin)
|
||||
fullSessionName = Tr::tr("%1 (last session)").arg(fullSessionName);
|
||||
if (isActiveSession && !isDefaultVirgin)
|
||||
fullSessionName = Tr::tr("%1 (current session)").arg(fullSessionName);
|
||||
|
||||
const QRect switchRect = QRect(x, y, rc.width() - SESSION_ARROW_RECT_WIDTH, SESSION_LINE_HEIGHT);
|
||||
const bool switchActive = switchRect.contains(mousePos);
|
||||
const int textSpace = rc.width() - TEXT_OFFSET_HORIZONTAL - 6;
|
||||
const int sessionNameTextSpace =
|
||||
textSpace -(hovered || expanded ? SESSION_ARROW_RECT_WIDTH : 0);
|
||||
painter->setPen(linkColor);
|
||||
painter->setFont(sizedFont(13, option.widget, switchActive));
|
||||
const QRect switchR(x, y, hdR.width() - arrowHoverR.width(),
|
||||
hdR.height() + s(VGapL));
|
||||
const bool switchActive = switchR.contains(mousePos);
|
||||
painter->setPen(sessionNameTF.color());
|
||||
painter->setFont(sessionNameTF.font(switchActive));
|
||||
const QString fullSessionNameElided = painter->fontMetrics().elidedText(
|
||||
fullSessionName, Qt::ElideRight, sessionNameTextSpace);
|
||||
painter->drawText(x1, firstBase, fullSessionNameElided);
|
||||
fullSessionName, Qt::ElideRight, sessionNameWidth);
|
||||
painter->drawText(sessionNameR, sessionNameTF.drawTextFlags,
|
||||
fullSessionNameElided);
|
||||
if (switchActive)
|
||||
m_activeSwitchToRect = switchRect;
|
||||
m_activeSwitchToRect = switchR;
|
||||
}
|
||||
if (arrowVisible) {
|
||||
if (arrowHoverR.adjusted(0, 0, 0, s(VGapL)).contains(mousePos)) {
|
||||
m_activeExpandRect = arrowHoverR;
|
||||
} else {
|
||||
painter->save();
|
||||
painter->setClipRect(arrowHoverR);
|
||||
drawBackgroundRect(painter, bgR, false);
|
||||
painter->restore();
|
||||
}
|
||||
static const QPixmap arrowDown =
|
||||
QPixmap::fromImage(arrow.toImage().mirrored(false, true));
|
||||
painter->drawPixmap(arrowX, arrowY, expanded ? arrowDown : arrow);
|
||||
}
|
||||
|
||||
int yy = hdR.bottom();
|
||||
if (expanded) {
|
||||
painter->setPen(textColor);
|
||||
painter->setFont(sizedFont(12, option.widget));
|
||||
const FilePaths projects = ProjectManager::projectsForSessionName(sessionName);
|
||||
int yy = firstBase + SESSION_LINE_HEIGHT - 3;
|
||||
QFontMetrics fm(option.widget->font());
|
||||
for (const FilePath &projectPath : projects) {
|
||||
// Project name.
|
||||
QString completeBase = projectPath.completeBaseName();
|
||||
painter->setPen(textColor);
|
||||
painter->drawText(x1, yy, fm.elidedText(completeBase, Qt::ElideMiddle, textSpace));
|
||||
yy += 18;
|
||||
const QFont projectNameFont = sessionProjetNameTF.font();
|
||||
const QFontMetrics projectNameFm(projectNameFont);
|
||||
const int projectNameLineHeight = sessionProjetNameTF.lineHeight();
|
||||
const QFont projectPathFont = projectPathTF.font();
|
||||
const QFontMetrics projectPathFm(projectPathFont);
|
||||
const int projectPathLineHeight = projectPathTF.lineHeight();
|
||||
const int textWidth = bgR.right() - s(HPaddingXs) - textX;
|
||||
|
||||
// Project path.
|
||||
const FilePaths projects = ProjectManager::projectsForSessionName(sessionName);
|
||||
for (const FilePath &projectPath : projects) {
|
||||
yy += s(VPaddingXs);
|
||||
{
|
||||
painter->setFont(projectNameFont);
|
||||
painter->setPen(sessionProjetNameTF.color());
|
||||
const QRect projectNameR(textX, yy, textWidth, projectNameLineHeight);
|
||||
const QString projectNameElided =
|
||||
projectNameFm.elidedText(projectPath.completeBaseName(), Qt::ElideMiddle,
|
||||
textWidth);
|
||||
painter->drawText(projectNameR, sessionProjetNameTF.drawTextFlags,
|
||||
projectNameElided);
|
||||
yy += projectNameLineHeight;
|
||||
yy += s(ExPaddingGapS);
|
||||
}
|
||||
{
|
||||
const QString displayPath =
|
||||
projectPath.osType() == OsTypeWindows ? projectPath.displayName()
|
||||
: projectPath.withTildeHomePath();
|
||||
painter->setPen(foregroundPrimaryColor);
|
||||
painter->drawText(x1, yy, fm.elidedText(displayPath, Qt::ElideMiddle, textSpace));
|
||||
yy += 22;
|
||||
painter->setFont(projectPathFont);
|
||||
painter->setPen(projectPathTF.color());
|
||||
const QRect projectPathR(textX, yy, textWidth, projectPathLineHeight);
|
||||
const QString projectPathElided =
|
||||
projectPathFm.elidedText(displayPath, Qt::ElideMiddle, textWidth);
|
||||
painter->drawText(projectPathR, projectPathTF.drawTextFlags,
|
||||
projectPathElided);
|
||||
yy += projectPathLineHeight;
|
||||
}
|
||||
yy += s(VPaddingXs);
|
||||
}
|
||||
yy += s(VGapXs);
|
||||
|
||||
yy += 3;
|
||||
int xx = x1;
|
||||
const QStringList actions = {
|
||||
Tr::tr("Clone"),
|
||||
Tr::tr("Rename"),
|
||||
Tr::tr("Delete")
|
||||
Tr::tr("Delete"),
|
||||
};
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
|
||||
const QFont actionFont = actionTF.font();
|
||||
const QFontMetrics actionFm(actionTF.font());
|
||||
|
||||
const int gapWidth = s(HGapXs) + actionSepWidth + s(HGapXs);
|
||||
int actionsTotalWidth = gapWidth * int(actions.count() - 1); // dividers
|
||||
const auto textWidths = Utils::transform(actions, [&] (const QString &action) {
|
||||
const int width = actionFm.horizontalAdvance(action);
|
||||
actionsTotalWidth += s(ExPaddingGapM) + width + s(ExPaddingGapM);
|
||||
return width;
|
||||
});
|
||||
|
||||
const int buttonHeight = this->actionButtonHeight();
|
||||
int xx = (bgR.width() - actionsTotalWidth) / 2;
|
||||
for (int i = 0; i < actions.count(); ++i) {
|
||||
const QString &action = actions.at(i);
|
||||
const int ww = fm.horizontalAdvance(action);
|
||||
const int spacing = 7; // Between action link and separator line
|
||||
const QRect actionRect =
|
||||
QRect(xx, yy - 10, ww, 15).adjusted(-spacing, -spacing, spacing, spacing);
|
||||
const bool isForcedDisabled = (i != 0 && sessionName == "default");
|
||||
const bool isActive = actionRect.contains(mousePos) && !isForcedDisabled;
|
||||
painter->setPen(isForcedDisabled ? disabledLinkColor : linkColor);
|
||||
painter->setFont(sizedFont(12, option.widget, isActive));
|
||||
painter->drawText(xx, yy, action);
|
||||
if (i < 2) {
|
||||
xx += ww + 2 * spacing;
|
||||
int pp = xx - spacing;
|
||||
painter->setPen(textColor);
|
||||
painter->drawLine(pp, yy - 10, pp, yy);
|
||||
const int ww = textWidths.at(i);
|
||||
const QRect actionR(xx, yy, s(ExPaddingGapM) + ww + s(ExPaddingGapM), buttonHeight);
|
||||
const bool isDisabled = i > 0 && SessionManager::isDefaultSession(sessionName);
|
||||
const bool isActive = actionR.adjusted(-s(VPaddingXs), 0, s(VPaddingXs) + 1, 0)
|
||||
.contains(mousePos) && !isDisabled;
|
||||
if (isActive) {
|
||||
WelcomePageHelpers::drawCardBackground(painter, actionR, Qt::transparent,
|
||||
themeColor(Theme::Token_Text_Muted));
|
||||
m_activeActionRects[i] = actionR;
|
||||
}
|
||||
if (isActive)
|
||||
m_activeActionRects[i] = actionRect;
|
||||
painter->setFont(actionFont);
|
||||
painter->setPen((isDisabled ? actionDisabledTF : actionTF).color());
|
||||
const QRect actionTextR = actionR.adjusted(0, 0, 0, -1);
|
||||
painter->drawText(actionTextR, actionTF.drawTextFlags, action);
|
||||
xx += actionR.width();
|
||||
if (i < actions.count() - 1) {
|
||||
const QRect dividerR(xx + s(HGapXs), yy, actionSepWidth, buttonHeight);
|
||||
painter->fillRect(dividerR, themeColor(Theme::Token_Text_Muted));
|
||||
}
|
||||
xx += gapWidth;
|
||||
}
|
||||
yy += buttonHeight;
|
||||
yy += s(VGapXs);
|
||||
}
|
||||
QTC_CHECK(option.rect.bottom() == yy + itemSpacing());
|
||||
}
|
||||
|
||||
static int headerHeight()
|
||||
{
|
||||
const int paddingsHeight = s(VPaddingXs + VPaddingXs);
|
||||
const int heightForSessionName = sessionNameTF.lineHeight() + paddingsHeight;
|
||||
const int heightForIcon =
|
||||
withIcon() ? int(icon().deviceIndependentSize().height()) + paddingsHeight : 0;
|
||||
return qMax(heightForSessionName, heightForIcon);
|
||||
}
|
||||
|
||||
QSize sizeHint(const QStyleOptionViewItem &, const QModelIndex &idx) const final
|
||||
{
|
||||
int h = SESSION_LINE_HEIGHT;
|
||||
QString sessionName = idx.data(Qt::DisplayRole).toString();
|
||||
if (m_expandedSessions.contains(sessionName)) {
|
||||
int h = headerHeight();
|
||||
if (expanded(idx)) {
|
||||
const QString sessionName = idx.data(Qt::DisplayRole).toString();
|
||||
const FilePaths projects = ProjectManager::projectsForSessionName(sessionName);
|
||||
h += projects.size() * 40 + LINK_HEIGHT - 6;
|
||||
const int projectEntryHeight =
|
||||
s(VPaddingXs)
|
||||
+ projectNameTF.lineHeight()
|
||||
+ s(ExPaddingGapS)
|
||||
+ projectPathTF.lineHeight()
|
||||
+ s(VPaddingXs);
|
||||
h += projects.size() * projectEntryHeight
|
||||
+ s(VGapXs)
|
||||
+ actionButtonHeight()
|
||||
+ s(VGapXs);
|
||||
}
|
||||
return QSize(380, h + ItemGap);
|
||||
return QSize(-1, h + itemSpacing());
|
||||
}
|
||||
|
||||
bool editorEvent(QEvent *ev, QAbstractItemModel *model,
|
||||
const QStyleOptionViewItem &option, const QModelIndex &idx) final
|
||||
const QStyleOptionViewItem &, const QModelIndex &idx) final
|
||||
{
|
||||
if (ev->type() == QEvent::MouseButtonRelease) {
|
||||
const QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(ev);
|
||||
const Qt::MouseButtons button = mouseEvent->button();
|
||||
const QPoint pos = static_cast<QMouseEvent *>(ev)->pos();
|
||||
const QRect rc(option.rect.right() - SESSION_ARROW_RECT_WIDTH, option.rect.top(),
|
||||
SESSION_ARROW_RECT_WIDTH, SESSION_LINE_HEIGHT);
|
||||
const QString sessionName = idx.data(Qt::DisplayRole).toString();
|
||||
if (rc.contains(pos) || button == Qt::RightButton) {
|
||||
if (m_activeExpandRect.contains(pos) || button == Qt::RightButton) {
|
||||
// The expand/collapse "button".
|
||||
if (m_expandedSessions.contains(sessionName))
|
||||
m_expandedSessions.removeOne(sessionName);
|
||||
@@ -421,23 +600,15 @@ public:
|
||||
}
|
||||
if (ev->type() == QEvent::MouseMove) {
|
||||
emit model->layoutChanged({QPersistentModelIndex(idx)}); // Somewhat brutish.
|
||||
//update(option.rect);
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
const QColor hoverColor = themeColor(Theme::Welcome_HoverColor);
|
||||
const QColor textColor = themeColor(Theme::Welcome_TextColor);
|
||||
const QColor linkColor = themeColor(Theme::Welcome_LinkColor);
|
||||
const QColor disabledLinkColor = themeColor(Theme::Welcome_DisabledLinkColor);
|
||||
const QColor backgroundPrimaryColor = themeColor(Theme::Welcome_BackgroundPrimaryColor);
|
||||
const QColor foregroundPrimaryColor = themeColor(Theme::Welcome_ForegroundPrimaryColor);
|
||||
const QColor foregroundSecondaryColor = themeColor(Theme::Welcome_ForegroundSecondaryColor);
|
||||
|
||||
QStringList m_expandedSessions;
|
||||
|
||||
mutable QRect m_activeExpandRect;
|
||||
mutable QRect m_activeSwitchToRect;
|
||||
mutable QRect m_activeActionRects[3];
|
||||
};
|
||||
@@ -453,58 +624,89 @@ class ProjectDelegate : public BaseDelegate
|
||||
public:
|
||||
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &idx) const final
|
||||
{
|
||||
QRect rc = option.rect;
|
||||
// visible on withIcon() Extra margin right of project item
|
||||
// | |
|
||||
// +-------+-------+ +------+-----+
|
||||
// | | | |
|
||||
//
|
||||
// +------------+--------+--------+------+--------+-------------+------------+------------+
|
||||
// | | | | | | (VPaddingXs)| | |
|
||||
// | | | | | +-------------+ | |
|
||||
// | | | | | |<projectName>| | |
|
||||
// | | | | | +-------------+ | |
|
||||
// |(HPaddingXs)|<number>|(HGapXs)|<icon>|(HGapXs)| (VGapXs) |(HPaddingXs)|(HPaddingXs)|
|
||||
// | |(16x16) | | | +-------------+ | |
|
||||
// | | | | | |<projectPath>| | |
|
||||
// | | | | | +-------------+ | |
|
||||
// | | | | | | (VPaddingXs)| | |
|
||||
// +------------+--------+--------+------+--------+-------------+------------+------------+ --+
|
||||
// | (VGapL) | +-- Gap between project items
|
||||
// +--------------------------------------------------------------------------------------+ --+
|
||||
|
||||
const bool hovered = option.widget->isActiveWindow() && option.state & QStyle::State_MouseOver;
|
||||
const QRect bgRect = rc.adjusted(0, 0, -ItemGap, -ItemGap);
|
||||
painter->fillRect(rc, themeColor(Theme::Welcome_BackgroundSecondaryColor));
|
||||
painter->fillRect(bgRect, themeColor(hovered ? Theme::Welcome_HoverColor
|
||||
: Theme::Welcome_BackgroundPrimaryColor));
|
||||
const bool hovered = option.widget->isActiveWindow()
|
||||
&& option.state & QStyle::State_MouseOver;
|
||||
|
||||
const int x = rc.x();
|
||||
const int y = rc.y();
|
||||
const int firstBase = y + 18;
|
||||
const int secondBase = firstBase + 19;
|
||||
const QRect bgR = option.rect.adjusted(0, 0, -s(HPaddingXs), -itemSpacing());
|
||||
|
||||
static const QPixmap projectIcon =
|
||||
pixmap("project", Theme::Welcome_ForegroundSecondaryColor);
|
||||
painter->drawPixmap(x + 11, y + 6, projectIcon);
|
||||
static const QPixmap icon = pixmap("project", Theme::Token_Text_Muted);
|
||||
const QSize iconS = icon.deviceIndependentSize().toSize();
|
||||
|
||||
QString projectName = idx.data(Qt::DisplayRole).toString();
|
||||
FilePath projectPath = FilePath::fromVariant(idx.data(ProjectModel::FilePathRole));
|
||||
const int x = bgR.x();
|
||||
const int numberX = x + s(HPaddingXs);
|
||||
const int iconX = numberX + shortcutNumberWidth + s(HGapXs);
|
||||
const int iconWidth = iconS.width();
|
||||
const int textX = withIcon() ? iconX + iconWidth + s(HGapXs) : iconX;
|
||||
const int textWidth = bgR.width() - s(HPaddingXs) - textX;
|
||||
|
||||
painter->setPen(themeColor(Theme::Welcome_ForegroundSecondaryColor));
|
||||
painter->setFont(sizedFont(10, option.widget));
|
||||
const int y = bgR.y();
|
||||
const int iconHeight = iconS.height();
|
||||
const int iconY = y + (bgR.height() - iconHeight) / 2;
|
||||
const int projectNameY = y + s(VPaddingXs);
|
||||
const QRect projectNameR(textX, projectNameY, textWidth, projectNameTF.lineHeight());
|
||||
const int projectPathY = projectNameY + projectNameR.height() + s(VGapXs);
|
||||
const QRect projectPathR(textX, projectPathY, textWidth, projectPathTF.lineHeight());
|
||||
|
||||
if (idx.row() < 9)
|
||||
painter->drawText(x + 3, firstBase, QString::number(idx.row() + 1));
|
||||
QTC_CHECK(option.rect.bottom() == projectPathR.bottom() + s(VPaddingXs) + itemSpacing());
|
||||
|
||||
const int textSpace = rc.width() - TEXT_OFFSET_HORIZONTAL - ItemGap - 6;
|
||||
|
||||
painter->setPen(themeColor(Theme::Welcome_LinkColor));
|
||||
painter->setFont(sizedFont(13, option.widget, hovered));
|
||||
{
|
||||
drawBackgroundRect(painter, bgR, hovered);
|
||||
}
|
||||
if (idx.row() < 9) {
|
||||
painter->setPen(shortcutNumberTF.color());
|
||||
painter->setFont(shortcutNumberTF.font());
|
||||
const QRect numberR(numberX, y, shortcutNumberWidth, bgR.height());
|
||||
const QString numberString = QString::number(idx.row() + 1);
|
||||
painter->drawText(numberR, shortcutNumberTF.drawTextFlags, numberString);
|
||||
}
|
||||
if (withIcon()) {
|
||||
painter->drawPixmap(iconX, iconY, icon);
|
||||
}
|
||||
{
|
||||
painter->setPen(projectNameTF.color());
|
||||
painter->setFont(projectNameTF.font(hovered));
|
||||
const QString projectName = idx.data(Qt::DisplayRole).toString();
|
||||
const QString projectNameElided =
|
||||
painter->fontMetrics().elidedText(projectName, Qt::ElideRight, textSpace);
|
||||
painter->drawText(x + TEXT_OFFSET_HORIZONTAL, firstBase, projectNameElided);
|
||||
|
||||
painter->setPen(themeColor(Theme::Welcome_ForegroundPrimaryColor));
|
||||
painter->setFont(sizedFont(13, option.widget));
|
||||
painter->fontMetrics().elidedText(projectName, Qt::ElideRight, textWidth);
|
||||
painter->drawText(projectNameR, projectNameTF.drawTextFlags, projectNameElided);
|
||||
}
|
||||
{
|
||||
painter->setPen(projectPathTF.color());
|
||||
painter->setFont(projectPathTF.font());
|
||||
const FilePath projectPath =
|
||||
FilePath::fromVariant(idx.data(ProjectModel::FilePathRole));
|
||||
const QString displayPath =
|
||||
projectPath.osType() == OsTypeWindows ? projectPath.displayName()
|
||||
: projectPath.withTildeHomePath();
|
||||
const QString displayPathElided =
|
||||
painter->fontMetrics().elidedText(displayPath, Qt::ElideMiddle, textSpace);
|
||||
painter->drawText(x + TEXT_OFFSET_HORIZONTAL, secondBase, displayPathElided);
|
||||
painter->fontMetrics().elidedText(displayPath, Qt::ElideMiddle, textWidth);
|
||||
painter->drawText(projectPathR, projectPathTF.drawTextFlags, displayPathElided);
|
||||
}
|
||||
}
|
||||
|
||||
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &idx) const final
|
||||
QSize sizeHint([[maybe_unused]] const QStyleOptionViewItem &option,
|
||||
[[maybe_unused]] const QModelIndex &idx) const override
|
||||
{
|
||||
QString projectName = idx.data(Qt::DisplayRole).toString();
|
||||
QString projectPath = idx.data(ProjectModel::FilePathRole).toString();
|
||||
QFontMetrics fm(sizedFont(13, option.widget));
|
||||
int width = std::max(fm.horizontalAdvance(projectName),
|
||||
fm.horizontalAdvance(projectPath)) + TEXT_OFFSET_HORIZONTAL;
|
||||
return QSize(width, 47 + ItemGap);
|
||||
return QSize(-1, itemHeight() + itemSpacing());
|
||||
}
|
||||
|
||||
bool editorEvent(QEvent *ev, QAbstractItemModel *model,
|
||||
@@ -541,6 +743,18 @@ public:
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
static int itemHeight()
|
||||
{
|
||||
const int height =
|
||||
s(VPaddingXs)
|
||||
+ projectNameTF.lineHeight()
|
||||
+ s(VGapXs)
|
||||
+ projectPathTF.lineHeight()
|
||||
+ s(VPaddingXs);
|
||||
return height;
|
||||
}
|
||||
};
|
||||
|
||||
class TreeView : public QTreeView
|
||||
@@ -559,10 +773,7 @@ public:
|
||||
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
|
||||
setFocusPolicy(Qt::NoFocus);
|
||||
|
||||
QPalette pal;
|
||||
pal.setColor(QPalette::Base, themeColor(Theme::Welcome_BackgroundSecondaryColor));
|
||||
viewport()->setPalette(pal);
|
||||
setBackgroundColor(viewport(), Theme::Token_Background_Default);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -577,54 +788,63 @@ public:
|
||||
if (!projectWelcomePage->m_projectModel)
|
||||
projectWelcomePage->m_projectModel = new ProjectModel(this);
|
||||
|
||||
auto manageSessionsButton = new WelcomePageButton(this);
|
||||
manageSessionsButton->setText(Tr::tr("Manage..."));
|
||||
manageSessionsButton->setWithAccentColor(true);
|
||||
manageSessionsButton->setOnClicked([] { SessionManager::showSessionManager(); });
|
||||
|
||||
auto sessionsLabel = new QLabel(this);
|
||||
sessionsLabel->setText(Tr::tr("Sessions"));
|
||||
|
||||
auto recentProjectsLabel = new QLabel(this);
|
||||
recentProjectsLabel->setText(Tr::tr("Projects"));
|
||||
using namespace Layouting;
|
||||
|
||||
auto sessions = new QWidget;
|
||||
{
|
||||
auto sessionsLabel = new Label(Tr::tr("Sessions"), Label::Primary);
|
||||
auto manageSessionsButton = new Button(Tr::tr("Manage..."), Button::MediumSecondary);
|
||||
auto sessionsList = new TreeView(this, "Sessions");
|
||||
sessionsList->setModel(projectWelcomePage->m_sessionModel);
|
||||
sessionsList->header()->setSectionHidden(1, true); // The "last modified" column.
|
||||
sessionsList->setItemDelegate(&m_sessionDelegate);
|
||||
sessionsList->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
|
||||
QSizePolicy sessionsSp(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
sessionsSp.setHorizontalStretch(3);
|
||||
sessions->setSizePolicy(sessionsSp);
|
||||
Column {
|
||||
Row {
|
||||
sessionsLabel,
|
||||
st,
|
||||
manageSessionsButton,
|
||||
customMargin({HPaddingS, 0, sessionScrollBarGap, 0}),
|
||||
},
|
||||
sessionsList,
|
||||
spacing(ExPaddingGapL),
|
||||
customMargin({ExVPaddingGapXl, ExVPaddingGapXl, 0, 0}),
|
||||
}.attachTo(sessions);
|
||||
connect(manageSessionsButton, &Button::clicked,
|
||||
this, &SessionManager::showSessionManager);
|
||||
}
|
||||
|
||||
auto projects = new QWidget;
|
||||
{
|
||||
auto projectsLabel = new Label(Tr::tr("Projects"), Label::Primary);
|
||||
auto projectsList = new TreeView(this, "Recent Projects");
|
||||
projectsList->setUniformRowHeights(true);
|
||||
projectsList->setModel(projectWelcomePage->m_projectModel);
|
||||
projectsList->setItemDelegate(&m_projectDelegate);
|
||||
projectsList->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
|
||||
QSizePolicy projectsSP(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
projectsSP.setHorizontalStretch(5);
|
||||
projects->setSizePolicy(projectsSP);
|
||||
Column {
|
||||
Row {
|
||||
projectsLabel,
|
||||
customMargin({HPaddingS, 0, 0, 0}),
|
||||
},
|
||||
projectsList,
|
||||
spacing(ExPaddingGapL),
|
||||
customMargin({ExVPaddingGapXl - sessionScrollBarGap, ExVPaddingGapXl, 0, 0}),
|
||||
}.attachTo(projects);
|
||||
}
|
||||
|
||||
auto sessionHeader = panelBar(this);
|
||||
auto hbox11 = new QHBoxLayout(sessionHeader);
|
||||
hbox11->setContentsMargins(12, 0, 0, 0);
|
||||
hbox11->addWidget(sessionsLabel);
|
||||
hbox11->addStretch(1);
|
||||
hbox11->addWidget(manageSessionsButton);
|
||||
|
||||
auto projectsHeader = panelBar(this);
|
||||
auto hbox21 = new QHBoxLayout(projectsHeader);
|
||||
hbox21->setContentsMargins(hbox11->contentsMargins());
|
||||
hbox21->addWidget(recentProjectsLabel);
|
||||
|
||||
auto grid = new QGridLayout(this);
|
||||
grid->setContentsMargins(0, 0, 0, ItemGap);
|
||||
grid->setHorizontalSpacing(0);
|
||||
grid->setVerticalSpacing(ItemGap);
|
||||
grid->addWidget(panelBar(this), 0, 0);
|
||||
grid->addWidget(sessionHeader, 0, 1);
|
||||
grid->addWidget(sessionsList, 1, 1);
|
||||
grid->addWidget(panelBar(this), 0, 2);
|
||||
grid->setColumnStretch(1, 9);
|
||||
grid->setColumnMinimumWidth(1, 200);
|
||||
grid->addWidget(projectsHeader, 0, 3);
|
||||
grid->addWidget(projectsList, 1, 3);
|
||||
grid->setColumnStretch(3, 20);
|
||||
Row {
|
||||
sessions,
|
||||
projects,
|
||||
spacing(0),
|
||||
noMargin(),
|
||||
}.attachTo(this);
|
||||
}
|
||||
|
||||
SessionDelegate m_sessionDelegate;
|
||||
|
@@ -117,11 +117,11 @@ public:
|
||||
if (!pipIsUsable(path)) {
|
||||
result << BuildSystemTask(
|
||||
Task::Warning,
|
||||
Tr::tr("Python \"%1\" does not contain a usable pip. Pip is used to install "
|
||||
"python "
|
||||
"packages from the Python Package Index, like PySide and the python "
|
||||
"language server. If you want to use any of that functionality "
|
||||
"ensure pip is installed for that python.")
|
||||
Tr::tr("Python \"%1\" does not contain a usable pip. pip is needed to install "
|
||||
"Python "
|
||||
"packages from the Python Package Index, like PySide and the Python "
|
||||
"language server. To use any of that functionality "
|
||||
"ensure that pip is installed for that Python.")
|
||||
.arg(path.toUserOutput()));
|
||||
}
|
||||
if (!venvIsUsable(path)) {
|
||||
@@ -130,7 +130,7 @@ public:
|
||||
Tr::tr(
|
||||
"Python \"%1\" does not contain a usable venv. venv is the recommended way "
|
||||
"to isolate a development environment for a project from the globally "
|
||||
"installed python.")
|
||||
"installed Python.")
|
||||
.arg(path.toUserOutput()));
|
||||
}
|
||||
}
|
||||
|
@@ -35,7 +35,7 @@ Tasks PythonProject::projectIssues(const Kit *k) const
|
||||
return {};
|
||||
return {
|
||||
BuildSystemTask{Task::Error,
|
||||
Tr::tr("No python interpreter set for kit \"%1\"").arg(k->displayName())}};
|
||||
Tr::tr("No Python interpreter set for kit \"%1\"").arg(k->displayName())}};
|
||||
}
|
||||
|
||||
PythonProjectNode::PythonProjectNode(const FilePath &path)
|
||||
|
@@ -56,6 +56,7 @@ void MakeFileParse::parseArgs(const QString &args, const QString &project,
|
||||
static const QRegularExpression regExp(QLatin1String("^([^\\s\\+-]*)\\s*(\\+=|=|-=|~=)(.*)$"));
|
||||
bool after = false;
|
||||
bool ignoreNext = false;
|
||||
bool nextIsQtConfArg = false;
|
||||
m_unparsedArguments = args;
|
||||
ProcessArgs::ArgIterator ait(&m_unparsedArguments);
|
||||
while (ait.next()) {
|
||||
@@ -63,11 +64,18 @@ void MakeFileParse::parseArgs(const QString &args, const QString &project,
|
||||
// Ignoring
|
||||
ignoreNext = false;
|
||||
ait.deleteArg();
|
||||
} else if (nextIsQtConfArg) {
|
||||
nextIsQtConfArg = false;
|
||||
m_qtConfFile = FilePath::fromUserInput(ait.value());
|
||||
ait.deleteArg();
|
||||
} else if (ait.value() == project) {
|
||||
ait.deleteArg();
|
||||
} else if (ait.value() == QLatin1String("-after")) {
|
||||
after = true;
|
||||
ait.deleteArg();
|
||||
} else if (ait.value() == "-qtconf") {
|
||||
nextIsQtConfArg = true;
|
||||
ait.deleteArg();
|
||||
} else if (ait.value().contains(QLatin1Char('='))) {
|
||||
const QRegularExpressionMatch match = regExp.match(ait.value());
|
||||
if (match.hasMatch()) {
|
||||
|
@@ -30,6 +30,7 @@ public:
|
||||
MakefileState makeFileState() const;
|
||||
Utils::FilePath qmakePath() const;
|
||||
Utils::FilePath srcProFile() const;
|
||||
Utils::FilePath qtConfPath() const { return m_qtConfFile;}
|
||||
QMakeStepConfig config() const;
|
||||
|
||||
QString unparsedArguments() const;
|
||||
@@ -59,6 +60,7 @@ private:
|
||||
MakefileState m_state;
|
||||
Utils::FilePath m_qmakePath;
|
||||
Utils::FilePath m_srcProFile;
|
||||
Utils::FilePath m_qtConfFile;
|
||||
|
||||
QmakeBuildConfig m_qmakeBuildConfig;
|
||||
QMakeStepConfig m_config;
|
||||
|
@@ -490,7 +490,9 @@ QmakeBuildConfiguration::MakefileState QmakeBuildConfiguration::compareToImportF
|
||||
return MakefileIncompatible;
|
||||
}
|
||||
|
||||
if (version->qmakeFilePath() != parse.qmakePath()) {
|
||||
if (version->qmakeFilePath() != parse.qmakePath()
|
||||
&& (parse.qtConfPath().isEmpty() // QTCREATORBUG-30354
|
||||
|| version->qmakeFilePath().parentDir() != parse.qtConfPath().parentDir())) {
|
||||
qCDebug(logs) << "**Different Qt versions, buildconfiguration:" << version->qmakeFilePath()
|
||||
<< " Makefile:" << parse.qmakePath();
|
||||
return MakefileForWrongProject;
|
||||
|
@@ -250,7 +250,7 @@ static QPixmap fetchPixmapAndUpdatePixmapCache(const QString &url)
|
||||
const int dpr = qApp->devicePixelRatio();
|
||||
// boundedTo -> don't scale thumbnails up
|
||||
const QSize scaledSize =
|
||||
WelcomePageHelpers::GridItemImageSize.boundedTo(img.size()) * dpr;
|
||||
WelcomePageHelpers::WelcomeThumbnailSize.boundedTo(img.size()) * dpr;
|
||||
const QImage scaled = img.isNull() ? img
|
||||
: img.scaled(scaledSize,
|
||||
Qt::KeepAspectRatio,
|
||||
|
@@ -21,13 +21,13 @@
|
||||
|
||||
#include <utils/algorithm.h>
|
||||
#include <utils/fileutils.h>
|
||||
#include <utils/layoutbuilder.h>
|
||||
#include <utils/pathchooser.h>
|
||||
#include <utils/qtcassert.h>
|
||||
#include <utils/stylehelper.h>
|
||||
#include <utils/theme/theme.h>
|
||||
#include <utils/winutils.h>
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QDesktopServices>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QElapsedTimer>
|
||||
@@ -249,7 +249,8 @@ protected:
|
||||
painter->setFont(option.font);
|
||||
painter->setCompositionMode(QPainter::CompositionMode_Difference);
|
||||
painter->setPen(Qt::white);
|
||||
painter->drawText(currentPixmapRect.translated(0, -WelcomePageHelpers::ItemGap),
|
||||
painter->drawText(
|
||||
currentPixmapRect.translated(0, -StyleHelper::SpacingTokens::VPaddingXxs),
|
||||
exampleItem->videoLength, Qt::AlignBottom | Qt::AlignHCenter);
|
||||
painter->restore();
|
||||
static const QPixmap playOverlay =
|
||||
@@ -274,29 +275,25 @@ public:
|
||||
{
|
||||
m_exampleDelegate.setShowExamples(isExamples);
|
||||
|
||||
auto searchBox = new SearchBox(this);
|
||||
m_searcher = searchBox->m_lineEdit;
|
||||
using namespace StyleHelper::SpacingTokens;
|
||||
|
||||
auto grid = new QGridLayout(this);
|
||||
grid->setContentsMargins(0, 0, 0, WelcomePageHelpers::ItemGap);
|
||||
grid->setHorizontalSpacing(0);
|
||||
grid->setVerticalSpacing(WelcomePageHelpers::ItemGap);
|
||||
using namespace Layouting;
|
||||
Row titleRow {
|
||||
customMargin({0, 0, ExVPaddingGapXl, 0}),
|
||||
spacing(ExVPaddingGapXl),
|
||||
};
|
||||
|
||||
auto searchBar = WelcomePageHelpers::panelBar(this);
|
||||
auto hbox = new QHBoxLayout(searchBar);
|
||||
hbox->setContentsMargins(0, 0, 0, 0);
|
||||
m_searcher = new SearchBox;
|
||||
if (m_isExamples) {
|
||||
m_searcher->setPlaceholderText(Tr::tr("Search in Examples..."));
|
||||
|
||||
auto exampleSetSelector = new QComboBox(this);
|
||||
QPalette pal = exampleSetSelector->palette();
|
||||
// for macOS dark mode
|
||||
pal.setColor(QPalette::Text, Utils::creatorTheme()->color(Theme::Welcome_TextColor));
|
||||
exampleSetSelector->setPalette(pal);
|
||||
exampleSetSelector->setMinimumWidth(Core::WelcomePageHelpers::GridItemWidth);
|
||||
exampleSetSelector->setMaximumWidth(Core::WelcomePageHelpers::GridItemWidth);
|
||||
auto exampleSetSelector = new ComboBox;
|
||||
exampleSetSelector->setSizeAdjustPolicy(QComboBox::AdjustToContents);
|
||||
exampleSetSelector->setMinimumWidth(ListItemDelegate::itemSize().width()
|
||||
- ExVPaddingGapXl);
|
||||
exampleSetSelector->setModel(s_exampleSetModel);
|
||||
exampleSetSelector->setCurrentIndex(s_exampleSetModel->selectedExampleSet());
|
||||
titleRow.addItem(exampleSetSelector);
|
||||
connect(exampleSetSelector,
|
||||
&QComboBox::activated,
|
||||
s_exampleSetModel,
|
||||
@@ -305,23 +302,23 @@ public:
|
||||
&ExampleSetModel::selectedExampleSetChanged,
|
||||
exampleSetSelector,
|
||||
&QComboBox::setCurrentIndex);
|
||||
|
||||
hbox->setSpacing(Core::WelcomePageHelpers::HSpacing);
|
||||
hbox->addWidget(exampleSetSelector);
|
||||
} else {
|
||||
m_searcher->setPlaceholderText(Tr::tr("Search in Tutorials..."));
|
||||
}
|
||||
hbox->addWidget(searchBox);
|
||||
grid->addWidget(WelcomePageHelpers::panelBar(this), 0, 0);
|
||||
grid->addWidget(searchBar, 0, 1);
|
||||
grid->addWidget(WelcomePageHelpers::panelBar(this), 0, 2);
|
||||
titleRow.addItem(m_searcher);
|
||||
|
||||
auto gridView = new SectionedGridView(this);
|
||||
auto gridView = new SectionedGridView;
|
||||
m_viewController
|
||||
= new ExamplesViewController(s_exampleSetModel, gridView, m_searcher, isExamples, this);
|
||||
|
||||
gridView->setItemDelegate(&m_exampleDelegate);
|
||||
grid->addWidget(gridView, 1, 1, 1, 2);
|
||||
|
||||
Column {
|
||||
titleRow,
|
||||
gridView,
|
||||
spacing(ExVPaddingGapXl),
|
||||
customMargin({ExVPaddingGapXl, ExVPaddingGapXl, 0, 0}),
|
||||
}.attachTo(this);
|
||||
|
||||
connect(&m_exampleDelegate, &ExampleDelegate::tagClicked,
|
||||
this, &ExamplesPageWidget::onTagClicked);
|
||||
|
@@ -155,11 +155,16 @@ GroupItem GenericDeployStep::transferTask(const Storage<FilesToTransfer> &storag
|
||||
}
|
||||
}
|
||||
if (!m_emittedDowngradeWarning && transferMethod != preferredTransferMethod) {
|
||||
addWarningMessage(Tr::tr("Transfer method was downgraded from \"%1\" to \"%2\". If "
|
||||
const QString message
|
||||
= Tr::tr("Transfer method was downgraded from \"%1\" to \"%2\". If "
|
||||
"this is unexpected, please re-test device \"%3\".")
|
||||
.arg(FileTransfer::transferMethodName(preferredTransferMethod),
|
||||
FileTransfer::transferMethodName(transferMethod),
|
||||
deviceConfiguration()->displayName()));
|
||||
deviceConfiguration()->displayName());
|
||||
if (transferMethod == FileTransferMethod::GenericCopy)
|
||||
addWarningMessage(message);
|
||||
else
|
||||
addProgressMessage(message);
|
||||
m_emittedDowngradeWarning = true;
|
||||
}
|
||||
transfer.setTransferMethod(transferMethod);
|
||||
|
@@ -98,6 +98,7 @@ GenericLinuxDeviceConfigurationWidget::GenericLinuxDeviceConfigurationWidget(
|
||||
const int dmCount = dm->deviceCount();
|
||||
for (int i = 0; i < dmCount; ++i) {
|
||||
IDevice::ConstPtr dev = dm->deviceAt(i);
|
||||
if (dev->id() != device->id())
|
||||
m_linkDeviceComboBox->addItem(dev->displayName(), dev->id().toSetting());
|
||||
}
|
||||
|
||||
@@ -307,13 +308,18 @@ void GenericLinuxDeviceConfigurationWidget::initGui()
|
||||
Id linkDeviceId = Id::fromSetting(device()->extraData(Constants::LinkDevice));
|
||||
auto dm = DeviceManager::instance();
|
||||
int found = -1;
|
||||
int minus = 0;
|
||||
for (int i = 0, n = dm->deviceCount(); i < n; ++i) {
|
||||
if (dm->deviceAt(i)->id() == linkDeviceId) {
|
||||
const auto otherId = dm->deviceAt(i)->id();
|
||||
if (otherId == linkDeviceId) {
|
||||
found = i;
|
||||
break;
|
||||
} else if (otherId == device()->id()) {
|
||||
// Since we ourselves do not appear in the combo box, we need to adjust the index.
|
||||
minus = 1;
|
||||
}
|
||||
}
|
||||
m_linkDeviceComboBox->setCurrentIndex(found + 1); // There's the "Direct" entry first.
|
||||
m_linkDeviceComboBox->setCurrentIndex(found + 1 - minus); // There's the "Direct" entry first.
|
||||
|
||||
m_hostLineEdit->setText(sshParams.host());
|
||||
m_sshPortSpinBox->setValue(sshParams.port());
|
||||
|
@@ -356,13 +356,14 @@ QFont FontSettings::font() const
|
||||
{
|
||||
QFont f(family(), fontSize());
|
||||
f.setStyleStrategy(m_antialias ? QFont::PreferAntialias : QFont::NoAntialias);
|
||||
f.setWeight(fontNormalWeight());
|
||||
return f;
|
||||
}
|
||||
|
||||
int FontSettings::fontNormalWeight() const
|
||||
QFont::Weight FontSettings::fontNormalWeight() const
|
||||
{
|
||||
// TODO: Fix this when we upgrade "Source Code Pro" to a version greater than 2.0.30
|
||||
int weight = QFont::Normal;
|
||||
QFont::Weight weight = QFont::Normal;
|
||||
if (Utils::HostOsInfo::isMacHost() && m_family == g_sourceCodePro)
|
||||
weight = QFont::Medium;
|
||||
return weight;
|
||||
|
@@ -61,7 +61,7 @@ public:
|
||||
void setRelativeLineSpacing(int relativeLineSpacing);
|
||||
|
||||
QFont font() const;
|
||||
int fontNormalWeight() const;
|
||||
QFont::Weight fontNormalWeight() const;
|
||||
|
||||
bool antialias() const;
|
||||
void setAntialias(bool antialias);
|
||||
|
@@ -81,6 +81,10 @@ void SyntaxHighlighter::delayedRehighlight()
|
||||
if (!d->rehighlightPending)
|
||||
return;
|
||||
d->rehighlightPending = false;
|
||||
|
||||
if (document()->isEmpty())
|
||||
return;
|
||||
|
||||
rehighlight();
|
||||
}
|
||||
|
||||
@@ -197,6 +201,10 @@ void SyntaxHighlighterPrivate::reformatBlocks(int from, int charsRemoved, int ch
|
||||
|
||||
QList<SyntaxHighlighter::Result> vecRes;
|
||||
|
||||
SyntaxHighlighter::Result resStart;
|
||||
resStart.m_state = SyntaxHighlighter::State::Start;
|
||||
vecRes << resStart;
|
||||
|
||||
while (block.isValid() && (block.position() < endPosition || forceHighlightOfNextBlock)) {
|
||||
if (QThread::currentThread()->isInterruptionRequested())
|
||||
break;
|
||||
@@ -758,7 +766,10 @@ void SyntaxHighlighter::setExtraFormats(const QTextBlock &block,
|
||||
SyntaxHighlighter::Result res;
|
||||
res.m_formatRanges = block.layout()->formats();
|
||||
res.fillByBlock(block);
|
||||
emit resultsReady({res});
|
||||
res.m_state = SyntaxHighlighter::State::Extras;
|
||||
SyntaxHighlighter::Result resDone;
|
||||
resDone.m_state = SyntaxHighlighter::State::Done;
|
||||
emit resultsReady({res, resDone});
|
||||
|
||||
document()->markContentsDirty(block.position(), blockLength - 1);
|
||||
d->inReformatBlocks = wasInReformatBlocks;
|
||||
@@ -784,7 +795,10 @@ void SyntaxHighlighter::clearExtraFormats(const QTextBlock &block)
|
||||
SyntaxHighlighter::Result res;
|
||||
res.m_formatRanges = block.layout()->formats();
|
||||
res.fillByBlock(block);
|
||||
emit resultsReady({res});
|
||||
res.m_state = SyntaxHighlighter::State::Extras;
|
||||
SyntaxHighlighter::Result resDone;
|
||||
resDone.m_state = SyntaxHighlighter::State::Done;
|
||||
emit resultsReady({res, resDone});
|
||||
|
||||
document()->markContentsDirty(block.position(), blockLength - 1);
|
||||
d->inReformatBlocks = wasInReformatBlocks;
|
||||
|
@@ -54,7 +54,9 @@ public:
|
||||
TextEditor::FontSettings fontSettings() const;
|
||||
|
||||
enum State {
|
||||
Start,
|
||||
InProgress,
|
||||
Extras,
|
||||
Done
|
||||
};
|
||||
|
||||
@@ -71,7 +73,6 @@ public:
|
||||
|
||||
m_hasBlockUserData = true;
|
||||
m_foldingIndent = userDate->foldingIndent();
|
||||
m_folded = userDate->folded();
|
||||
m_ifdefedOut = userDate->ifdefedOut();
|
||||
m_foldingStartIncluded = userDate->foldingStartIncluded();
|
||||
m_foldingEndIncluded = userDate->foldingEndIncluded();
|
||||
@@ -83,10 +84,11 @@ public:
|
||||
{
|
||||
block.setUserState(m_userState);
|
||||
|
||||
if (m_hasBlockUserData) {
|
||||
if (!m_hasBlockUserData)
|
||||
return;
|
||||
|
||||
TextBlockUserData *data = TextDocumentLayout::userData(block);
|
||||
data->setExpectedRawStringSuffix(m_expectedRawStringSuffix);
|
||||
data->setFolded(m_folded);
|
||||
data->setFoldingIndent(m_foldingIndent);
|
||||
data->setFoldingStartIncluded(m_foldingStartIncluded);
|
||||
data->setFoldingEndIncluded(m_foldingEndIncluded);
|
||||
@@ -98,13 +100,11 @@ public:
|
||||
|
||||
data->setParentheses(m_parentheses);
|
||||
}
|
||||
}
|
||||
|
||||
int m_blockNumber;
|
||||
bool m_hasBlockUserData = false;
|
||||
|
||||
int m_foldingIndent : 16;
|
||||
uint m_folded : 1;
|
||||
uint m_ifdefedOut : 1;
|
||||
uint m_foldingStartIncluded : 1;
|
||||
uint m_foldingEndIncluded : 1;
|
||||
|
@@ -100,7 +100,6 @@ public:
|
||||
|
||||
SyntaxHighlighter *m_highlighter = nullptr;
|
||||
QTextDocument *m_document = nullptr;
|
||||
|
||||
signals:
|
||||
void resultsReady(const QList<SyntaxHighlighter::Result> &result);
|
||||
|
||||
@@ -130,6 +129,8 @@ SyntaxHighlighterRunner::SyntaxHighlighterRunner(SyntaxHighlighter *highlighter,
|
||||
&QTextDocument::contentsChange,
|
||||
this,
|
||||
&SyntaxHighlighterRunner::changeDocument);
|
||||
|
||||
m_foldValidator.setup(qobject_cast<TextDocumentLayout *>(document->documentLayout()));
|
||||
} else {
|
||||
connect(d,
|
||||
&SyntaxHighlighterRunnerPrivate::resultsReady,
|
||||
@@ -169,7 +170,12 @@ void SyntaxHighlighterRunner::applyFormatRanges(const QList<SyntaxHighlighter::R
|
||||
|
||||
for (const SyntaxHighlighter::Result &result : results) {
|
||||
m_syntaxInfoUpdated = result.m_state;
|
||||
if (m_syntaxInfoUpdated == SyntaxHighlighter::State::Start) {
|
||||
m_foldValidator.reset();
|
||||
continue;
|
||||
}
|
||||
if (m_syntaxInfoUpdated == SyntaxHighlighter::State::Done) {
|
||||
m_foldValidator.finalize();
|
||||
emit highlightingFinished();
|
||||
return;
|
||||
}
|
||||
@@ -181,12 +187,11 @@ void SyntaxHighlighterRunner::applyFormatRanges(const QList<SyntaxHighlighter::R
|
||||
result.copyToBlock(docBlock);
|
||||
|
||||
if (result.m_formatRanges != docBlock.layout()->formats()) {
|
||||
TextDocumentLayout::FoldValidator foldValidator;
|
||||
foldValidator.setup(qobject_cast<TextDocumentLayout *>(m_document->documentLayout()));
|
||||
docBlock.layout()->setFormats(result.m_formatRanges);
|
||||
m_document->markContentsDirty(docBlock.position(), docBlock.length());
|
||||
foldValidator.process(docBlock);
|
||||
}
|
||||
if (m_syntaxInfoUpdated != SyntaxHighlighter::State::Extras)
|
||||
m_foldValidator.process(docBlock);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -56,6 +56,7 @@ private:
|
||||
bool m_useGenericHighlighter = false;
|
||||
QString m_definitionName;
|
||||
std::optional<QThread> m_thread;
|
||||
TextDocumentLayout::FoldValidator m_foldValidator;
|
||||
};
|
||||
|
||||
} // namespace TextEditor
|
||||
|
@@ -3492,13 +3492,36 @@ QByteArray TextEditorWidget::saveState() const
|
||||
return state;
|
||||
}
|
||||
|
||||
bool TextEditorWidget::singleShotAfterHighlightingDone(std::function<void()> &&f)
|
||||
{
|
||||
if (d->m_document->syntaxHighlighterRunner()
|
||||
&& !d->m_document->syntaxHighlighterRunner()->syntaxInfoUpdated()) {
|
||||
connect(d->m_document->syntaxHighlighterRunner(),
|
||||
&SyntaxHighlighterRunner::highlightingFinished,
|
||||
this,
|
||||
[f = std::move(f)] { f(); }, Qt::SingleShotConnection);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void TextEditorWidget::restoreState(const QByteArray &state)
|
||||
{
|
||||
if (state.isEmpty()) {
|
||||
const auto callFoldLicenseHeader = [this] {
|
||||
auto callFold = [this] {
|
||||
if (d->m_displaySettings.m_autoFoldFirstComment)
|
||||
d->foldLicenseHeader();
|
||||
};
|
||||
|
||||
if (!singleShotAfterHighlightingDone(callFold))
|
||||
callFold();
|
||||
};
|
||||
|
||||
if (state.isEmpty()) {
|
||||
callFoldLicenseHeader();
|
||||
return;
|
||||
}
|
||||
|
||||
int version;
|
||||
int vval;
|
||||
int hval;
|
||||
@@ -3514,6 +3537,7 @@ void TextEditorWidget::restoreState(const QByteArray &state)
|
||||
if (version >= 1) {
|
||||
QList<int> collapsedBlocks;
|
||||
stream >> collapsedBlocks;
|
||||
auto foldingRestore = [this, collapsedBlocks] {
|
||||
QTextDocument *doc = document();
|
||||
bool layoutChanged = false;
|
||||
for (const int blockNumber : std::as_const(collapsedBlocks)) {
|
||||
@@ -3524,14 +3548,16 @@ void TextEditorWidget::restoreState(const QByteArray &state)
|
||||
}
|
||||
}
|
||||
if (layoutChanged) {
|
||||
auto documentLayout = qobject_cast<TextDocumentLayout*>(doc->documentLayout());
|
||||
QTC_ASSERT(documentLayout, return );
|
||||
auto documentLayout = qobject_cast<TextDocumentLayout *>(doc->documentLayout());
|
||||
QTC_ASSERT(documentLayout, return);
|
||||
documentLayout->requestUpdate();
|
||||
documentLayout->emitDocumentSizeChanged();
|
||||
}
|
||||
};
|
||||
if (!singleShotAfterHighlightingDone(foldingRestore))
|
||||
foldingRestore();
|
||||
} else {
|
||||
if (d->m_displaySettings.m_autoFoldFirstComment)
|
||||
d->foldLicenseHeader();
|
||||
callFoldLicenseHeader();
|
||||
}
|
||||
|
||||
d->m_lastCursorChangeWasInteresting = false; // avoid adding last position to history
|
||||
@@ -6642,6 +6668,9 @@ void TextEditorWidget::ensureCursorVisible()
|
||||
|
||||
void TextEditorWidget::ensureBlockIsUnfolded(QTextBlock block)
|
||||
{
|
||||
if (singleShotAfterHighlightingDone([this, block] { ensureBlockIsUnfolded(block); }))
|
||||
return;
|
||||
|
||||
if (!block.isVisible()) {
|
||||
auto documentLayout = qobject_cast<TextDocumentLayout*>(document()->documentLayout());
|
||||
QTC_ASSERT(documentLayout, return);
|
||||
@@ -8215,6 +8244,9 @@ void TextEditorWidget::foldCurrentBlock()
|
||||
|
||||
void TextEditorWidget::fold(const QTextBlock &block)
|
||||
{
|
||||
if (singleShotAfterHighlightingDone([this, block] { fold(block); }))
|
||||
return;
|
||||
|
||||
QTextDocument *doc = document();
|
||||
auto documentLayout = qobject_cast<TextDocumentLayout*>(doc->documentLayout());
|
||||
QTC_ASSERT(documentLayout, return);
|
||||
@@ -8235,6 +8267,9 @@ void TextEditorWidget::fold(const QTextBlock &block)
|
||||
|
||||
void TextEditorWidget::unfold(const QTextBlock &block)
|
||||
{
|
||||
if (singleShotAfterHighlightingDone([this, block] { unfold(block); }))
|
||||
return;
|
||||
|
||||
QTextDocument *doc = document();
|
||||
auto documentLayout = qobject_cast<TextDocumentLayout*>(doc->documentLayout());
|
||||
QTC_ASSERT(documentLayout, return);
|
||||
@@ -8254,6 +8289,9 @@ void TextEditorWidget::unfoldCurrentBlock()
|
||||
|
||||
void TextEditorWidget::unfoldAll()
|
||||
{
|
||||
if (singleShotAfterHighlightingDone([this] { unfoldAll(); }))
|
||||
return;
|
||||
|
||||
QTextDocument *doc = document();
|
||||
auto documentLayout = qobject_cast<TextDocumentLayout*>(doc->documentLayout());
|
||||
QTC_ASSERT(documentLayout, return);
|
||||
|
@@ -645,6 +645,7 @@ private:
|
||||
friend class Internal::TextEditorOverlay;
|
||||
friend class RefactorOverlay;
|
||||
|
||||
bool singleShotAfterHighlightingDone(std::function<void()> &&f);
|
||||
void updateVisualWrapColumn();
|
||||
};
|
||||
|
||||
|
Before Width: | Height: | Size: 106 B |
Before Width: | Height: | Size: 148 B |
BIN
src/plugins/welcome/images/link.png
Normal file
After Width: | Height: | Size: 186 B |
BIN
src/plugins/welcome/images/link@2x.png
Normal file
After Width: | Height: | Size: 236 B |
Before Width: | Height: | Size: 106 B After Width: | Height: | Size: 175 B |
Before Width: | Height: | Size: 112 B After Width: | Height: | Size: 225 B |
Before Width: | Height: | Size: 118 B After Width: | Height: | Size: 192 B |
Before Width: | Height: | Size: 161 B After Width: | Height: | Size: 255 B |